Azure AD ユーザーアカウントの棚卸しに便利なスクリプト


Azure AD 上のすべてのユーザーがサインインできているか、使われなくなったアカウントを特定したいなど、ユーザーアカウントの棚卸ししたいシーンがあると思います。

「サインインできていますか？」、「このアカウント使っていますか？」と、関係者全員に問い合わせしますか？問いかけに答えてくれるなら良いですが、無反応な方もいますよね。そもそも、この作業自体がとってもめんどくさい。なので、ここはビジネスライクで機械的にやりましょう。

Azure AD ユーザーアカウントの棚卸しについて、以下のドキュメントに書いてあるように「最後にサインインが成功した日時」で判断しちゃいましょう。

[参考：Azure AD で非アクティブなユーザー アカウントを管理する](https://docs.microsoft.com/ja-jp/azure/active-directory/reports-monitoring/howto-manage-inactive-user-accounts?WT.mc_id=AZ-MVP-5003113)

「最後にサインインが成功した日時」を確認するには、以下の方法が挙がると思います。

- [Azure AD サインインレポート](https://docs.microsoft.com/ja-jp/azure/active-directory/reports-monitoring/concept-sign-ins?WT.mc_id=AZ-MVP-5003113)で確認する
- [Microsoft Graph API](https://docs.microsoft.com/ja-jp/graph/overview?WT.mc_id=AZ-MVP-5003113&view=graph-rest-beta#whats-in-microsoft-graph) で問い合わせる

前者の方法だと、[サインインログの保存期間](https://docs.microsoft.com/ja-jp/azure/active-directory/reports-monitoring/reference-reports-data-retention?WT.mc_id=AZ-MVP-5003113#how-long-does-azure-ad-store-the-data) (エディションにより異なる) が決まっているため、正確に判断することができません。
後者の方法では、Microsoft Graph API の [signInActivity アクティブリソースの種類](https://docs.microsoft.com/ja-jp/graph/api/resources/signinactivity?WT.mc_id=AZ-MVP-5003113&view=graph-rest-beta) の lastSignInDateTime プロパティの値を確認することで、前者の方法の悩みが解消できます。

この記事では、Azure AD ユーザーアカウントの棚卸しの助けとなる以下の便利な PowerShell スクリプトが公開されていましたので、このスクリプトの使い方、カスタマイズ方法を記します。

[参考：jpazureid / get-last-signin-reports (use-signin-activity-beta-api branch)](https://github.com/jpazureid/get-last-signin-reports/tree/use-signin-activity-beta-api)

※この PowerShell スクリプトは予告なく更新されると思いますので、更新された場合は良しなに読み替えていただけると幸いです。


# スクリプトの準備

まず、[ブランチ](https://github.com/jpazureid/get-last-signin-reports/tree/use-signin-activity-beta-api)からスクリプト一式 (ZIP) を[ダウンロード](https://github.com/jpazureid/get-last-signin-reports/archive/use-signin-activity-beta-api.zip)します。
次に、任意のフォルダー (例えば C:\SignInReport) を作成して、ダウンロードした ZIP ファイルを解凍して、このフォルダーに配置します。


## スクリプトを実行できるようにポリシーを変更する

実行ポリシーを確認します。PowerShell を管理者として実行し、Get-ExecutionPolicy を実行して、その結果が「RemoteSigned」であれば問題ありません。

```
> Get-ExecutionPolicy
RemoteSigned
```

「RemoteSigned」でない場合は、Set-ExecutionPolicy で実行ポリシーを変更します。

```
> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned                                               
実行ポリシーの変更
実行ポリシーは、信頼されていないスクリプトからの保護に役立ちます。実行ポリシーを変更すると、about_Execution_Policies
のヘルプ トピック (https://go.microsoft.com/fwlink/?LinkID=135170)
で説明されているセキュリティ上の危険にさらされる可能性があります。実行ポリシーを変更しますか?
[Y] はい(Y)  [A] すべて続行(A)  [N] いいえ(N)  [L] すべて無視(L)  [S] 中断(S)  [?] ヘルプ (既定値は "N"): Y
```


## スクリプト実行に必要なライブラリを取得する

スクリプトを実行するために、必要なライブラリを取得します。
[README.md](https://github.com/jpazureid/get-last-signin-reports/tree/use-signin-activity-beta-api#get-last-sign-in-activity--reports) に掲載されている以下の手順に従って操作します。

[参考：1.事前準備](https://github.com/jpazureid/get-last-signin-reports/tree/use-signin-activity-beta-api#1%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99)

> ・処理に必要なライブラリを nuget で取得するスクリプトの準備と実行
>
> GetModuleByNuget.ps1 を C:\SignInReport フォルダー配下に保存し実行します。
> 
> トークン取得処理に必要なライブラリを nuget で取得します。

上記のスクリプトでは、[Microsoft.Identity.Client](https://www.nuget.org/packages/microsoft.identity.client) など、トークン取得処理に必要なライブラリを取得して任意のフォルダー (例えば C:\SignInReport) 配下の Tools フォルダーに保存しますので、忘れずに実行しましょう。


# アプリケーションの登録

Microsoft Graph API にアクセスする場合は、Azure AD にアプリケーションを登録する必要があります。
[Azure ポータル](https://portal.azure.com)にサインインして、Azure Active Directory > アプリの登録 を開き、[新規登録] をクリックします。

以下の項目を入力して、[登録] をクリックします。

| 項目 | 入力値 |
|:-----|:-------|
| 名前 | 適当な名前 |
| サポートされているアカウントの種類 | 「この組織ディレクトリのみに含まれるアカウント」を選択 |
| リダイレクト URI (省略可能) | https://localhost |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/1cc6ad2d-c949-de13-3c73-a99307c615e7.png)

無事登録できたら、[概要] の**「アプリケーション (クライアント) ID」**と**「ディレクトリ (テナント) ID」**をメモしておきましょう。
※あとで使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/5a394998-eafd-7f76-ce8b-96a3c1ca96fa.png)


## 証明書とシークレット

Microsoft API を使用してサインインレポートへアクセス、またはサインインする時、エラーが発生しないように、**「証明書を用意してアップロードする」**か、または**「クライアントシークレットを取得」**します。(どちらかで構いません)

Azure Active Directory > アプリの登録 を開き、登録したアプリケーション (例えば Reporting API application) を選択します。そして、[管理] カテゴリ配下の [証明書とシークレット] をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/37b8e562-8ed4-e229-c26c-7dcd5e6f506e.png)

そして、「証明書を利用する場合」または「クライアントシークレットを利用する場合」のいずれかの手順に進みます。


### 証明書を利用する場合

[README.md](https://github.com/jpazureid/get-last-signin-reports/tree/use-signin-activity-beta-api#get-last-sign-in-activity--reports) に掲載されている以下の手順に従って、証明書とクライアント側の環境を準備します。

[参考：1.事前準備](https://github.com/jpazureid/get-last-signin-reports/tree/use-signin-activity-beta-api#1%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99)

> ・認証に使用する証明書の作成
>
> CreateAndExportCert.ps1 を実行します。
>
> CreateAndExportCert.ps1 は自己署名証明書を生成し、ユーザーの証明書ストア (個人) に格納します。さらに、公開鍵を含む証明書 (SelfSignedCert.cer ファイル) をカレント ディレクトリに出力します。

そして、証明書 (SelfSignedCert.cer) をアップロードします。

[証明書とシークレット] で、[証明書のアップロード] をクリックして、「証明書」をアップロードします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/7e9c3ba6-2641-b6b0-01f8-288ed77e6c35.png)

「証明書」がアップロードできたら、**「捺印の値」**をメモしておきましょう。
※あとで使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/b0f4e832-9888-ad4d-5a7c-c86cfc483f51.png)


### クライアントシークレットを利用する場合 

[証明書とシークレット] で、[新しいクライアント シークレット] をクリックして、「クライアント シークレット」を取得します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/1383ba53-5f85-8bc8-d70c-07c98d3ca792.png)

「クライアント シークレット」が取得できたら、**「クライアント シークレットの値」**をメモしておきましょう。
※クライアント シークレットの値は**登録直後にしか表示されません**ので、忘れずにコピーしましょう。
※あとで使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/0494cf75-08c3-e720-76d1-06b9d6b1422e.png)


## API のアクセス許可

Microsoft Graph API の signInActivity アクティブリソースの種類のプロパティ「lastSignInDateTime」を読み取るためには、アプリケーションに対して以下の権限を付与する必要があります。

- AuditLog.Read.All
- Organisation.Read.All

その他に、ユーザー情報 (UPN などのプロファイル情報) を読み取るための権限も付与する必要があります。
とりあえず、アプリケーションに対して以下の権限を付与します。

- User.Read.All

ということで、アプリケーションにアクセス許可を付与します。

まず、Azure Active Directory > アプリの登録 を開き、登録したアプリケーション (例えば Reporting API application) を選択します。そして、[管理] カテゴリ配下の [API のアクセス許可] をクリックします。

[API のアクセス許可] で [アクセス許可の追加] をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/6334693e-9f96-c41c-abe0-ce087c837ea4.png)

[API アクセス許可の要求] で 「Microsoft Graph」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/a94a45ea-1f8e-570e-dd0a-89faa8990ee4.png)

[アプリケーションに必要なアクセス許可の種類] で [アプリケーションの許可] を選択し、[アクセス許可を選択する] で前述した権限をひとつずつ探して選択して、[アクセス許可の追加] をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/45f89f30-852b-f88a-0105-4d398c83d9de.png)

最後に、追加した API のアクセス許可に対して管理者の同意を付与します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/b077fde8-a1e9-9d8e-2eab-99698da8d2ac.png)

これで、アプリケーションに対して各 API に対してアクセス許可が付与できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/0137fe4a-76c9-b2ba-6c90-ddfdaa7593ff.png)

以上で、アプリケーションの登録は完了です。


# スクリプトの実行

PowerShell を開き、前述で作成した任意のフォルダー (例えば、C:\SignInReport) に移動します。そして、「GetLastSignIn.ps1」を実行してみましょう。


## 証明書を利用する場合

証明書を使う場合は、以下のようなコマンドを実行します。

```PowerShell:&nbsp;(証明書を利用する場合)
> .\GetLastSignIn.ps1 `
    -authMethod Cert `
    -clientSecretOrThumbprint <アップロードした証明書の捺印値> `
    -tenantId <ディレクトリ (テナント) ID> `
    -clientId <アプリケーション (クライアント) ID> `
    -outfile <出力先の CSV ファイルパス>
```


## クライアントシークレットを利用する場合

クライアント シークレットを使う場合は、以下のようなコマンドを実行します。

```PowerShell:&nbsp;(クライアント&nbsp;シークレットを利用する場合)
> .\GetLastSignIn.ps1 `
    -authMethod Key `
    -clientSecretOrThumbprint <取得したクライアントシークレット値> `
    -tenantId <ディレクトリ (テナント) ID> `
    -clientId <アプリケーション (クライアント) ID> `
    -outfile <出力先の CSV ファイルパス>
```


## 実行結果を確認

実行中、以下のような例外が発生するかもしれませんが、気にせず終わるまで待ちます。
※原因は、あるユーザーの「lastSignInDateTime」プロパティ値が取得できなかったためです。

```
ConvertFrom-Json : 引数が null であるため、パラメーター 'InputObject' にバインドできません。
発生場所 C:\SignInReport\GetLastSignIn.ps1:104 文字:53
+                 $signInEvent = ($signInEventJason | ConvertFrom-Json)
+                                                     ~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [ConvertFrom-Json]、ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.ConvertFromJsonCommand
```

出力された CSV ファイルの中身は以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/02550b03-b3b2-2d40-a2a2-d8ec44c80889.png)

「Last sign-in event date in UTC」列は、そのユーザーの最後にサインインした日時 (UTC) が出力されます。ここに「This user never had sign in activity event.」と記載されているユーザーは、**「一度もサインイン (が成功) していない」** (または「最後のサインインが 2019/12/01 より前である」) ということを表しています。

[参考：空のプロパティ値は何を意味するのでしょうか?](https://docs.microsoft.com/ja-jp/azure/active-directory/reports-monitoring/howto-manage-inactive-user-accounts?WT.mc_id=AZ-MVP-5003113#what-does-a-blank-property-value-mean)

なお、Microsoft API にリクエストして返されるレコード数は上限 100 件となっています。

[参考：Microsoft Graph に関する既知の問題 > スキーマ拡張機能のプロパティ値はリソースのインスタンスごとに 100 に制限されています。](https://docs.microsoft.com/ja-jp/graph/known-issues?WT.mc_id=AZ-MVP-5003113#limit-of-100-schema-extension-property-values-allowed-per-resource-instance)

「[GetLastSignIn.ps1](https://github.com/jpazureid/get-last-signin-reports/blob/use-signin-activity-beta-api/GetLastSignIn.ps1)」ではその制限を考慮して 100 件を超過しても問題なく出力できるようになっていますので、ご安心ください。
※ただし、ヘッダー行は API を繰り返し呼び出した回数分、出力されてしまいます。


## Microsoft Graph API へのリクエストをカスタマイズ

実際に、Microsoft Graph API に対するリクエストは、[GetLastSignIn.ps1](https://github.com/jpazureid/get-last-signin-reports/blob/use-signin-activity-beta-api/GetLastSignIn.ps1) の 50 行目にあります。また、*$resource* には、*"https://graph.microsoft.com"* がセットされています。スクリプトを実行する際にパラメーター *-resource* で指定することもできます。

ということで、*$reqUrl* の実際の値は *"https://graph.microsoft.com/beta/users/?`$select=userPrincipalName,signInActivity"* となっています。

```powershell:GetLastSignIn.ps1&#58;50行目&nbsp;(オリジナル)
$reqUrl = "$resource/beta/users/?`$select=userPrincipalName,signInActivity"
```

よって、このリクエストをカスタマイズすることで、さらに好みのシナリオが解決できると思います。
また、カスタマイズしたリクエストは、[Microsoft Graph Explorer](https://developer.microsoft.com/ja-jp/graph/graph-explorer?WT.mc_id=AZ-MVP-5003113) や [Postman](https://www.postman.com/) などでお試しされることをおすすめします。 

[参考：Microsoft Graph API を使用する](https://docs.microsoft.com/ja-jp/graph/use-the-api?WT.mc_id=AZ-MVP-5003113&context=graph/api/1.0)
[参考：クエリ パラメーターを使用して応答をカスタマイズする](https://docs.microsoft.com/ja-jp/graph/query-parameters?WT.mc_id=AZ-MVP-5003113)
[参考：Microsoft Graph REST API v1.0 リファレンス](https://docs.microsoft.com/ja-jp/graph/api/overview?WT.mc_id=AZ-MVP-5003113&toc=.%2Fref%2Ftoc.json&view=graph-rest-1.0)
[参考：Microsoft Graph ベータ版のエンドポイント リファレンス](https://docs.microsoft.com/ja-jp/graph/api/overview?WT.mc_id=AZ-MVP-5003113&toc=.%2Fref%2Ftoc.json&view=graph-rest-beta)
[参考：Microsoft Graph API で Postman を使用する](https://docs.microsoft.com/ja-jp/graph/use-postman?WT.mc_id=AZ-MVP-5003113&context=graph/api/beta&view=graph-rest-beta)


### 条件に一致するユーザーのみ出力したい

抽出するユーザーを絞り込みたい場合は、filter クエリ パラメーターを編集します。

[参考：クエリ パラメーターを使用して応答をカスタマイズする > filter パラメーター](https://docs.microsoft.com/ja-jp/graph/query-parameters?WT.mc_id=AZ-MVP-5003113#filter-parameter)


#### 例1) メンバーのみ抽出したい場合

```powershell:GetLastSignIn.ps1&#58;50行目&nbsp;(メンバーのみ)
$reqUrl = "$resource/beta/users/?`$filter=userType eq 'member'&`$select=userPrincipalName,signInActivity"
```

#### 例2) 指定日以降にサインインしたユーザーのみ抽出したい場合

```powershell:GetLastSignIn.ps1&#58;50行目&nbsp;(指定日以降にサインイン)
$reqUrl = "$resource/beta/users?`$filter=signInActivity/lastSignInDateTime ge 2020-10-01T00:00:00Z&`$select=userPrincipalName,signInActivity"
```

※filter パラメーターに「signInActivity アクティブリソースの種類」を含めてリクエストを実行する場合は、Azure AD Premium が必要のようです。


### ユーザーの他のプロパティも出力したい

他のプロパティの値を出力するには、複数の箇所を変更する必要があります。
以下より、「表示名 (displayName)」を追加して出力したい場合の例を示します。


#### リクエスト

select クエリ パラメーターを編集します。
※以下の例では、メンバーのみ抽出する条件も含んでいます。

[参考：クエリ パラメーターを使用して応答をカスタマイズする > select パラメーター](https://docs.microsoft.com/ja-jp/graph/query-parameters?WT.mc_id=AZ-MVP-5003113#select-parameter)

```powershell:GetLastSignIn.ps1&#58;50行目&nbsp;(変更前)
$reqUrl = "$resource/beta/users/?`$select=userPrincipalName,signInActivity"
```

```powershell:GetLastSignIn.ps1&#58;50行目&nbsp;(変更後)
$reqUrl = "$resource/beta/users/?`$filter=userType eq 'member'&`$select=userPrincipalName,displayName,signInActivity"
```


#### タイトル行

[GetLastSignIn.ps1](https://github.com/jpazureid/get-last-signin-reports/blob/use-signin-activity-beta-api/GetLastSignIn.ps1) の (61 行目からの) 「# Create title for the out put file」の部分を以下のように変更します。

```powershell:GetLastSignIn.ps1&nbsp;(#&nbsp;Create&nbsp;title&nbsp;for&nbsp;the&nbsp;out&nbsp;put&nbsp;file&nbsp;部分(変更前))
#
# Create title for the out put file
#
$data += "UserPrincipalName,Last sign-in event date in UTC,Cloud Application"
```

```powershell:GetLastSignIn.ps1&nbsp;(#&nbsp;Create&nbsp;title&nbsp;for&nbsp;the&nbsp;out&nbsp;put&nbsp;file&nbsp;部分(変更後))
#
# Create title for the out put file
#
$data += "UserPrincipalName,DisplayName,Last sign-in event date in UTC,Cloud Application"
```


#### プロパティ値の取得

[GetLastSignIn.ps1](https://github.com/jpazureid/get-last-signin-reports/blob/use-signin-activity-beta-api/GetLastSignIn.ps1) の (70 行目からの) 「# Get user and event information」の部分を以下のように変更します。

```powershell:GetLastSignIn.ps1&nbsp;(#&nbsp;Get&nbsp;user&nbsp;and&nbsp;event&nbsp;information&nbsp;部分(変更前))
#
# Get user and event information
#
$userUPN = $signInActivityJsonValue.userPrincipalName[$j]
$allSignInAct = $signInActivityJsonValue.signInActivity[$j]
$lastRequestID = $allSignInAct.lastSignInRequestId
$lastSignin = $allSignInAct.lastSignInDateTime
```

```powershell:GetLastSignIn.ps1&nbsp;(#&nbsp;Get&nbsp;user&nbsp;and&nbsp;event&nbsp;information&nbsp;部分(変更後))
#
# Get user and event information
#
$userUPN = $signInActivityJsonValue.userPrincipalName[$j]
$displayName = $signInActivityJsonValue.displayName[$j]    # <---追加
$allSignInAct = $signInActivityJsonValue.signInActivity[$j]
$lastRequestID = $allSignInAct.lastSignInRequestId
$lastSignin = $allSignInAct.lastSignInDateTime
```


#### データ行 (プロパティ値)

[GetLastSignIn.ps1](https://github.com/jpazureid/get-last-signin-reports/blob/use-signin-activity-beta-api/GetLastSignIn.ps1) の 98, 110, 119 行目を以下のように変更します。

```powershell:GetLastSignIn.ps1&nbsp;(変更前)
$data += $userUPN + "," + $lastSignin + "," + $appDisplayName
```

```powershell:GetLastSignIn.ps1&nbsp;(変更後)
$data += $userUPN + "," + $displayName + "," + $lastSignin + "," + $appDisplayName
```

### カスタマイズした実行結果を確認

出力された CSV ファイルの中身は以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/0cff0040-a1fa-696b-5507-6d1805dcb679.png)



最後まで読んでいただきありがとうございました。
この記事で紹介した内容がみなさんの業務や知識の向上に少しでもお役に立てればと思います。