PowerShellを使ってリモートデスクトップのログを確認する方法

Remote Desktopの接続履歴は、LocalSessionManager Operational logのEvent ID 21・23・24・25をXML EventDataから読み、UserとSessionIDが完全一致する行だけを時刻とRecordId順に並べます。アクセス拒否、log無効、真の0件を区別し、1149や4624は認証の補助証拠として別表示します。

目次

LocalSessionManager logの利用可否を確定する

Get-WinEvent -ListLogでchannel accessとIsEnabledを確認します。query failureを0件へ変換しません。成功して配列が空の場合だけNoSessionLifecycleEventsです。

$ErrorActionPreference='Stop'
$channel='Microsoft-Windows-TerminalServices-LocalSessionManager/Operational'
try{$log=Get-WinEvent -ListLog $channel -ErrorAction Stop}catch{throw "cannot access LocalSessionManager log: $($_.Exception.Message)"}
if(-not $log.IsEnabled){throw "log is disabled: $channel"}
$start=(Get-Date).AddDays(-7)
try{$events=@(Get-WinEvent -FilterHashtable @{LogName=$channel;Id=21,23,24,25;StartTime=$start} -ErrorAction Stop)}catch{throw "LocalSessionManager query failed; not treated as no-event: $($_.Exception.Message)"}
if($events.Count -eq 0){[pscustomobject]@{Status='NoSessionLifecycleEvents';Channel=$channel;Start=$start};return}
$events.Count

named EventDataからUserとSessionIDを読む

Message文字列の言語依存splitは使いません。各EventRecordをToXmlし、EventData/DataのNameをkeyにしてUser、SessionID、Addressを取得します。TimeCreatedが同じ場合はRecordIdで安定整列します。

function Convert-EventData([Diagnostics.Eventing.Reader.EventRecord]$Event){
  [xml]$xml=$Event.ToXml();$fields=@{}
  foreach($node in @($xml.Event.EventData.Data)){$fields[[string]$node.Name]=[string]$node.'#text'}
  [pscustomobject]@{EventId=[int]$Event.Id;TimeCreated=$Event.TimeCreated;RecordId=[long]$Event.RecordId;User=[string]$fields['User'];SessionId=[string]$fields['SessionID'];Address=[string]$fields['Address']}
}
$timeline=@($events|ForEach-Object {Convert-EventData $_}|Where-Object {$_.User -and $_.SessionId}|Sort-Object TimeCreated,RecordId)
if($timeline.Count -eq 0){throw 'events existed but exact User/SessionID EventData could not be parsed'}
$timeline|Select-Object TimeCreated,RecordId,EventId,User,SessionId,Address

exact sessionの21・23・24・25を時系列化する

確認対象のSessionIDとUserを完全一致でfilterします。21はlogon、23はlogoff、24はdisconnect、25はreconnectとして表示し、別sessionの行を混在させません。

$sessionId='PASTE_EXACT_SESSION_ID'
$user='PASTE_EXACT_USER'
$oneSession=@($timeline|Where-Object {$_.SessionId -ceq $sessionId -and $_.User -ceq $user}|Sort-Object TimeCreated,RecordId)
if($oneSession.Count -eq 0){[pscustomobject]@{Status='NoExactSession';User=$user;SessionId=$sessionId};return}
$meaning=@{21='Logon';23='Logoff';24='Disconnected';25='Reconnected'}
$oneSession|ForEach-Object {[pscustomobject]@{TimeCreated=$_.TimeCreated;RecordId=$_.RecordId;EventId=$_.EventId;Meaning=$meaning[[int]$_.EventId];User=$_.User;SessionId=$_.SessionId;Address=$_.Address}}

1149とSecurity 4624を別証拠として扱う

RemoteConnectionManager 1149とSecurity 4624 LogonType 10は認証・network logonの証拠です。session lifecycleのSessionIDを直接表さないため、件数とnamed fieldsを別表にし、21/23/24/25の代用にしません。

# 1149と4624は認証証拠であり、21/23/24/25のsession lifecycleとは別に照合する。
$rmChannel='Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational'
$security='Security'
try{$rm=@(Get-WinEvent -FilterHashtable @{LogName=$rmChannel;Id=1149;StartTime=$start} -ErrorAction Stop)}catch{throw "1149 query failed: $($_.Exception.Message)"}
try{$logons=@(Get-WinEvent -FilterHashtable @{LogName=$security;Id=4624;StartTime=$start} -ErrorAction Stop)}catch{throw "Security 4624 query failed: $($_.Exception.Message)"}
$rdpLogons=@($logons|ForEach-Object {
  [xml]$x=$_.ToXml();$d=@{};foreach($n in @($x.Event.EventData.Data)){$d[[string]$n.Name]=[string]$n.'#text'}
  if($d['LogonType'] -eq '10'){[pscustomobject]@{TimeCreated=$_.TimeCreated;RecordId=[long]$_.RecordId;User=$d['TargetUserName'];Domain=$d['TargetDomainName'];IpAddress=$d['IpAddress'];LogonType=10}}
}|Sort-Object TimeCreated,RecordId)
[pscustomobject]@{SessionLifecycleRows=$timeline.Count;Authentication1149Rows=$rm.Count;SecurityLogonType10Rows=$rdpLogons.Count;Correlation='corroboration only; do not replace exact SessionID timeline'}

結果の読み方

disconnect後にreconnectがある、logon後にlogoffがない、といった状態を同じSessionID内で判断します。event retention外、audit policy、clock差、channel forwardingの有無も併記し、missing eventを未発生と断定しません。

受入条件

disabled logは明示error、access denied/query failureもerror、正常0件だけNoSessionLifecycleEventsです。fixtureでは同一User/SessionIDの21→24→25→23がTimeCreated/RecordId順に再現され、他sessionは除外されます。

公式情報・参考資料

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次