Windows Remote-Access Incident Triage – Live Collection Script & Workflow
Live Collection Script & Workflow
This article presents a complete, field-ready triage workflow for Windows machines suspected of compromise via remote-access tools.
Typical cases include:
- A user was “helped remotely” via AnyDesk, TeamViewer, RustDesk, etc.
- Money disappeared from online banking or crypto wallets.
- The user observed someone controlling the computer.
The goal is to run a portable triage script from an external drive to:
- collect system, network, user, and browser artefacts,
- detect remote-access tools,
- export logs relevant to remote sessions,
- preserve evidence for a later deep forensic analysis.
This is not a replacement for full imaging — it is a practical, repeatable workflow for first responders.
1. Design Goals
- Runs from an external USB drive
- No installation or system changes
- Collects only evidence — does not clean or modify
- Focused on remote-access & banking abuse
- Produces a structured evidence directory for lab analysis
2. The Complete PowerShell Triage Script
Save as: remote_access_triage.ps1
param([string]$CaseId = "CASE")
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$rootPath = $PSScriptRoot
$baseDir = Join-Path $rootPath ("Triage_" + $CaseId + "_" + $timestamp)
$dirs = @(
"system","network","logs","users","browser","registry","remote_tools"
) | ForEach-Object { Join-Path $baseDir $_ }
New-Item -ItemType Directory -Force -Path $dirs | Out-Null
function Save-Command {
param($CommandLine,$OutFile)
cmd.exe /c "$CommandLine" | Out-File -FilePath $OutFile -Encoding UTF8 -Force
}
# System info
Save-Command "systeminfo" (Join-Path $baseDir "system/systeminfo.txt")
Save-Command "wmic os get Caption,Version" (Join-Path $baseDir "system/os_version.txt")
Save-Command "wmic computersystem get Manufacturer,Model" (Join-Path $baseDir "system/hardware.txt")
# Users
try { Get-LocalUser | Out-File (Join-Path $baseDir "users/local_users.txt") } catch {}
Save-Command "query user" (Join-Path $baseDir "users/sessions_query_user.txt")
Save-Command "qwinsta" (Join-Path $baseDir "users/sessions_qwinsta.txt")
# Installed software
$regPaths = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"
)
$apps = foreach ($p in $regPaths) {
try { Get-ItemProperty $p -ErrorAction SilentlyContinue | Where-Object DisplayName } catch {}
}
$apps | Sort-Object DisplayName |
Export-Csv (Join-Path $baseDir "system/installed_software.csv") -NoTypeInformation
# Remote tool hits
$remoteNames = @(
"AnyDesk","TeamViewer","RustDesk","UltraViewer","DWService",
"Chrome Remote Desktop","SupRemo","VNC","Radmin","Zoho","LogMeIn"
)
$apps | Where-Object {
$n = $_.DisplayName
$remoteNames | Where-Object { $n -like "*$_*" }
} | Export-Csv (Join-Path $baseDir "remote_tools/remote_tools_installed.csv") -NoTypeInformation
# Scan Program Files
foreach ($pf in @("C:\Program Files","C:\Program Files (x86)")) {
if (Test-Path $pf) {
Get-ChildItem $pf -Directory | ForEach-Object {
if ($remoteNames | Where-Object { $_.Name -like "*$_*" }) {
$_.FullName | Out-File -Append (Join-Path $baseDir "remote_tools/programfiles_hits.txt")
}
}
}
}
# Processes / services / tasks
try { Get-Process | Out-File (Join-Path $baseDir "system/processes.txt") } catch {}
try { Get-Service | Out-File (Join-Path $baseDir "system/services.txt") } catch {}
Save-Command "schtasks /query /fo LIST /v" (Join-Path $baseDir "system/scheduled_tasks.txt")
# Network
Save-Command "ipconfig /all" (Join-Path $baseDir "network/ipconfig_all.txt")
Save-Command "route print" (Join-Path $baseDir "network/route_print.txt")
Save-Command "arp -a" (Join-Path $baseDir "network/arp_a.txt")
Save-Command "netstat -ano" (Join-Path $baseDir "network/netstat_ano.txt")
Save-Command "netsh advfirewall show allprofiles" (Join-Path $baseDir "network/firewall_profiles.txt")
try {
Get-NetTCPConnection | Where-Object State -eq "Established" |
Out-File (Join-Path $baseDir "network/Get-NetTCPConnection_established.txt")
} catch {}
# Event logs
$evList = @(
"Security","System","Application",
"Microsoft-Windows-TerminalServices-LocalSessionManager/Operational",
"Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational",
"Microsoft-Windows-RemoteDesktopServices-RdpCoreTS/Operational"
)
$evBase = Join-Path $baseDir "logs/eventlogs"
New-Item -ItemType Directory -Force -Path $evBase | Out-Null
foreach ($log in $evList) {
$safe = ($log -replace '[\\/]', '_')
$out = Join-Path $evBase "$safe.evtx"
try { wevtutil epl "$log" "$out" /ow:true } catch {}
}
# Browser artefacts
function CopyIfExists { param($s,$d) if (Test-Path $s) { Copy-Item $s $d -Force } }
$chrome = "$env:LOCALAPPDATA\Google\Chrome\User Data\Default"
if (Test-Path $chrome) {
$d = Join-Path $baseDir "browser/Chrome_Default"
New-Item -ItemType Directory -Force -Path $d | Out-Null
"History","Login Data","Cookies","Web Data","Preferences" |
ForEach-Object { CopyIfExists (Join-Path $chrome $_) $d }
}
$edge = "$env:LOCALAPPDATA\Microsoft\Edge\User Data\Default"
if (Test-Path $edge) {
$d = Join-Path $baseDir "browser/Edge_Default"
New-Item -ItemType Directory -Force -Path $d | Out-Null
"History","Login Data","Cookies","Web Data","Preferences" |
ForEach-Object { CopyIfExists (Join-Path $edge $_) $d }
}
$ff = "$env:APPDATA\Mozilla\Firefox\Profiles"
if (Test-Path $ff) {
Get-ChildItem $ff -Directory | ForEach-Object {
$d = Join-Path $baseDir ("browser/Firefox_" + $_.Name)
New-Item -ItemType Directory -Force -Path $d | Out-Null
"places.sqlite","logins.json","key4.db","cookies.sqlite" |
ForEach-Object { CopyIfExists (Join-Path $_.FullName $_) $d }
}
}
# Registry
$regBase = Join-Path $baseDir "registry"
foreach ($rk in @(
"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce",
"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce",
"HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server",
"HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations",
"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
)) {
$safe = ($rk -replace '[\\/:*?"<>|]', '_')
Save-Command "reg export `"$rk`" `"$regBase\$safe.reg`" /y" "$regBase\$safe.txt"
}
# Remote tools data
$remoteData = @(
"$env:PROGRAMDATA\AnyDesk",
"$env:PROGRAMDATA\TeamViewer",
"$env:PROGRAMDATA\RustDesk",
"$env:PROGRAMDATA\DWService",
"$env:PROGRAMDATA\UltraViewer"
)
foreach ($p in $remoteData) {
if (Test-Path $p) {
Copy-Item $p (Join-Path $baseDir "remote_tools") -Recurse -Force -ErrorAction SilentlyContinue
}
}
Write-Host "[*] Triage complete. Output stored in: $baseDir"
3. Field Usage (Short Version)
Boot suspect Windows machine without rebooting.
Plug in USB drive with the script.
Run PowerShell as Administrator:
Set-ExecutionPolicy Bypass -Scope Process -Force E: .\remote_access_triage.ps1 -CaseId “CASE_001”
4. Let it finish.
5. Take:
the Triage_CASE_001_YYYYMMDD_HHMMSS folder to the lab.