PowerShellを使ってリモートコンピュータへの接続履歴を確認する方法

remote computerのRDP connection historyは、一つの承認済みFQDNへ限定してSecurity 4624/4625を照会し、XMLのnamed EventDataからLogonType 10だけを抽出します。User、IP、success/failure、RecordIdを出し、access/connect errorを0件と扱いません。1149とsession eventは別の補助表です。

日程Fit。無料・登録不要。「いつ空いてる?」を、ひとつのリンクで。リンクを送って、○△×でかんたん日程調整。無料で日程を作る。
目次

一つのremote FQDNを承認対象にする

短縮名、wildcard、複数host配列は使いません。DNS解決できるexact FQDNとquery IDsをtokenへ含め、credentialは対話入力だけで取得してstateやfileへ保存しません。

$ErrorActionPreference='Stop'
$remoteFqdn='rdsh01.corp.example.com'
if([uri]::CheckHostName($remoteFqdn) -ne [UriHostNameType]::Dns -or $remoteFqdn -notmatch '\.'){throw 'one exact remote FQDN is required'}
$resolved=@(Resolve-DnsName -Name $remoteFqdn -Type A_AAAA -ErrorAction Stop)
if($resolved.Count -eq 0){throw 'approved remote FQDN did not resolve'}
$token="QUERY-RDP-HISTORY REMOTE=$remoteFqdn IDS=4624,4625 LOGONTYPE=10"
if((Read-Host "対象と監査承認を確認し、照会する場合は $token") -ne $token){throw 'remote query not approved'}
$credential=Get-Credential -Message "read Windows event logs on $remoteFqdn"
$start=(Get-Date).AddDays(-7)

4624/4625のnamed fieldsを解析する

remote Security queryはErrorAction Stopで失敗を伝播します。Message textではなくEventData Nameをhashtable化し、LogonType=10だけをUser、Domain、IpAddress、status、RecordIdへ変換します。

try{
  $securityEvents=@(Get-WinEvent -ComputerName $remoteFqdn -Credential $credential -FilterHashtable @{LogName='Security';Id=4624,4625;StartTime=$start} -ErrorAction Stop)
}catch{throw "remote Security query failed; not treated as no-event: $($_.Exception.Message)"}
function Get-NamedEventData([Diagnostics.Eventing.Reader.EventRecord]$Event){
  [xml]$xml=$Event.ToXml();$fields=@{}
  foreach($node in @($xml.Event.EventData.Data)){$fields[[string]$node.Name]=[string]$node.'#text'}
  $fields
}
$rdp=@($securityEvents|ForEach-Object {
  $d=Get-NamedEventData $_
  if($d['LogonType'] -eq '10'){
    [pscustomobject]@{RemoteFqdn=$remoteFqdn;TimeCreated=$_.TimeCreated;RecordId=[long]$_.RecordId;EventId=[int]$_.Id;Status=$(if($_.Id -eq 4624){'Success'}else{'Failure'});Domain=[string]$d['TargetDomainName'];User=[string]$d['TargetUserName'];IpAddress=[string]$d['IpAddress'];IpPort=[string]$d['IpPort'];LogonType=[int]$d['LogonType'];FailureStatus=[string]$d['Status'];SubStatus=[string]$d['SubStatus']}
  }
}|Sort-Object TimeCreated,RecordId)
if($rdp.Count -eq 0){[pscustomobject]@{Status='NoRemoteInteractiveSecurityEvents';RemoteFqdn=$remoteFqdn;Start=$start}}else{$rdp}

1149 authenticationを別queryにする

RemoteConnectionManager 1149はauthentication evidenceです。4624 successや4625 failureと意味が異なるため、Param fieldsを別rowにし、件数をSecurity結果へ足しません。

# 1149 authentication evidence is queried separately from Security success/failure.
$rmChannel='Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational'
try{$auth1149=@(Get-WinEvent -ComputerName $remoteFqdn -Credential $credential -FilterHashtable @{LogName=$rmChannel;Id=1149;StartTime=$start} -ErrorAction Stop)}catch{throw "remote 1149 query failed: $($_.Exception.Message)"}
$authRows=@($auth1149|ForEach-Object {
  $d=Get-NamedEventData $_
  [pscustomobject]@{RemoteFqdn=$remoteFqdn;Evidence='1149';TimeCreated=$_.TimeCreated;RecordId=[long]$_.RecordId;User=[string]$d['Param1'];Domain=[string]$d['Param2'];IpAddress=[string]$d['Param3']}
}|Sort-Object TimeCreated,RecordId)
$authRows

session lifecycleを別queryにする

LocalSessionManager 21/23/24/25はSessionIDを含むlifecycle evidenceです。明示したuser/IP/time windowでcorroborateできますが、Security statusを書き換えたり、単純な時刻近接だけで同一connectionと断定しません。

# LocalSessionManager is session corroboration, not a replacement for 4624/4625 status.
$lsmChannel='Microsoft-Windows-TerminalServices-LocalSessionManager/Operational'
try{$sessionEvents=@(Get-WinEvent -ComputerName $remoteFqdn -Credential $credential -FilterHashtable @{LogName=$lsmChannel;Id=21,23,24,25;StartTime=$start} -ErrorAction Stop)}catch{throw "remote session query failed: $($_.Exception.Message)"}
$sessionRows=@($sessionEvents|ForEach-Object {
  $d=Get-NamedEventData $_
  [pscustomobject]@{RemoteFqdn=$remoteFqdn;Evidence='SessionLifecycle';TimeCreated=$_.TimeCreated;RecordId=[long]$_.RecordId;EventId=[int]$_.Id;User=[string]$d['User'];SessionId=[string]$d['SessionID'];IpAddress=[string]$d['Address']}
}|Sort-Object TimeCreated,RecordId)
[pscustomobject]@{RemoteFqdn=$remoteFqdn;SecurityLogonType10Rows=$rdp.Count;Authentication1149Rows=$authRows.Count;SessionLifecycleRows=$sessionRows.Count;JoinPolicy='display separately; correlate by explicit user/IP/time window only'}

0件・拒否・接続不能を区別する

query成功かつLogonType 10が0件の場合だけNoRemoteInteractiveSecurityEventsです。RPC/firewall、credential、event log ACL、channel disable、network failureはerrorとして修復後に再照会します。

受入条件

fixtureでは4624/4625のnamed user/IP/status/RecordIdが再現され、LogonType 3などは除外されます。別hostのeventを混ぜず、1149とsession rowは独立countのまま提示されます。

公式情報・参考資料

この記事を書いた人

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

コメント

コメントする

目次