退職した人のアカウントが、まだ残っているかもしれない。監査や情報システムの棚卸しで「使われていないアカウントを一覧にしてください」と言われて、Entra ID(旧Azure AD)のユーザーの最終サインイン日時を調べ始める。ここまでは誰でも同じです。

困るのはその先です。日時が空欄のユーザーがいる。最後に使ったアプリの名前が出るユーザーと出ないユーザーがいる。半年前に辞めたはずの人が、先週サインインしたことになっている。どれを信じて、どこで線を引けばいいのかが分からなくなります。

この記事は、メンバー限定動画「PowerShellでEntra IDの最終サインインレポートを作成」を中心に、無料動画「M365アプリ登録入門|なぜアプリにもIDが必要なのか」と「証明書基礎:デジタル証明書についてザックリ解説」、メンバー限定の「第3回:サービスプリンシパルで完全自動化」を素材に、最終サインイン日時を棚卸しに使うときの判断をまとめたものです。日時の種類と保存期間は、2026年10月30日時点のMicrosoft LearnとMicrosoft Graphのリファレンスで確認しました。先に結論を書きます。

  1. 「最終サインイン日時」は1つではない。 ユーザーには、対話型の最終サインイン、非対話型の最終サインイン、最後に成功したサインインの3つの日時が記録されている
  2. 使われていないかどうかは「最後に成功したサインイン」で見る。 対話型の最終サインインは失敗した試行でも更新される。パスワードを当てようとする攻撃を受けているアカウントほど、使われているように見えてしまう
  3. 空欄は「使われていない」とは限らない。 一度もサインインしていないか、記録が始まる前に最後にサインインしたかのどちらか。最後に成功したサインインは、2023年12月より前の分が入っていない
  4. 最終サインイン日時はユーザーが存在する限り残るが、サインインログは最大30日で消える。 日時は出るのにアプリ名が出ないのは、このずれのせい
  5. 一覧で取り出すにはMicrosoft Entra ID P1以上のライセンスが要る。 権限は User.Read.All と AuditLog.Read.All

「最終サインイン日時」は3種類ある

Microsoft Graphでユーザーを取得すると、signInActivity というプロパティに日時が入っています。公式リファレンスの「signInActivity リソースの種類」によると、中身は次の3つです。

プロパティ 何の日時か 失敗した試行で更新されるか
lastSignInDateTime 最後の対話型サインイン。人がパスワードや多要素認証を入力したもの される
lastNonInteractiveSignInDateTime 最後の非対話型サインイン。アプリがユーザーの代わりに裏でトークンを更新したものなど される
lastSuccessfulSignInDateTime 対話型・非対話型を問わず、最後に成功したサインイン されない

古いスクリプトや解説記事で「最終サインイン」と呼ばれているのは、たいてい1つ目の lastSignInDateTime です。リファレンスはこのプロパティについて、失敗した試行も記録されるので実際の利用状況を正確に反映していない可能性がある、アカウントが実際に使われたかを追うなら lastSuccessfulSignInDateTime を使うように、と注記しています。

冒頭の「辞めたはずの人が先週サインインしている」は、これで説明がつきます。外からパスワードを当てようとする試行がそのアカウントに来ていれば、失敗していても対話型の最終サインイン日時は新しくなります。放置されて狙われているアカウントほど、使われているアカウントに見える。 棚卸しで真っ先に見つけたいものが、いちばん見つけにくくなる並び方です。

2つ目の非対話型も、単独では判断を誤らせます。公式の「Microsoft Entra での非対話型ユーザー サインインとは」にあるとおり、これはクライアントアプリやOSの部品が、ユーザーに代わって裏でトークンを更新したときの記録です。本人がしばらく触っていなくても、どこかの端末にサインインしたままのアプリが残っていれば、日時は進みます。

動画では15分9秒から(メンバー限定)、出力したCSVの非対話型の列に「Microsoft Edgeのエンタープライズ新しいタブページ」が記録されているのを見つけて、「タブを開いたときに勝手にサインインしている」と読み解いています。人が何もしていなくても日時が動く、という感覚はこの画面で一度見ておくと掴みやすいです。

空欄になるのは、使われていないからとは限らない

日時が空欄のユーザーをそのまま「未使用」に数えると、判断を間違えます。公式の「非アクティブなユーザー アカウントを検出して調査する方法」は、対話型の最終サインイン日時が空になるのは次の場合だと書いています。

  • 最後のサインインが2020年4月より前だった
  • そのアカウントが、一度もサインインに使われていない

さらに、lastSuccessfulSignInDateTime は2023年12月1日から記録が始まったプロパティで、それ以前の分はさかのぼって埋められていません(前述のリファレンス)。2023年11月に最後にサインインしたきりのユーザーは、対話型の日時は入っているのに、成功の日時は空、という形になります。

もう1つ、記録は即時ではありません。同じページによると、最終サインインの日付は更新に最大24時間かかります。「昨日サインインしたはずなのに古い日付のまま」は、壊れているのではなく反映待ちのことがあります。

空欄は「古すぎる」「一度も使っていない」「新しい項目なので過去の分が無い」の3つを区別してから扱ってください。作成日(createdDateTime)と並べれば、作ったばかりでまだ一度も使っていないアカウントは見分けられます。

日時は出るのに、アプリ名が出ない理由

動画で使っているのは、マイクロソフトの日本のID関連サポートチームがGitHubで公開しているサンプル「get-last-signin-reports」です。ユーザーごとの最終サインイン日時に加えて、そのとき使ったアプリの名前もCSVに出してくれます。

動画の14分43秒から(メンバー限定)で結果のCSVを開くと、アプリ名が空のユーザーがかなりあります。動画ではこれを「取れなかったんでしょうね」と流していますが、理由はスクリプトの作りと保存期間の違いで説明できます。

動画の17分11秒から(メンバー限定)でスクリプトの中身を読んでいるとおり、処理は2段階です。

  1. ユーザー一覧を取り、signInActivity から最終サインイン日時と、そのサインインの要求IDを取り出す
  2. その要求IDでサインインログを検索し、ログに書かれているアプリ名を取ってくる

1つ目の日時はユーザーのオブジェクトに付いた値で、リファレンスにあるとおりユーザーが存在する限り保持されます。一方、2つ目のサインインログはずっとは残りません。公式の「Microsoft Entra のデータ保持」によると、サインインログの保持期間は無料版で7日、P1・P2で30日です。

つまり、最後のサインインが30日より前のユーザーは、日時は出てもアプリ名は出ません。 ログのほうがもう消えているからです。そしてそれこそが、棚卸しで探している「しばらく使われていない」ユーザーです。アプリ名が空の行を不具合として扱わず、むしろ候補の目印として読むのが正しい読み方になります。30日より前の利用状況まで追いたいなら、サインインログをLog Analyticsやストレージアカウントへあらかじめ転送しておくしかありません。消えた後からは取り戻せません。

画面で見るか、PowerShellで一覧にするか

管理センターで見る

数十人程度なら、Entra 管理センターのユーザー一覧で足ります。前述の「非アクティブなユーザー アカウントを検出して調査する方法」の手順では、[ユーザー]の[すべてのユーザー]で[ビューの管理]→[列の編集]から[前回の対話型サインイン時間]の列を足し、フィルターで日付を指定すると、その日より前にサインインしたユーザーに絞り込めます。

ただし、この列は対話型の日時です。失敗した試行でも動く値なので、ここで「最近サインインしている」に見えたユーザーを、それだけで使われていると判断しないでください。

PowerShellで一覧にする

数百人を超えるなら、Microsoft Graph PowerShellで3つの日時をまとめて取り出すほうが早いです。最小の形はこうなります。

Connect-MgGraph -Scopes "User.Read.All", "AuditLog.Read.All"

Get-MgUser -All -Property Id, DisplayName, UserPrincipalName, AccountEnabled, UserType, CreatedDateTime, SignInActivity |
    Select-Object DisplayName, UserPrincipalName, AccountEnabled, UserType, CreatedDateTime,
        @{ n = 'LastSuccessful';     e = { $_.SignInActivity.LastSuccessfulSignInDateTime } },
        @{ n = 'LastInteractive';    e = { $_.SignInActivity.LastSignInDateTime } },
        @{ n = 'LastNonInteractive'; e = { $_.SignInActivity.LastNonInteractiveSignInDateTime } } |
    Export-Csv -Path .\last-sign-in.csv -NoTypeInformation -Encoding utf8

押さえておくことは3つです。

  • SignInActivity は指定しないと返ってきません。公式の「ユーザーを一覧表示する」にあるとおり、$select か $filter で明示したときだけ取れます。上の -Property がそれにあたります
  • 同じページによると、signInActivity の取得にはMicrosoft Entra ID P1またはP2のライセンスと AuditLog.Read.All が必要です。無料版のテナントでは、この一覧は作れません
  • 日時はすべてUTCです。日本時間の日付で線を引くなら、9時間ずらして考えます

signInActivity はサーバー側で日付の絞り込み(le など)もできますが、同じページの注記のとおり、accountEnabled などほかの絞り込み条件と組み合わせることはできません。無効化済みのアカウントやゲストを除く処理は、取り出した後に手元でやるほうが素直です。

PowerShellのスクリプトそのものが動かないときは、「このシステムではスクリプトの実行が無効になっているため」と出たときの記事を先に見てください。サンプルのREADMEは実行ポリシーを Bypass に変える手順から始まりますが、常用する設定ではありません。

定期的に回すなら、アプリとして動かす

上のコマンドは、管理者が自分でサインインして手で回す形です。毎月の棚卸しを人がいなくても回したいなら、動画のサンプルと同じく、アプリとしてサインインする形にします。

ここで迷うのが、Entra IDにアプリを登録するときの権限の種類です。無料動画「M365アプリ登録入門」の30分32秒からが、「ユーザーの代わりに動くのか、アプリ自身の権限で動くのか」という分かれ道を説明しています。ユーザー委任なら、そのユーザーが見られる範囲までしか届きません。人がいないところで全ユーザーを読むなら、アプリケーションの許可を選び、管理者の同意を与える必要があります。動画では9分53秒から(メンバー限定)、サンプルの手順どおり、アプリケーションの許可で Directory.Read.All と AuditLog.Read.All を付け、管理者の同意を与えるところまでを画面で見せています。ユーザーを読むだけなら、前述の公式ページが挙げている User.Read.All のほうが範囲は狭く済みます。

アプリの認証には、パスワードにあたるクライアントシークレットではなく証明書を使います。メンバー限定の「第3回:サービスプリンシパルで完全自動化」の22分14秒からで、シークレットを本番で使うのは「ほぼ禁止に近い」と考えたほうがいい理由と、証明書ならWindowsの中に安全にしまっておけることを説明しています。証明書を作ってアプリに登録する流れは、最終サインインの動画の11分31秒から(メンバー限定)で通しで見られます。

自分で作った証明書なのに、なぜそれで本人確認ができるのか。アップロードするのは公開鍵だけで、秘密鍵は手元から出さないからです。無料動画「証明書基礎」の7分35秒からが、公開鍵は誰に見られても構わず、秘密鍵は絶対に外に出さない、という役割分担を説明しています。文章で順を追って確かめたいなら、証明書の信頼の仕組みをまとめた記事も合わせて読んでください。

サンプルを動かすときにつまずく所

動画で実際に引っかかった所と、サンプルの作りから分かる注意点です。

つまずく所 何が起きるか どうするか
モジュールの入れ直し 古いMicrosoft Graphのモジュールが入っていると、-Force を付けても「コマンドがすでに存在する」と止まる(動画の7分1秒から、メンバー限定) 動画では -AllowClobber で入れている。ほかのスクリプトが古い版に依存していないか先に確かめる
Windows PowerShellとPowerShell 7 モジュールの置き場所が別なので、片方に入れても、もう片方では使えないことがある(動画の2分12秒から、メンバー限定) 実行するほうのPowerShellで入れる
クライアントIDとオブジェクトID アプリの画面にIDが複数並んでいて取り違える 指定するのはアプリケーション(クライアント)ID。オブジェクトIDは別物(動画の13分38秒から、メンバー限定)
時間がかかる ユーザー1人ずつサインインログを引くので、人数が多いと長い アプリ名が要らないなら、上の Get-MgUser だけで済ませる
日時がUTC CSVの日付が日本時間とずれる サンプルは -TimeZone "Tokyo Standard Time" で日本時間の列を足せる(動画の15分55秒から、メンバー限定)
出力される日時の種類 サンプルが出すのは対話型と非対話型の日時で、最後に成功したサインインは出ない 棚卸しの判断に使うなら、LastSuccessfulSignInDateTime を自分で列に足す

最後の行は見落としやすいところです。このサンプルの最終更新は2023年9月で、lastSuccessfulSignInDateTime が使えるようになる2023年12月より前です。CSVの LastSignInDateUTC は失敗した試行でも更新される値なので、そのまま「最後に使われた日」として報告しないでください。

うまく動かないときの切り分けは、動画の19分17秒から(メンバー限定)が参考になります。スクリプトを動かす前に、Connect-MgGraph で証明書を使ってアプリとしてサインインし、Get-MgUser と Get-MgAuditLogSignIn が手で動くかを1つずつ確かめる。どちらかが失敗するなら、スクリプトではなく権限か認証の問題です。

一覧が出た後に、何で線を引くか

前述の公式ページは、使われていないと見なす期間を「多くの組織では90日から180日」としています。ただ、日数で機械的に消すと事故が起きます。一覧を見ながら、次の順に確かめてください。

  1. 最後に成功したサインインで並べる。 対話型の日時だけで並べない
  2. 空欄を3つに分ける。 作成直後でまだ使っていない/2023年12月より前から使っていない/一度も使っていない
  3. 普段使わないことが正しいアカウントを外す。 締め出しに備えた緊急アクセス用のアカウントは、何か月もサインインしないのが正常です。条件付きアクセスの設計の記事で書いたとおり、このアカウントはすべてのポリシーから除外して別に守っているはずで、棚卸しの一覧からは理由を書いて除外する
  4. まだ在籍しているか、まだそのアクセスが要るかを人に確かめる。 休職中や長期の出張は、日時だけでは分からない
  5. いきなり削除せず、まずサインインを止める。 無効化しておけば、必要だったと分かったときに戻せる

ゲストのアカウントは別の手順が用意されています。公式の「古いゲスト アカウントを監視およびクリーンアップする」を参照してください。この確認を定期的に回したいなら、Entra ID ガバナンスの「アクセス レビュー」で、使われていないユーザーを対象にしたレビューを組めます。こちらはMicrosoft Entra ID Governanceのライセンスが要ります。

オンプレミスのActive Directoryから同期しているユーザーは、Entra ID側では消せません。消すならAD側で消すことになります。その前に、ADで削除したユーザーを戻す方法の記事で、ごみ箱が有効になっているかを確かめておいてください。間違って消したときに戻せるかどうかが、そこで決まります。

棚卸しのチェックリスト

確かめること 見るもの
P1以上のライセンスがあるか 無ければ signInActivity の一覧は取れない
取り出す権限 User.Read.All と AuditLog.Read.All。画面で見るならレポート閲覧者以上
判断に使う日時 lastSuccessfulSignInDateTime。対話型の日時だけで判断しない
空欄の意味 作成日と並べて、作成直後・記録開始前・未使用を分ける
アプリ名が空の行 最後のサインインが30日より前の目印。不具合ではない
日時の時差 UTC。日本時間なら9時間足す
除外するアカウント 緊急アクセス用など、使わないのが正常なもの
消す前に 本人・上長への確認、まず無効化、同期ユーザーはAD側で

まとめ

  • ユーザーの最終サインイン日時は3つある。対話型、非対話型、最後に成功したサインイン
  • 対話型の日時は失敗した試行でも更新される。狙われている放置アカウントほど、使われているように見える。棚卸しは最後に成功したサインインで見る
  • 空欄は、記録が始まる前に最後にサインインしたか、一度も使っていないか。最後に成功したサインインは2023年12月より前の分が無い
  • 最終サインイン日時はユーザーがいる限り残るが、サインインログは最大30日。日時が出てアプリ名が出ない行は、30日以上使われていない目印
  • 一覧で取り出すにはP1以上と AuditLog.Read.All が要る。定期的に回すなら、アプリの許可と証明書でアプリとして動かす
  • 日数で機械的に消さない。使わないのが正常なアカウントを外し、人に確かめ、まず無効化する

最終サインイン日時は、出してしまえば簡単に見える数字です。けれど、どの日時を見ているかで結論が逆になります。手元の一覧の「最終サインイン」の列が、3つのうちどれなのか。 それを確かめるところから始めてください。