Skip to content

Kansa Hands on Labs

Interactive Lab

Browser based

Kansa Interactive Lab

No installation

Investigate an unusual process under C:\Users\Public on WIN11-LAB.

Windows PowerShell

Copyright (C) Microsoft Corporation. All rights reserved.

Full Lab

Self hosted / real collection

Kansa Full Lab

One Windows VM

Case KANSA-01 / before and after

One VM. Two collections. A finding you can explain.

A triage note mentions chrome_update.exe in C:\Users\Public. Collect a process baseline, start a harmless stand in on the same Windows VM, collect again, and show what changed. The filename is a lead, not a malware verdict.

Tick a checkpoint only after saving its evidence. Progress is self reported; this page cannot check your VM.

Investigation brief

Case questionDid the reported process appear after the controlled change?
Collection scope

One Windows VM. Kansa collects through localhost; you compare two saved process snapshots.

Real case note: This lab gives you a training baseline. In an incident, you may only have a live result to check against other evidence.

Activity 1: Prepare the VM

Your first winSave the VM identity, confirm the module, and get a response from localhost.
1. Create a new case folder on the Windows VM

Open Windows PowerShell as administrator on the VM. Keep this window open through Activity 2. These checks confirm the tool and shell before creating a new case folder.

$kansaCase = 'C:\Cases\Kansa-Local-01'
if ($PSVersionTable.PSVersion.ToString() -notlike '5.1.*') { throw 'Open Windows PowerShell 5.1 for this lab.' }
Set-Location 'C:\Tools\Kansa' -ErrorAction Stop
if (-not (Test-Path -LiteralPath '.\kansa.ps1')) { throw 'Kansa runner not found.' }
if (-not (Test-Path -LiteralPath '.\Modules\Process\Get-ProcsWMI.ps1')) { throw 'Process module not found.' }
if (Test-Path -LiteralPath $kansaCase) { throw 'Case folder already exists. Choose a new folder.' }
New-Item -ItemType Directory -Path $kansaCase -ErrorAction Stop | Out-Null
@(
    "Computer: $env:COMPUTERNAME"
    "Local time: $(Get-Date -Format o)"
    "Time zone: $((Get-TimeZone).Id)"
    "PowerShell: $($PSVersionTable.PSVersion)"
    'Target: localhost'
    'Module: Modules\Process\Get-ProcsWMI.ps1'
) | Set-Content -LiteralPath "$kansaCase\case-context.txt"
if (Test-Path -LiteralPath '.\.git') {
    git rev-parse HEAD | Set-Content -LiteralPath "$kansaCase\kansa-revision.txt"
}
Get-Content -LiteralPath "$kansaCase\case-context.txt"

Look for: case-context.txt with your VM's name and time. If Kansa came from a ZIP rather than Git, add the download URL, date, and archive name to that file. Check that C:\Users\Public\chrome_update.exe is absent; if it is already there, inspect it and use a fresh VM snapshot for this lab.

2. Check local PowerShell Remoting

Kansa reaches the same VM through WinRM. Run this test from the elevated PowerShell window:

Test-WSMan -ComputerName localhost
Invoke-Command -ComputerName localhost -Authentication Default -ErrorAction Stop -ScriptBlock {
    [pscustomobject]@{
        HostName = $env:COMPUTERNAME
        Time = Get-Date -Format o
    }
} | Export-Csv -LiteralPath "$kansaCase\localhost-check.csv" -NoTypeInformation
Import-Csv -LiteralPath "$kansaCase\localhost-check.csv" | Format-Table

Look for: a WinRM response and one row with this VM's name and time. If the test fails, follow the local setup in Remoting and modules: check the network profile, run Enable-PSRemoting -Force on this disposable VM when appropriate, then repeat the test. Record any network profile or WinRM change so you can reverse it later. No TrustedHosts entry is needed for localhost.

Ready to collectPause point: keep the case folder. In a new PowerShell window, set $kansaCase again and return to C:\Tools\Kansa.

Next: capture the VM's running processes.

Continue to collection

Activity 2: Collect before and after

Your next winSave two Kansa runs from the same VM, with one controlled change between them.
Make your call

Before running Kansa, predict what the two saved process files will say about C:\Users\Public\chrome_update.exe. What would a matching row prove, and what would it leave unanswered?

1. Collect the baseline before creating the training file

Confirm the training file is absent, then run only the Get-ProcsWMI collector. -Target localhost confines this run to the current VM. A baseline is your saved starting state; TSV is a text table with tabs between its columns.

$kansaSeed = 'C:\Users\Public\chrome_update.exe'
if (Test-Path -LiteralPath $kansaSeed) { throw 'Training path already exists. Stop and inspect it.' }
Set-Location 'C:\Tools\Kansa'
$kansaExistingRuns = @(Get-ChildItem -Path . -Directory -Filter 'Output_*' |
    Select-Object -ExpandProperty FullName)
.\kansa.ps1 -Target localhost -ModulePath '.\Modules\Process\Get-ProcsWMI.ps1' -Authentication Default -OutputFormat TSV -Transcribe -Verbose
$kansaNewRuns = @(Get-ChildItem -Path . -Directory -Filter 'Output_*' |
    Where-Object { $_.FullName -notin $kansaExistingRuns })
if ($kansaNewRuns.Count -ne 1) { throw 'Expected one new Output_ folder. Inspect the Kansa run.' }
$kansaBaselineRun = $kansaNewRuns[0].FullName
$kansaBaselineRun

Kansa writes a timestamped Output_* folder. The commands above select the folder created by this run. Preserve the whole folder, including any transcript or Error.Log:

if (Test-Path -LiteralPath "$kansaCase\baseline") { throw 'Baseline already saved. Do not overwrite it.' }
Copy-Item -LiteralPath $kansaBaselineRun -Destination "$kansaCase\baseline" -Recurse -ErrorAction Stop
$kansaBaselineRun | Set-Content -LiteralPath "$kansaCase\baseline-run-path.txt"
Get-ChildItem -LiteralPath "$kansaCase\baseline" -Recurse -File | Select-Object FullName, Length

Look for: a process TSV, usually inside baseline\ProcsWMI. Open it in Notepad: the first line names the columns, and the following lines should contain processes. A file containing only column headings is an empty result. Check the transcript and Error.Log if present. Some processes may have blank paths or hashes; note those gaps. Fix an empty collection before starting the training process.

2. Start one harmless training process

This copies the VM's own cmd.exe under the training filename. /k keeps the command window open and title labels it. It downloads no program. Run it only on the disposable VM, then leave the labelled window open until the second collection finishes.

if (Test-Path -LiteralPath $kansaSeed) { throw 'Training path already exists. Stop and inspect it.' }
Copy-Item -LiteralPath "$env:SystemRoot\System32\cmd.exe" -Destination $kansaSeed -ErrorAction Stop
$kansaSeedProcess = Start-Process -FilePath $kansaSeed -ArgumentList '/k title KANSA-01 harmless training process' -PassThru
$kansaSeedProcess | Select-Object Id, ProcessName, StartTime |
    Out-String | Set-Content -LiteralPath "$kansaCase\training-process.txt"
Get-FileHash -Algorithm SHA256 -LiteralPath $kansaSeed |
    Export-Csv -LiteralPath "$kansaCase\training-file-sha256.csv" -NoTypeInformation
$kansaSeedProcess | Select-Object Id, ProcessName, StartTime

Look for: a labelled command window and a process ID (PID). The path is intentionally unusual here; this exercise does not make it malicious.

3. Collect again while the training window is open

Run the same Kansa command from the same VM. Record the new Output_* folder separately from the baseline.

Set-Location 'C:\Tools\Kansa'
$kansaExistingRuns = @(Get-ChildItem -Path . -Directory -Filter 'Output_*' |
    Select-Object -ExpandProperty FullName)
.\kansa.ps1 -Target localhost -ModulePath '.\Modules\Process\Get-ProcsWMI.ps1' -Authentication Default -OutputFormat TSV -Transcribe -Verbose
$kansaNewRuns = @(Get-ChildItem -Path . -Directory -Filter 'Output_*' |
    Where-Object { $_.FullName -notin $kansaExistingRuns })
if ($kansaNewRuns.Count -ne 1) { throw 'Expected one new Output_ folder. Inspect the Kansa run.' }
$kansaAfterRun = $kansaNewRuns[0].FullName
$kansaBaselineRun = Get-Content -LiteralPath "$kansaCase\baseline-run-path.txt"
if ($kansaAfterRun -eq $kansaBaselineRun) { throw 'Select the new run, not the baseline.' }
if (Test-Path -LiteralPath "$kansaCase\after") { throw 'Second run already saved. Do not overwrite it.' }
Copy-Item -LiteralPath $kansaAfterRun -Destination "$kansaCase\after" -Recurse -ErrorAction Stop
$kansaAfterRun | Set-Content -LiteralPath "$kansaCase\after-run-path.txt"
Get-ChildItem -LiteralPath "$kansaCase\after" -Recurse -File | Select-Object FullName, Length

Check that the second run has a nonempty process TSV. Read its transcript and Error.Log if present. A warning about an inaccessible process is different from an empty or failed collection; record the warning and continue if the expected rows were captured. If you must retry, retain the failed run in a separately named folder and record why.

Optional: hash the saved Kansa output

Keep the hash list outside the two folders it describes:

Get-ChildItem -LiteralPath "$kansaCase\baseline", "$kansaCase\after" -Recurse -File |
    Get-FileHash -Algorithm SHA256 |
    Export-Csv -LiteralPath "$kansaCase\kansa-output-sha256.csv" -NoTypeInformation

These SHA-256 hashes identify the saved files. They do not prove that every live process was captured. The collector's own Hash field is an MD5 of the process image, where available.

Collection preservedPause point: the saved runs are enough for Activity 3. Keep the training window open until you have checked the second result.

Next: find the changed row and follow its parent.

Investigate the saved results

Activity 3: Investigate and report

Your final winShow the changed row in saved output and explain what the result means.

The answer is now in the saved files. These are two live collections taken at different times; other ordinary processes may also change between them.

Returning in a new PowerShell window?

Open Windows PowerShell on the same VM and restore the two variables used below. Use your actual case path if you changed it. Then run Activity 3 from Step 1 to load the saved files; you do not need to collect again.

$kansaCase = 'C:\Cases\Kansa-Local-01'
$kansaSeed = 'C:\Users\Public\chrome_update.exe'
1. Open and check the two saved process files

Find the one TSV in each saved folder. Kansa may name the file with localhost or the VM name. Confirm both names are consistent with the saved localhost check. If either folder has zero or multiple TSVs, inspect the run before choosing a file.

$kansaBaselineFiles = @(Get-ChildItem -LiteralPath "$kansaCase\baseline" -Recurse -File -Filter '*.tsv')
$kansaAfterFiles = @(Get-ChildItem -LiteralPath "$kansaCase\after" -Recurse -File -Filter '*.tsv')
if ($kansaBaselineFiles.Count -ne 1 -or $kansaAfterFiles.Count -ne 1) {
    throw 'Expected one process TSV in each saved run. Inspect the folders and logs.'
}
$kansaBaselineRows = @(Import-Csv -LiteralPath $kansaBaselineFiles[0].FullName -Delimiter ([char]9))
$kansaAfterRows = @(Import-Csv -LiteralPath $kansaAfterFiles[0].FullName -Delimiter ([char]9))
if ($kansaBaselineRows.Count -eq 0 -or 'ExecutablePath' -notin $kansaBaselineRows[0].PSObject.Properties.Name) {
    throw 'Empty or unexpected baseline TSV. Check the file, delimiter, and log.'
}
if ($kansaAfterRows.Count -eq 0 -or 'ExecutablePath' -notin $kansaAfterRows[0].PSObject.Properties.Name) {
    throw 'Empty or unexpected second TSV. Check the file, delimiter, and log.'
}
foreach ($kansaFile in @($kansaBaselineFiles[0], $kansaAfterFiles[0])) {
    if ($kansaFile.Name -notlike 'localhost-*' -and
        $kansaFile.Name -notlike "$($env:COMPUTERNAME)-*") {
        throw 'A TSV filename does not match localhost or this VM. Check both runs.'
    }
}
"Baseline: $($kansaBaselineRows.Count) process rows"
"After: $($kansaAfterRows.Count) process rows"
$kansaAfterRows | Select-Object -First 5 Name, ProcessId | Format-Table

Look for: a positive row count for each run and a preview of five processes. Your totals will vary. You have now loaded real output from your VM; the original TSVs remain in their saved folders.

2. Count the matching rows before and after

Filter on the full executable path, then save a compact comparison. Run this in the same PowerShell window as Step 1:

$kansaBaselineHits = @($kansaBaselineRows | Where-Object ExecutablePath -EQ $kansaSeed)
$kansaAfterHits = @($kansaAfterRows | Where-Object ExecutablePath -EQ $kansaSeed)
@(
    [pscustomobject]@{Phase='Baseline'; Rows=$kansaBaselineRows.Count; Matches=$kansaBaselineHits.Count; Source=$kansaBaselineFiles[0].FullName}
    [pscustomobject]@{Phase='After'; Rows=$kansaAfterRows.Count; Matches=$kansaAfterHits.Count; Source=$kansaAfterFiles[0].FullName}
) | Export-Csv -LiteralPath "$kansaCase\comparison.csv" -NoTypeInformation
Import-Csv -LiteralPath "$kansaCase\comparison.csv" | Format-Table

Expected result: zero matches in the baseline and one in the second run if the labelled window was open during collection. That shows Kansa saw the controlled process at collection time. Closing the window later does not change a saved TSV.

My result is different
What you see What to check
Zero matches in both runs Was the training window open during the second collection? Search its PID from training-process.txt in the second TSV; a missing path may explain the failed path search.
A match in the baseline Check the run times and source folders. You may have used an earlier lab run or started the process before taking the baseline.
More than one match after Inspect every matching PID and command line. There may be more than one training window.
No process rows Keep the logs and resolve the collection failure. This cannot support an absence finding.

Keep the original runs and record the actual result. If you repeat collection, save it under a new name such as after-retry-1 and identify that source in your note.

3. Inspect the row and its parent

A matching path is the start of an investigation. Read its command line and parent process ID from the saved second run:

$kansaAfterHits |
    Select-Object Name, ExecutablePath, ProcessId, ParentProcessId, CommandLine, CreationDate, Username, Hash |
    Format-List
foreach ($kansaHit in $kansaAfterHits) {
    "Parent candidate for child PID $($kansaHit.ProcessId):"
    $kansaParentId = $kansaHit.ParentProcessId
    $kansaAfterRows | Where-Object ProcessId -EQ $kansaParentId |
        Select-Object Name, ProcessId, ExecutablePath, CommandLine, CreationDate | Format-List
}

Look for: the training title in CommandLine and a parent candidate such as the PowerShell process you used to launch it. Compare the two CreationDate values: a parent candidate created after the child cannot be its original parent. A missing parent row may mean the parent exited. Windows can reuse process IDs, so record the timing and any uncertainty.

The collector's Hash is MD5, so compare it only with another MD5. The SHA-256 you saved identifies the training file separately.

4. Write a short case handover
Make the analyst's callSuppose the baseline has zero matches and the second run has one. Which finding can you defend?

Use your actual counts, file paths, and collection times from the two runs. Paste this template into Notepad and save it as handover.txt inside your $kansaCase folder.

Question: Did the training process appear after the controlled change?
VM name and time zone:
Kansa source or revision, module, and exact command:
Baseline: collection time, TSV path, row count, matching rows.
After: collection time, TSV path, row count, matching rows.
Supporting row: process ID, path, command line, and hash if present.
Parent process: observed name and path, or not present in the saved snapshot.
Meaning: What does the change from baseline to second run show?
Limits: What can these two live snapshots not establish?
Errors or missing fields:
Next check:

Explain the zero carefully: no baseline match means the collector returned no matching row at that moment. It does not prove the file never existed or never ran. For a real alert, you could next check the file's provenance, event records, and parent process context.

Case closeout5. Verify evidence and clean up the VM
Preserve first

Check that comparison.csv, handover.txt, and both run folders are in the case folder. Close the labelled command window before removing the training file.

Then confirm the process ended and the file still matches its saved hash:

$kansaSeed = 'C:\Users\Public\chrome_update.exe'
if (Get-Process -Name chrome_update -ErrorAction SilentlyContinue) { throw 'Close the labelled training window first.' }
$kansaRecordedHash = (Import-Csv -LiteralPath "$kansaCase\training-file-sha256.csv").Hash
if ((Get-FileHash -Algorithm SHA256 -LiteralPath $kansaSeed).Hash -ne $kansaRecordedHash) {
    throw 'Training file changed. Inspect it before cleanup.'
}
Remove-Item -LiteralPath $kansaSeed

Record the closeout: note the cleanup and any WinRM or network profile change you reversed in the handover. Before restoring a VM snapshot, copy the complete case folder outside the VM and open the copy.

Ready for another analystCompletion is supported by the saved evidence, including a clearly explained failure or gap.

Finish with a note someone else can verify from your files.

Review the evidence checklist

Full Lab evidence checklist

Case folder handoverSave the two checkpoints in each activity.
  • Contextcase-context.txt, localhost-check.csv, and the Kansa source or revision (kansa-revision.txt if created)
  • Collectionsbaseline/ and after/ with nonempty process TSVs and any run logs; training-process.txt and training-file-sha256.csv
  • Findingcomparison.csv and handover.txt, with collection times, errors, limits, and a next check

Before snapshot rollback: keep failed or retried runs with an explanation, then copy and open the complete case folder outside the VM.