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.