Reading width
Wide uses the full column for everything, text, diagrams, code, and exercises. Narrow keeps the standard reading width.
Text size
Scales the body text. Headings and code blocks keep their size.
In this section
0.4 Console, Terminal and VS Code
Introduction
PowerShell's engine is the same wherever it runs, as Lesson 0.3 showed, but the program you type into changes what is convenient, what is remembered and what is saved. An analyst uses three: the PowerShell console for quick questions and live response, Windows Terminal to keep several shells in tabs, and Visual Studio Code for anything worth keeping as a script.
Each of these also leaves traces. That double role, a tool for you and a record about others, runs through this whole lesson and through the course. The console's history file holds what was typed, profiles run code at every start, and each host keeps its own of both.
Those are conveniences for you and, in Module 7 and Module 8, evidence about someone else. This lesson sets up the three hosts and shows what each one keeps.
None of the three is required by this course. Every lesson's commands run in any PowerShell 7.4 or later, in any of them. What changes is how comfortable long work becomes, and how much of it is saved in a form you can reuse. The sections below cover what each host is good for, in the order most analysts adopt them.
By the end of the lesson you can use tab completion to find commands and parameters, run a script from the current folder, organize shells in Windows Terminal, write and check scripts in VS Code with the PowerShell extension, find PSReadLine's history file, locate each host's profiles, and keep long values intact when you copy output into notes or reports.
The evidence is a record of the hosts on Northgate's analyst workstation, NGE-DFIR-WS01: the profile paths, history file and PSReadLine version for each shell, for the analyst account adm.r.ir, shown as the workstation prints them. Completion, script running, the analyzer and output formatting run live in this course's PowerShell 7.6.
The Console
completion does the typingThe pwsh console is the quickest place to ask a question, and the place most analysts first meet PowerShell. Its most useful feature is tab completion: type part of a command, a parameter or a path and press Tab. The function behind it, TabExpansion2, can be called directly to show what Tab would offer:
PS C:\Evidence> (TabExpansion2 'Get-Proc' 8).CompletionMatches.CompletionText
Get-Process
PS C:\Evidence> (TabExpansion2 'Get-Process -Na' 15).CompletionMatches.CompletionText -join ', '
-Name
PS C:\Evidence> (TabExpansion2 'Import-Csv ./task' 17).CompletionMatches.CompletionText
./tasklist-sharma.csv
Get-Proc completed to Get-Process, the only command whose name starts that way. -Na completed to -Name, the only parameter of Get-Process starting with those letters. A path completed too, from ./task to the tasklist file in the current folder, so a long evidence file name never has to be typed in full. In the console, pressing Tab cycles through the matches, and Ctrl+Space shows them all as a menu.
Completion is more than saved keystrokes. It confirms that a command and a parameter exist before you run anything, which is the cheapest defense against the invented commands and parameters Lesson 11.8 found in AI-written scripts. If Tab will not complete it, PowerShell does not have it.
Completion also works on property names after a dot, on variables you have defined, and on the values a parameter accepts, such as the log names Get-WinEvent can read on Windows. For commands you use rarely, it is often faster than looking anything up.
The console also keeps a short-term history of the current session: the up arrow recalls earlier lines, and Get-History lists them. That history ends with the session; the longer-lived one, in a file, comes from PSReadLine, which Section 5 covers.
The console has its own small habits worth knowing. Ctrl+C stops a running command, which is the right response to a query that is taking too long against a remote host. Esc clears the line being typed. And a line ending in a pipe character or an open brace continues on the next line, shown with >>, which is how longer commands in this course are written.
Running a Script
the current folder is not searchedA script is a file of PowerShell commands with a .ps1 extension. Running one from the folder you are in has one rule that surprises everyone once:
PS C:\Evidence> Set-Content ./check.ps1 "'script ran'"
PS C:\Evidence> check.ps1
check.ps1: The term 'check.ps1' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
PS C:\Evidence> ./check.ps1
script ran
PS C:\Evidence> Remove-Item ./check.ps1
check.ps1 alone was not recognized: PowerShell does not look in the current folder for commands. ./check.ps1, with the path, ran it. On Windows .\check.ps1 works the same way.
The rule is a deliberate security decision. If PowerShell searched the current folder first, a file named after a common command, dropped into a folder an analyst opens, would run instead of the real command.
Requiring an explicit path means you always know which file you are running. It also makes scripts in command lines and logs easier to read: ./collect.ps1 or C:\IR\Collect-Incident.ps1 says exactly which file ran, where a bare name would leave the reader guessing which of several copies PowerShell found. The command prompt does search the current folder, which is one reason attackers have long planted files named like system tools.
The same caution applies to anything you run from a shared or downloaded folder. A script's name tells you nothing about its contents, and Lesson 11.8's review applies to every .ps1 you did not write, however it arrived on your machine.
Execution policy, which Module 7 examines, may also stop a script on Windows, with a message about the policy rather than about the path. The two messages are different problems: "not recognized" means PowerShell did not find it; a policy message means it found it and declined to run it.
Windows Terminal
every shell in one windowWindows Terminal is Microsoft's modern terminal application, included in current Windows 11 and available for Windows 10. It replaced the old console window as the default terminal in Windows 11, so on a current machine, starting pwsh usually opens it inside Terminal without any setup.
It runs any command-line shell in tabs and panes, each from a profile: PowerShell 7, Windows PowerShell, the command prompt, Linux shells under WSL, and SSH sessions. It can be opened with tabs already set up:
# Windows Terminal from a command prompt or Run box: three tabs, one per shell
wt -p "PowerShell" `; new-tab -p "Windows PowerShell" `; new-tab -p "Command Prompt"
Three tabs, one per shell, opened together from a single command line, each starting with its own profile's settings. The backtick before each semicolon stops PowerShell from treating the semicolon as its own statement separator, so the whole line reaches Windows Terminal intact; typed in a command prompt, the backticks are left out.
For investigation work that is a practical arrangement: pwsh for analysis, Windows PowerShell for checking how something behaves in 5.1, the evidence a host's own scripts would produce, and a command prompt for the occasional tool that expects it.
Windows Terminal is a host for other programs, not a shell itself, and importantly it changes nothing about how PowerShell behaves inside it. PowerShell inside it still uses the console host and still loads PSReadLine; what Terminal adds is the window around it, with tabs, panes, better text rendering and per-profile settings such as starting folder and color scheme.
A colored profile per environment is worth the minute it takes: a red background for a session connected to a production server, for example, makes it harder to type into the wrong tab during an incident, which is a mistake that has ended more than one investigation badly.
Visual Studio Code
where scripts are writtenAnything you will run more than once belongs in a script, and the place to write scripts is Visual Studio Code with Microsoft's PowerShell extension. Visual Studio Code is free and runs on Windows, macOS and Linux, so the same editor works wherever your analysis does.
The extension adds an integrated PowerShell console, syntax highlighting, completion, a debugger, and live analysis by PSScriptAnalyzer, the tool Lesson 11.8 used to review scripts. What it would underline in a two-line draft:
PS C:\Evidence> Set-Content ./draft.ps1 "gps | ? Name -eq rundll32`n`$pw = ConvertTo-SecureString 'Spring2026!' -AsPlainText -Force"
PS C:\Evidence> Import-Module PSScriptAnalyzer
PS C:\Evidence> Invoke-ScriptAnalyzer ./draft.ps1 | Format-Table Line, RuleName, Severity
Line RuleName Severity
---- -------- --------
1 PSAvoidUsingCmdletAliases Warning
1 PSAvoidUsingCmdletAliases Warning
2 PSAvoidUsingConvertToSecureStringWithPlainText Error
2 PSUseDeclaredVarsMoreThanAssignments Warning
PS C:\Evidence> Remove-Item ./draft.ps1
Four findings: the alias gps, the alias ?, a variable assigned and never used, and a password written into the script as an Error. PSScriptAnalyzer ships with the PowerShell extension, so the same rules run in the editor without anything else to install; installing the module separately, as Lesson 11.8 did, lets you run the same checks outside the editor too.
In VS Code each appears as a squiggle under the line as you type, with the rule's explanation on hover, so the problems are fixed before the script ever runs.
VS Code also changes how an investigation is recorded. A console session is a sequence of commands that exists only in history; a script in a folder is a document you can save beside the evidence, rerun, review and hand to someone else.
This course's lessons are written as scripts for that reason, and the habit of moving from the console to a script once a question gets serious is one of the most valuable an analyst can form.
The extension's integrated console runs the script you are editing in the same session, so variables and results remain available for inspection after a run. Its debugger can stop at a line and show every variable's value at that moment, which is often faster than adding temporary output to find out why a filter returns nothing.
One caution with the integrated console: because the session persists between runs, a variable set by an earlier run is still there in the next one. A script that seems to work may be relying on a value left behind, and fail when run fresh. The extension can restart its session, and running a finished script once in a new pwsh window before trusting it is a good habit.
PSReadLine: History and Editing
what the console remembersThe console's editing, colors, prediction and history file come from the PSReadLine module, which pwsh loads in interactive sessions. This build has it available:
PS C:\Evidence> (Get-Module PSReadLine -ListAvailable).Version.ToString()
2.4.5
PS C:\Evidence> (Get-Module PSReadLine -ListAvailable).ModuleBase -like '*Modules*PSReadLine*'
True
Version 2.4.5, the one that ships with this PowerShell. The second line confirms where it is installed, in a Modules folder, so it loads like any other module. On the workstation, PSReadLine's history file for the analyst account, and the version pwsh loads there:
PS C:\> (Get-PSReadLineOption).HistorySavePath # pwsh, as adm.r.ir
C:\Users\adm.r.ir\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
PS C:\> (Get-Module PSReadLine).Version # pwsh
2.4.5
By default, every line typed in the console is appended to ConsoleHost_history.txt in the user's roaming profile, across sessions, unless it matches PSReadLine's filter for sensitive words.
The file is plain text, one command per line, with no timestamps, so it says what was typed and in what order but not when; Module 7 pairs it with logs that do carry times. Ctrl+R searches it backwards, which is the fastest way to find a command you ran last week.
PSReadLine keeps its options in the session, and Set-PSReadLineOption changes them: the history file's location, whether history is saved at all, and the filter that decides which lines are kept only in memory.
Lesson 10.4 showed that filter at work, keeping lines with words like apikey and password out of the file. An attacker can change the same options to stop their commands being written, which is why a missing or truncated history file is itself worth noting in an investigation.
That file is both a convenience and a liability. Lesson 10.4 found API keys in exactly this file, and Module 7 reads attackers' commands from it. For your own work, it means anything typed at the prompt, including a secret, is likely kept on disk: prefer prompts, vaults and parameters to typing secrets as text.
For an investigation, it means the history file belongs in every collection from a Windows host. It is also per user, so a machine used by several accounts has several, one in each profile, and the attacker's commands may be in the history of an account nobody expected to be used.
PSReadLine also predicts as you type, offering earlier commands from history as grey suggestions, accepted with the right arrow. It is fast and occasionally dangerous: a predicted command from a different case, accepted too quickly, runs against the wrong host. Read the whole line before pressing Enter, every single time.
Profiles
code that runs at every startA profile is an ordinary PowerShell script that PowerShell runs automatically when it starts, used to load modules, set preferences and define shortcuts. There are four for each shell, for all users or the current user, and for all hosts or the current one. On the workstation, pwsh's four, and the one VS Code's console uses:
PS C:\> $PROFILE | Select-Object *Host* | Format-List # pwsh
AllUsersAllHosts : C:\Program Files\PowerShell\7\profile.ps1
AllUsersCurrentHost : C:\Program Files\PowerShell\7\Microsoft.PowerShell_profile.ps1
CurrentUserAllHosts : C:\Users\adm.r.ir\Documents\PowerShell\profile.ps1
CurrentUserCurrentHost : C:\Users\adm.r.ir\Documents\PowerShell\Microsoft.PowerShell_profile.ps1
PS C:\> $PROFILE # in VS Code's integrated console
C:\Users\adm.r.ir\Documents\PowerShell\Microsoft.VSCode_profile.ps1
pwsh's profiles live in Program Files for all users and under Documents\PowerShell for the current user, with one file for every host and one for the console host alone. The current-user files sit in the user's Documents folder, which OneDrive often redirects, so on some machines the profile is quietly synced to the cloud and back to every device the user signs into.
VS Code's console has its own current-host profile, Microsoft.VSCode_profile.ps1. Windows PowerShell has a separate set under Documents\WindowsPowerShell and System32. The Windows PowerShell ISE, an older script editor included with Windows, loads its own profile too, but it supports only Windows PowerShell 5.1 and Microsoft no longer develops it, which is why this course uses VS Code instead.
Six or more files on one machine, any of which may run when PowerShell starts. Most are absent on a typical machine; the paths are where PowerShell looks, not files that must exist. Test-Path on each of $PROFILE's four properties shows which ones actually do, which is the first check Module 8 makes on a suspect host.
For your own workstation, a profile is where this course's small amount of setup goes in Lesson 0.8. For an investigation, profiles are a persistence location: code an attacker adds to any of them runs every time anyone starts that shell, which Module 8 hunts on the case's fleet.
Starting PowerShell with -NoProfile skips them, which is why collection scripts and responders use it on a machine they do not trust.
Keep your own profiles deliberately small. Every line in a profile runs every time PowerShell starts, slows every start a little, and changes the environment your commands run in.
A command that works in your console because your profile loaded something may fail in a scheduled task or on a colleague's machine, which is a confusing bug to chase. Lesson 0.8 puts only a few lines in the analyst profile for this reason.
Output That Survives Copying
width, truncation and listsConsole output is drawn for the window it appears in. When a table is wider than the window, its last column is cut off. Copying that into a ticket is a common way to lose the most important value in it:
PS> $p | Where-Object ProcessId -eq 8012 | Format-Table ProcessId, Owner, CommandLinePS> $p | Where-Object ProcessId -eq 8012 | Format-List ProcessId, Owner, CommandLineBoth, at a console width of 80 characters:
PS C:\Evidence> $p = Get-Content ./procs/proc-sharma.json -Raw | ConvertFrom-Json
PS C:\Evidence> $p | Where-Object ProcessId -eq 8012 | Format-Table ProcessId, Owner, CommandLine | Out-String -Width 80
ProcessId Owner CommandLine
--------- ----- -----------
8012 NT AUTHORITY\SYSTEM "C:\Windows\System32\rundll32.exe" C:\Users\p.sha…
PS C:\Evidence> $p | Where-Object ProcessId -eq 8012 | Format-List ProcessId, Owner, CommandLine
ProcessId : 8012
Owner : NT AUTHORITY\SYSTEM
CommandLine : "C:\Windows\System32\rundll32.exe" C:\Users\p.sharma\AppData\Local\Temp\upd.dll,Start
The table cut the command line at C:\Users\p.sha and an ellipsis; the list showed it in full. The difference is the whole point of the evidence: the full value names the DLL and its folder, the truncated one names neither.
Nothing in the table warned that the value was incomplete, and in a ticket it would look like the whole record. A wider window would have shown more, which is exactly the problem: what you copy depends on how wide your window happened to be, not on the evidence.
For anything you copy into notes, tickets or reports, use Format-List, or better, export the objects to JSON or CSV as Lesson 0.1 did and attach the file.
A JSON file attached to a ticket can be read back by anyone with PowerShell into exactly the objects you had, which no amount of careful copying from the screen can match. Out-String -Width with a large value also helps when a table really is the clearest view, but the safest rule is to copy data, not drawings of data.
The same truncation happens with long property values in Format-List when they exceed what the host can show, with arrays of more than a few items, which PowerShell abbreviates with an ellipsis by default, and with nested objects shown as type names. Each is a display choice. The underlying object is complete; only the drawing is not.
The Hosts, Checked
five factsThe facts of this lesson, written as checks against the record and this build: pwsh and Windows PowerShell have different profiles, VS Code has its own, both consoles share one history file, completion finds Get-Process, and PSReadLine is version 2 or later:
PS C:\Evidence> $h = Get-Content ./hosts-ws01.json -Raw | ConvertFrom-Json
PS C:\Evidence> @(
>> $h.PowerShell7.CurrentUserCurrentHost -ne $h.WindowsPowerShell.CurrentUserCurrentHost
>> $h.VSCode.CurrentUserCurrentHost -like "*VSCode_profile.ps1"
>> $h.PowerShell7.HistorySavePath -eq $h.WindowsPowerShell.HistorySavePath
>> (TabExpansion2 "Get-Proc" 8).CompletionMatches.CompletionText -contains "Get-Process"
>> [version](Get-Module PSReadLine -ListAvailable).Version -ge [version]"2.0"
>> )
True
True
True
True
True
Five True. Each check reads the record or asks this build's PowerShell directly, so the facts hold as the lesson states them. The third is the one investigators remember: Windows PowerShell and pwsh consoles append to the same ConsoleHost_history.txt for a user, so one file holds both shells' typed commands, while VS Code's console keeps its own file beside it.
These facts are also where a collection script looks: the history files, every profile path for every shell, and the PSReadLine settings that decide what was kept. Module 9's collector gathers all of them, and this lesson's record is a small version of what it produces.
With the hosts set up, the next lesson looks at what you run in them: cmdlets, functions, scripts and modules, and the naming rule that makes PowerShell readable.
Practice
- Complete. Use Tab to find commands, parameters and paths, and trust Tab over memory.
- Script it. Move anything you will run twice into a script in VS Code with the PowerShell extension.
- Mind history. Remember the console keeps what you type; keep secrets out of the command line.
- Copy data. Copy from lists and exports, never from tables cut to the window.
Then, on a Windows machine of your own, open pwsh, Windows PowerShell and VS Code's integrated console, and run $PROFILE and $Host.Name in each.
Next: cmdlets, functions, scripts and modules, the four kinds of command you will run in these hosts.