de:code 2017に登壇いたします

告知が直前となってしまいまして申し訳ございません。

毎年恒例となりましたMicrosoft主催の開発者&ITプロ向けのイベントであるde:code 2017に登壇します。

セキュリティトラックの中で「Active Directoryのディザスタリカバリ対策」というテーマで、いざという時にIT基盤のかなめであるActive Directoryをどう守るかについて事例を交えながらお話しできればと思っています。

既にチケットは完売しておりますが、参加される方はぜひ SC03 セッションまでお越し頂ければ幸いです。

第18回 Office365勉強会が開催されます

3/11(土) 13:00より、飯田橋のIIJセミナールームをお借りして第18回のOffice 365勉強会が開催されます。

今回のテーマは ”Identity&Security” Office 365から広がるクラウドの世界とそのセキュリティ対策 ということで、私も話を是非聴きたいと思っていた著名なスピーカーの方々をお招きしてOffice 365とそれを取り巻くセキュリティ関連の最新情報をお話しして頂く予定となっております。

既に満席になっている可能性はありますが、毎回一定数のキャンセルは発生しますので、是非キャンセル待ちに登録を頂ければと思います。

詳細は こちら をご参照下さい。

 

CSPパートナーがユーザーのAzureテナントにPowerShellで接続する

CSPパートナーがテナントを新規作成してAzureサブスクリプションを払い出した場合、CSPはユーザーテナントに対する代理管理者の権限を与えられます。この権限により、CSPはユーザーテナントの全体管理者として各種作業を代行することができます。

パートナーポータルからAzureポータルやOffice 365ポータルにも接続することができますが、PowerShellからも接続ができます。

通常アカウントと同じように、Login-AzureRmAccountでログインした後、Select-AzureRmSubscriptionにTenantIDとSubscriptionIDを指定しただけだと、Your Azure credentials have not been set up or have expired, please run Login-AzureRmAccount to set up your Azure Credentials.とエラーが表示されてしまいます。

よくよく見ると、Select-AzureRmSubscriptionの結果のContextにSubscriptionIDは入っていますが、SubscriptionNameが入っていないですね。正しく接続先の情報が渡せていないことが分かります。ここは、

Get-AzureRmSubscription -TenantId $TenantID -SubscriptionId $SubscriptionID | Set-AzureRmContext

としてあげれば、正しく接続することができるようになります。

web.configをPowerShellから変更する

Exchange Server 2013/2016では、これまでのバージョンと同じく細かいカスタマイズを行う場合やバグフィックスの為にweb.confgを直接書き換えるというシーンが比較的あります。

例)
クライアント固有のメッセージのサイズ制限を構成する
MSExchangeApplicationLogic Event 3018 in Exchange Server 2013
Exchange Server 2013 doesn’t display all OUs when it creates a new mailbox
Using security groups to set calendar permissions does not work in an Exchange resource forest

しかしながら、こちらのファイルはCUを適用する度にデフォルトの状態にリセットされます。CUは基本的に四半期に一度リリースされますので、3か月に一度設定がクリアされるという形になります。

毎回メモ帳などを利用して書き戻すというのも運用上の負担になりますし、作業漏れやミスによって障害が引き起こされる可能性もあるので、今回はその変更作業をPowerShellを利用して行いたいと思います。

web.configは中身はXMLドキュメントになりますので、Get-Contentした物を[XML]キャストすればPowerShellから普通に扱えます。例えば、KB2882961対策でOWAのweb.configのAppSettingsの欄に<add key=”UseDisabledAccount” value=”1″ />を加えたい場合、以下の様に行います。

$webConfig = "C:\Program Files\Microsoft\Exchange Server\V15\ClientAccess\Owa\web.config"
$AppSetKey = "UseDisabledAccount"
$AppSetValue = "1"
$doc = [XML](Get-Content $webConfig)
$newAppSetting = $doc.CreateElement("add")
$doc.configuration.appSettings.AppendChild($newAppSetting)
$newAppSetting.SetAttribute("key",$AppSetKey);
$newAppSetting.SetAttribute("value",$AppSetValue);
$doc.Save($webConfig)

同様に、AppSettings以外のsystem.webなどの項目も書き換えられます。例えば、ActiveSyncの最大送受信サイズを20MBにするためにCASのweb.configの既存のmaxRequestLengthを書き換える場合は、

$webConfig = "C:\Program Files\Microsoft\Exchange Server\V15\FrontEnd\HttpProxy\sync\web.config"
$ObjKey = "maxRequestLength"
$NewValue = "20480"
$doc = [XML](Get-Content $webConfig)
$obj = $doc.configuration."system.web".httpRuntime.$ObjKey
if ($obj -ne $null) {
  $doc.configuration."system.web".httpRuntime.$ObjKey = $NewValue
  $doc.Save($webConfig)
} else {
  Write-Host "$ObjKey not found"
}

こんな感じです。(既存の値を変更するので、既存の値の存在チェックを入れています)

なお、同様に既存のweb.configの設定内容を確認することもできますので、書き戻しが必要かどうかチェックする際にも利用することが可能です。色々ご自分の環境に合わせてお試しいただくと良いかと思います。

AAD ConnectでExchange属性が同期されない(その2)

先日の記事を投稿したところ、友人より「GUIだと手順が面倒だし、美しくない」と優しいコメントを頂きましたので、PowerShellで書き直してみました。

これだと既存のコネクタが一時的にDisableになったりしませんし、後から自分で追加したカスタムルールということが明示できるので今後のソフトウェアのアップデートなどの際に分かりやすいかもしれません。

※こちらのスクリプトはExchangeスキーマを拡張していない環境では実施しないで下さい(参照先のルールが存在しませんので)。また、マルチドメイン環境には対応していません。

# Get Connector ID
$conID = (Get-ADSyncConnector | ? {$_.Type -eq "AD"} | select -first 1).Identifier.Guid

New-ADSyncRule `
  -Name 'Custom rule from AD(Exchange)' `
  -Description 'User object with Exchange schema in Active Directory. (without mailNickname)' `
  -Direction 'Inbound' `
  -Precedence 200 `
  -SourceObjectType 'user' `
  -TargetObjectType 'person' `
  -Connector $conID `
  -LinkType 'Join' `
  -SoftDeleteExpiryInterval 0 `
  -ImmutableTag 'Custom rule from AD(Exchange)' `
  -OutVariable syncRule

# Get attribute flow mapping from existing rule "In from AD - User Exchange"
$AttributeFlowMappings = (Get-ADSyncRule | ? {$_.Name -eq "In from AD - User Exchange"}).AttributeFlowMappings
foreach ($mapping in $AttributeFlowMappings){
  Add-ADSyncAttributeFlowMapping -SynchronizationRule $syncRule[0] $mapping
}

New-Object `
  -TypeName 'Microsoft.IdentityManagement.PowerShell.ObjectModel.ScopeCondition' `
  -ArgumentList 'mailNickname','','ISNULL' `
  -OutVariable condition

Add-ADSyncScopeConditionGroup `
  -SynchronizationRule $syncRule[0] `
  -ScopeConditions @($condition[0]) `
  -OutVariable syncRule

Add-ADSyncRule -SynchronizationRule $syncRule[0]

 

DirSyncのAADConnectへのアップグレードが失敗する(その2)

前回の投稿では、古いバージョンのディレクトリ同期ツールからAAD Connectへのアップグレードの際にエラーが出ないようにする手順について紹介させて頂きました。

しかしながら、気づかずにこちらを実施せずアップグレードをしてしまうと、結構悲惨な状態になります。[問題が発生したため、統合を完了できません – Syncronization Serviceをインストールできません。詳細については、イベントログを参照して下さい。]と表示され、アップグレードが失敗します。

ログを確認すると、

[21:38:17.249] [ 12] [ERROR] PerformConfigurationPageViewModel: Caught exception while installing synchronization service.
Exception Data (Raw): System.Exception: Synchronization Service をインストールできません。詳細については、イベント ログを参照してください。 ---> System.DirectoryServices.AccountManagement.NoMatchingPrincipalException: Group 'FIMSyncAdmins' was not found.
   場所 Microsoft.Azure.ActiveDirectory.Synchronization.Framework.AccountManagementAdapter.GetGroupBySamAccountName(String groupSamAccountName)
   場所 Microsoft.Azure.ActiveDirectory.Synchronization.Setup.SynchronizationServiceSetupTask.<>c__DisplayClass9.<ValidateOrCreateSyncServiceGroups>b__8()
   場所 Microsoft.Azure.ActiveDirectory.Synchronization.Framework.ActionExecutor.Execute(Action action, String description)
   場所 Microsoft.Azure.ActiveDirectory.Synchronization.Setup.SynchronizationServiceSetupTask.InstallCore(String logFilePath, String logFileSuffix)
   場所 Microsoft.Azure.ActiveDirectory.Synchronization.Framework.ActionExecutor.ExecuteWithSetupResultsStatus(SetupAction action, String description, String logFileName, String logFileSuffix)
   場所 Microsoft.Azure.ActiveDirectory.Synchronization.Setup.SetupBase.Install()
   --- 内部例外スタック トレースの終わり ---
   場所 Microsoft.Azure.ActiveDirectory.Synchronization.Setup.SetupBase.ThrowSetupTaskFailureException(String exceptionFormatString, String taskName, Exception innerException)
   場所 Microsoft.Azure.ActiveDirectory.Synchronization.Setup.SetupBase.Install()
   場所 Microsoft.Online.Deployment.OneADWizard.Providers.EngineSetupProvider.SetupSyncEngine(String setupFilesPath, String installationPath, String sqlServerName, String sqlInstanceName, String serviceAccountName, String serviceAccountDomain, String serviceAccountPassword, String groupAdmins, String groupBrowse, String groupOperators, String groupPasswordSet, Int32 numberOfServiceInstances, ProgressChangedEventHandler progressChanged, NetworkCredential& serviceAccountCredential, SecurityIdentifier& serviceAccountSid)
   場所 Microsoft.Online.Deployment.OneADWizard.Runtime.Stages.InstallSyncEngineStage.ExecuteInstallCore(ISyncEngineInstallContext syncEngineInstallContext, ProgressChangedEventHandler progressChangesEventHandler)
   場所 Microsoft.Online.Deployment.OneADWizard.Runtime.Stages.InstallSyncEngineStage.ExecuteInstall(ISyncEngineInstallContext syncEngineInstallContext, ProgressChangedEventHandler progressChangesEventHandler)
   場所 Microsoft.Online.Deployment.OneADWizard.UI.WizardPages.PerformConfigurationPageViewModel.ExecuteSyncEngineInstallCore(AADConnectResult& result)

と表示されており、FIMSyncAdminsグループが存在しない為に発生したエラーだということが推測されます。

しかしながら、FIMSyncAdminsグループを作成後に[再試行]をクリックしても、[指定されたファイルが見つかりません。]というエラーが表示されて、続行できません。参照されたログファイルを見ても、続きを実施する方法は分かりません。

しかも都合の悪いことに、前のバージョンのディレクトリ同期は既にアンインストールされており、切り戻しもできません。再度インストーラーを走らせると新規インストールになってしまいます。

全てデフォルト設定で利用していた場合はそれで良いかもしれませんが、同期フィルタなどを設定していた場合はどんな設定をしただろう…?って途方に暮れてしまうこともあるかと思います。

根気強くログファイルを見ていくと、見つけました!

[21:27:25.520] [ 10] [INFO ] AnalyzeDirSyncInstallation: Backing up DirSync upgrade settings to C:\Users\syncadm\AppData\Local\AADConnect\DirSyncSettings20161214-212725.xml.

どうやら、Appdata\Local\AADConnect配下に作業ファイルとして古い設定データがxmlで保存されているようです。(syncadmはAAD Connectのインストールを実行したユーザー名ですので、皆さんの環境に合わせて読み替えて下さい)

※OUの除外設定などもきちんと残っています。

これさえ分かれば、後は並列デプロイと同じ方式で移行を継続できます。

PS C:\> cd "C:\Program Files\Microsoft Azure Active Directory Connect\"
PS C:\Program Files\Microsoft Azure Active Directory Connect> .\AzureADConnect.exe /migrate

設定ファイルをインポートする画面が出ますので、先ほどのフォルダ配下の.xmlファイルを指定します。また、[既存のサービスアカウントを使用する]のチェックボックスを外して[次へ]を選択します。
 

ここから先は通常のアップグレードと同じです。Azure ADの管理者アカウントの情報を入力し、次にオンプレミスのActive DirectoryのEnterprise Adminsのアカウント情報を入力し、最後に[インストール]を実施します。

これでインストールは完了です。インストール後の状態は、ステージングモードというエクスポートのみ行わない状態になっています。

MIISClientなどで既存のフィルタがきちんと移行されているかを確認(新しいMIISClientのパスはC:\Program Files\Microsoft Azure AD Sync\UIShell\miisclient.exeです。実行には一度ログオフが必要です)した後に、ステージングモードを解除します。

Azure AD Connectを起動し、[構成][ステージングモードの構成][次へ]を選択。Azure ADの管理者情報を入力、[ステージングモードを有効にする]のチェックボックスを外して[次へ]をクリックし、最後に[構成]をクリックします。
 

これで、無事AAD Connectの環境に移行できました。

良いAADライフを!

AAD Connect更新時のSfB属性

AAD ConnectでExchange属性が同期されない の記事でオンプレミスのExchange関連のスキーマについて記載させて頂いた所、Skype for Business(Lync)関連のスキーマについてはどうなのかと質問を頂きましたので、簡単に調べた範囲で記載したいと思います。

オンプレミスのADスキーマがSkype for Business(Lync)用の拡張がされていた場合、以下の3つの同期ルールが作成されます。

・In from AD – User Lync
対象:user
以下の属性を同期する:
msRTCSIP-ApplicationOptions
msRTCSIP-DeploymentLocator
msRTCSIP-Line
msRTCSIP-OrganatorSid
msRTCSIP-OwnerUrn
msRTCSIP-PrimaryUserAddress
msRTCSIP-UserEnabled
msRTCSIP-OptionFlags

・In from AD – InetOrgPerson Lync
対象:inetOrgPerson
以下の属性を同期する:
msRTCSIP-ApplicationOptions
msRTCSIP-DeploymentLocator
msRTCSIP-Line
msRTCSIP-OwnerUrn
msRTCSIP-PrimaryUserAddress
msRTCSIP-UserEnabled
msRTCSIP-OptionFlags

・In from AD – Contact Lync
対象:contact
以下の属性を同期する:
msRTCSIP-DeploymentLocator
msRTCSIP-Line
msRTCSIP-PrimaryUserAddress
msRTCSIP-OptionFlags

20161214_01

msRTCSIP-OrganatorSid 以外の属性では、既存のディレクトリ同期ツールにおいては無条件で(存在する場合)同期されていました。

msRTCSIP-OrganatorSid については、リソースフォレストモデルでの展開の際に利用されるものですので、AAD Connectから新規で追加された同期ルールになります。

従いまして、Exchangeの様に特に同期する対象を限定するスコープなど設定されていないため、アップグレード前後で同期属性が変化することは、Skype for Business Serverをリソースフォレストモデルで展開していない限りはないと言えます。

AAD ConnectでExchange属性が同期されない

ディレクトリ同期(AAD Sync含む)を利用する環境下において、Exchange Onlineで利用されるユーザーなどの情報は全てオンプレミスのActive Directoryから同期され、管理されます。

このため、アドレス帳非表示やふりがななど、メールボックスの動作を制御する一部の属性はオンプレミス側で設定し、同期する必要があります。オンプレミスにExchange Serverが無い場合は、その設定用にスキーマ拡張のみして属性を設定するというTIPSは比較的昔から存在していました。
20161210_01

こういった環境のユーザーがAAD Connectに移行すると、アドレス帳非表示の設定が消えるなどの影響が出る可能性があり、注意が必要です。

なぜかと言いますと、AAD Connectはマルチフォレスト(特にリソースフォレストモデルのExchange環境)のサポートを行うために、デフォルトの同期ルールが以前と変わっています。

具体的には、ユーザーの属性を取得する際に、Exchange関連の属性は[mailNickname]が存在するフォレストから取得するようになっています。

従来:
Azure Active Directory に同期した属性値のリスト

AAD Connect:
Azure AD Connect Sync: 既定の構成について

MailNicknameは、Exchangeにおける[Alias]として利用されている物であり、Exchange関連のオブジェクトであれば必須のパラメータとなっています。しかし、ADSIエディタなどでアドレス帳非表示を制御するmsExchHideFromAddressListsなどの属性を加える運用を行っている場合、mailNicknameには値が入っていないというケースが多くあります。

PowerShellから、Exchange属性であるmsExchHideFromAddressListsを同期しているルールを確認すると、[In from AD – User Exchange]というルールで同期されており、そのフィルタの条件はmailNicknameがNOTNULLということが分かります。
20161210_02

Get-ADSyncRule | ? {$_.AttributeFlowMappings -like "*msExchHideFromAddressLists*" -and $_.SourceObjectType -eq "user"} |  ft Name,Identifier
(Get-ADSyncRule -Identifier [上記で表示されたID]).ScopeFilter.ScopeConditionList

同様に、ルールエディタからも確認ができます。
20161210_03 20161210_04

※このルールはExchangeのスキーマ拡張がされていない環境では作成されません。また、マルチフォレスト、マルチドメイン環境では複数作成されます。

さて、では対応するためにどうしていけばいいかですが、アプローチとしては2つあります。

  1. [In from AD – User Exchange]のデフォルトルールを変更し、mailNicknameによる条件フィルタ自体を削除する
  2. [In from AD – User Exchange]をコピーして新たなルールを作成し、条件をmailNickname = NULLにする

いずれも、全ユーザーに対してExchange関連属性を設定するという意味では同じですが、今後ソフトウェア更新をした際にデフォルト設定が変わる可能性があるリスクを考えると2番の方がメンテナンス性は良いかと個人的には考えてます。(1で実装する場合も、実際には元の設定は無効化し、コピーしたルールに手を加えます)

手順ですが、Syncroniztion Rule Editorを管理者として起動し、[In from AD – User Exchange]のEditを選択する。この際、[YES:既存のルールのコピーを作成し、オリジナルのルールを無効化する][No:既存のルールの変更を続ける]の確認が表示されるので、[はい(Y)]を選択する。
20161210_05 20161210_09

Precedenceに既存と重複せず、元のルールより低い(数値の大きい)値を設定する。(ここでは200とした。)Scoping Filterの項目を開き、プルダウンのISNOTNULLをISNULLに変更する。
20161210_06 20161210_07

もう一度[In from AD – User Exchange]の[Edit]を選択し、今度は[いいえ(N)]を選択してオリジナルのルールを開く。[Disabled]のチェックを外して保存。
20161210_08

なお、既定の構成に手を加える場合は、以下の記事を一読されることをお勧め致します。 Azure AD Connect Sync: 既定の構成を変更するためのベスト プラクティス

DirSyncのAADConnectへのアップグレードが失敗する

ディレクトリ同期ツールをAAD Connectにインプレースアップグレードする際、以下のような画面が出てアップグレードに失敗することがあります。
gw-00003 gw-00004

参照されるログファイルを確認すると、以下のエラーが記録されています。

[11:27:39.819] [ 18] [ERROR] PerformConfigurationPageViewModel: Caught exception while installing synchronization service.
Exception Data (Raw): System.Exception: Synchronization Service をインストールできません。詳細については、イベント ログを参照してください。 ---> System.DirectoryServices.AccountManagement.NoMatchingPrincipalException: Group 'FIMSyncAdmins' was not found.
 場所 Microsoft.Azure.ActiveDirectory.Synchronization.Framework.AccountManagementAdapter.GetGroupBySamAccountName(String groupSamAccountName)

FIMSyncAdminsグループが見つからないというエラーです。確かに、FIMSyncAdminsグループができたのはパスワード同期が実施できるようになった辺りのバージョンなので、ローカルには存在していませんでした。
gw-00006

グループが無いのがエラーの原因と記載されていますので、単純にメンバーは空で良いのでグループを作ってみます。(説明はMIISAdminから適当にコピーしましたが、無くても問題ないです)
gw-00007

これでインストールを行ったところ、問題なく実施ができるようになります。
gw-00009

ちなみに、FIMSyncAdminsグループにはオンプレミスのADとディレクトリ情報をやり取りするアカウント AAD_<nnnnnnnnnnnn>とAAD Connectのインストールに利用したアカウントがデフォルトでメンバーとして登録されます。

また、Microsoftから提示されている標準手順のうち、並列デプロイの方式を利用してもアップグレードは可能ですので、オブジェクトの数や作業の手間などを考えて、どうアップグレードするか決めるといいと思います。

PowerShellから全社員に予定を投入したい

この投稿は PowerShell Advent Calendar 2016 に参加しています。

Facebookでリスクエストを貰ったので、PowerShellから全社員のメールボックスに創立記念日の予定を入れるPowerShellを作ってみたいと思います。

Exchangeでは、各種管理用のPowerShellコマンドレットが用意されておりますが、各ユーザーのメールボックスのアイテムを直接操作するコマンドは基本的には有りません。

ただし、EWS(Exchange Web Services)というAPIが用意されておりますので、そちらを利用する事によりメールボックスにアプローチできます。また、その際にアプリケーション偽装(ApplicationImpersonation)権限を利用する事により、「そのユーザーに成り代わって」そのアイテムを利用できます。

今回は、目的が明確ですので一番近いサンプルスクリプトとして以下のstack overflowに回答例として提示されているスクリプトを元に、作成用のコマンドを作ってみます。

How to import meetings into office365 (EWS and Powershell?)

変更している所は赤字で記載します。

function Create-Appointment
{
    [CmdletBinding()]
    param(
        [Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
        [Parameter(Position=1, Mandatory=$true)] [string]$Subject,
        [Parameter(Position=2, Mandatory=$true)] [DateTime]$Start,
        [Parameter(Position=3, Mandatory=$true)] [DateTime]$End,
        [Parameter(Position=4, Mandatory=$true)] [AllowEmptyString()] [string]$Location,
        [Parameter(Position=5, Mandatory=$true)] [AllowEmptyString()] [string]$Body,
        [Parameter(Position=6, Mandatory=$true)] [Boolean]$IsAllDayEvent,
        [Parameter(Position=7, Mandatory=$true)] [PSCredential]$Credentials
    )
    Begin
    {
        $EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll")
        if (Test-Path $EWSDLL)
        {
            Import-Module $EWSDLL
        }
        else
        {
            "$(get-date -format yyyyMMddHHmmss):"
            "This script requires the EWS Managed API 1.2 or later."
            "Please download and install the current version of the EWS Managed API from"
            "http://go.microsoft.com/fwlink/?LinkId=255472"
            ""
            "Exiting Script."
            exit
        }

        $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2
        $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)
        $creds = New-Object System.Net.NetworkCredential($Credentials.UserName.ToString(),$Credentials.GetNetworkCredential().password.ToString())
        $service.Credentials = $creds
        $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider
        $Compiler=$Provider.CreateCompiler()
        $Params=New-Object System.CodeDom.Compiler.CompilerParameters
        $Params.GenerateExecutable=$False
        $Params.GenerateInMemory=$True
        $Params.IncludeDebugInformation=$False
        $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null
$TASource=@'
  namespace Local.ToolkitExtensions.Net.CertificatePolicy{
    public class TrustAll : System.Net.ICertificatePolicy {
      public TrustAll() { 
      }
      public bool CheckValidationResult(System.Net.ServicePoint sp,
        System.Security.Cryptography.X509Certificates.X509Certificate cert, 
        System.Net.WebRequest req, int problem) {
        return true;
      }
    }
  }
'@ 
        $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
        $TAAssembly=$TAResults.CompiledAssembly
        $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
        [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll
        $service.AutodiscoverUrl($MailboxName,{$true})
        #"Using CAS Server : " + $Service.url
        $service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
        $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$MailboxName)
        $Calendar = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
        $Appointment = New-Object Microsoft.Exchange.WebServices.Data.Appointment -ArgumentList $service
        $Appointment.Start = $Start
        $Appointment.End = $End
        $Appointment.Subject = $Subject
        $Appointment.Location = $Location
        $Appointment.Body = $Body
        $Appointment.IsAllDayEvent = $IsAllDayEvent
        $Appointment.Save($Calendar.Id,[Microsoft.Exchange.WebServices.Data.SendInvitationsMode]::SendToNone)
    }
}
こちらを実行するためには、まず実行するアカウント(ここでは、仮にews_userとします)にApplicationImpersonation権限を付与します。ExchangeにPower Shellで接続し、以下のコマンドレットを実行します。
New-ManagementRoleAssignment –Name:EWSCalendarApplication -Role:ApplicationImpersonation –User:ews_user

続いて、PowerShellを実行するコンピュータにEWS Managed APIをインストールします。

PowerShellを開き、上記コードを実行し、Create-AppointmentのFunctionを登録します。

最後に、以下のコマンドを実行します。最初の行では実行アカウントのID/PASSを入力します。

$LiveCred = Get-Credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $LiveCred -Authentication Basic -AllowRedirection
Import-PSSession $Session -AllowClobber

$results = Get-Mailbox -RecipientTypeDetails UserMailbox -ResultSize Unlimited | select WindowsEmailAddress

foreach ($result in $results){
    Create-Appointment -MailboxName $result.WindowsEmailAddress -Subject "創立記念日" -Start "2017/07/01" -End "2017/07/01" -Body $null -Location $null -IsAllDayEvent $true -Credential $LiveCred
}

これで、来年の7/1に終日イベントとして[創立記念日]が全ユーザーMBXに作成されます。

 

他にも、これを応用すれば会議室の利用状況を集計したり、役員の今日の予定の印刷用の元データを抜いたりできます。

また、このコマンドを行うには対象となるメールボックスが先に作成されている必要があります。メールボックスはNew-Mailboxコマンドなどで作成した時点では実体は作成されていません。ユーザー自身のログイン以外にも、以下の様な動作を行う事により作成されますので、エラーが出て登録出来ないメールボックスには試してみると良いでしょう。

  1. メールの受信(例えば、メールの開通通知などの名目でテストメールを送る)
  2. 受信トレイルールの作成(管理者がNew-InboxRuleコマンドレットで作成できます。ダミーのルールを作成し、すぐに消せば影響は無いと思います)