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.6 Finding Your Way Around
Introduction
No one remembers every PowerShell command, parameter and property, not even the people who wrote them, and no one needs to. PowerShell describes itself: four commands answer almost every question about what exists, what a command does, what an object holds and what a short name means.
Analysts who use them work faster than those who search the web, and their answers are right for the version in front of them.
A search engine answers for whatever version the author of the page was using, on whatever platform, with whatever modules. The session answers for exactly what is installed and loaded now. For a course whose evidence spans Windows PowerShell 5.1, PowerShell 7 and several modules, that difference is not academic: the same command can have different parameters in different places.
The four are Get-Command, Get-Help, Get-Member and Get-Alias. Each answers one question, and together they cover the four things you need to know about anything in PowerShell: whether it exists, how to use it, what it gives you, and what a short name hides.
This lesson uses each on the case's evidence and on the commands this course relies on, and shows the most common mistake they prevent: guessing a property name, getting no results and no error, and believing the empty answer.
They are also the commands to use when this course is not in front of you. Every later module introduces new cmdlets, and each lesson explains them, but the habit that lasts is being able to meet an unfamiliar command or object on a real case and work out how to use it in a few minutes, without leaving the console.
By the end of the lesson you can read a command's help and install full help, write help for your own functions, find commands by verb, noun or part of a name, read a command's syntax and parameter sets, list an object's properties and methods, expand aliases, and use strict mode to turn a misspelled property into an error instead of a silent empty result.
The evidence is NE-SHARMA-LT's process export from Lesson 0.1, explored as if for the first time. Everything runs live in this course's PowerShell 7.6.
Get-Help
and what a fresh install showsGet-Help shows a command's documentation, the first thing to read about any command you have not used before. On a fresh PowerShell 7 installation, it shows less than you might expect:
PS C:\Evidence> Get-Help Get-Process
NAME
Get-Process
SYNTAX
Get-Process [[-Name] <string[]>] [-Module] [-FileVersionInfo] [<CommonParameters>]
Get-Process [[-Name] <string[]>] -IncludeUserName [<CommonParameters>]
Get-Process -Id <int[]> [-Module] [-FileVersionInfo] [<CommonParameters>]
Get-Process -Id <int[]> -IncludeUserName [<CommonParameters>]
Get-Process -InputObject <Process[]> [-Module] [-FileVersionInfo] [<CommonParameters>]
Get-Process -InputObject <Process[]> -IncludeUserName [<CommonParameters>]
ALIASES
gps
REMARKS
Get-Help cannot find the Help files for this cmdlet on this computer. It is displaying only partial help.
-- To download and install Help files for the module that includes this cmdlet, use Update-Help.
-- To view the Help topic for this cmdlet online, type: "Get-Help Get-Process -Online" or
go to https://go.microsoft.com/fwlink/?LinkID=2096814.
The name, the syntax, the alias gps, and a remark: Get-Help cannot find the help files and is showing only partial help. PowerShell 7 does not ship its full help text; it generates what it can from the command itself and points to two ways to get the rest, Update-Help and the -Online switch.
Shipping help separately keeps PowerShell small and lets Microsoft correct the documentation without releasing a new version of the shell, at the cost of a step every new installation needs.
Even partial help is useful. It is generated from the command's own definition, so it can never be out of date or describe parameters the installed version lacks.
The syntax lines show every way the command can be called, which Section 4 reads in detail, and the alias line explains a short name you may have seen in a script. For a quick check of what parameters exist, partial help is enough.
Get-Help also takes a parameter's name, Get-Help Get-Process -Parameter Name, to explain just one parameter, and -Examples to show only the worked examples, which are often the fastest route from not knowing a command to using it correctly. Both work best once full help is installed.
Full Help, and Your Own
Update-Help and comment-based helpUpdate-Help downloads the full help for the modules installed, run once and then occasionally to refresh it, after which Get-Help shows descriptions, every parameter's explanation and worked examples, offline. Get-Help -Online opens the same page on Microsoft Learn in a browser.
The about_ topics, such as about_Command_Precedence behind Lesson 0.5, arrive with Update-Help too. Update-Help needs an internet connection and, for modules installed for all users, an elevated session; on an isolated analysis machine, Save-Help on a connected machine and Update-Help -SourcePath on the isolated one does the same job from a folder.
Your own functions get full help without downloading anything, from a comment block at the top, as Lesson 11.3 teaches in depth. A function that lists user-owned processes whose parent is missing from the export, with help, then asked for its example and one parameter's description, then run:
PS C:\Evidence> function Get-UserOrphan {
>> <#
>> .SYNOPSIS
>> Lists user-owned processes whose parent is not in the same export.
>> .PARAMETER Path
>> A process export written with ConvertTo-Json.
>> .EXAMPLE
>> Get-UserOrphan -Path ./procs/proc-sharma.json
>> #>
>> param([Parameter(Mandatory)][string]$Path)
>> $all = Get-Content $Path -Raw | ConvertFrom-Json
>> $all | Where-Object { $_.Owner -notlike '*SYSTEM' -and $_.ParentProcessId -notin $all.ProcessId -and $_.Name -ne 'System Idle Process' }
>> }
PS C:\Evidence> Get-Help Get-UserOrphan -Examples
NAME
Get-UserOrphan
SYNOPSIS
Lists user-owned processes whose parent is not in the same export.
-------------------------- EXAMPLE 1 --------------------------
PS > Get-UserOrphan -Path ./procs/proc-sharma.json
PS C:\Evidence> (Get-Help Get-UserOrphan -Parameter Path).description.Text
A process export written with ConvertTo-Json.
PS C:\Evidence> Get-UserOrphan -Path ./procs/proc-sharma.json | Format-Table ProcessId, Name, ParentProcessId
ProcessId Name ParentProcessId
--------- ---- ---------------
3608 explorer.exe 3584
7488 rundll32.exe 7316
Get-Help showed the example exactly as it was written, and the parameter's description. It found them because the comment block sits directly inside the function, using keywords such as .SYNOPSIS, .PARAMETER and .EXAMPLE that Get-Help recognizes; misspell a keyword and that part of the help silently disappears, which Lesson 11.3's help tests catch.
The function itself found two user-owned processes whose parent is not in the export: explorer.exe, whose parent exits by design once the user's session is set up, and rundll32 7488, whose parent was the PowerShell process that Word started.
The help you write is the help your colleagues read. A function shared without it forces everyone to read its code to learn what it does; a function with a synopsis, a parameter description and one example explains itself through the same Get-Help they use for Microsoft's commands.
Notice how the function used its own name for the idea: Get-UserOrphan, a Verb-Noun name built from an approved verb and a noun saying what it returns. Anyone who meets it in a script can guess its purpose, run Get-Help on it, and be right about what it does without reading the code, which is the whole point of naming and documenting tools well.
Get-Command for Discovery
searching by verb, noun and patternLesson 0.5 used Get-Command to identify a name. Pointed at a name that does not exist, it fails with the same not-recognized message as running it would, which makes it a safe way to test a command from an unfamiliar script without running anything. It is just as useful to search for commands you do not know yet. By a wildcard, by verb and by noun:
PS C:\Evidence> (Get-Command *Json*).Name -join ', '
ConvertFrom-Json, ConvertTo-Json, Test-Json
PS C:\Evidence> (Get-Command -Verb ConvertTo).Name -join ', '
ConvertTo-CliXml, ConvertTo-Csv, ConvertTo-Html, ConvertTo-Json, ConvertTo-SecureString, ConvertTo-Xml
PS C:\Evidence> (Get-Command -Noun Process).Name -join ', '
Debug-Process, Get-Process, Start-Process, Stop-Process, Switch-Process, Wait-Process
Json found the three commands with Json in their names. On Windows the same search finds the same three, because they are part of PowerShell itself rather than of the operating system; searches for Windows features, such as WinEvent or ScheduledTask, find nothing on Linux, which is itself an answer. -Verb ConvertTo listed every conversion to another format, from CLI XML to a secure string. -Noun Process listed every command that acts on processes. The three lists together took one line each and no prior knowledge of any command in them. Each search starts from what you want to do or what you want to act on, which is usually what you know. Wildcards work on any part of a name: Defender or Mp finds Defender's commands on Windows, Event finds the event log commands, and Secret finds the vault commands of Module 10, each a quick way into an unfamiliar area.
Because the names follow Verb-Noun, these searches work across modules you have never seen. Searching by noun is often the better first move, since the thing you want to act on, a process, a service, an event, a user, is usually clearer in your mind than the verb a module's author chose for the action.
A newly installed module's commands appear in the same searches, and -Module restricts a search to one, as Lesson 11.6 did with the toolkit. When you think a command ought to exist, Get-Command will tell you in a second whether it does.
Get-Command can also search by parameter: Get-Command -ParameterName ComputerName lists every command that can reach another machine directly, a useful question when planning a collection. And -CommandType Cmdlet or Function narrows a search to one kind, which keeps native programs on the path out of the results.
Syntax and Parameter Sets
reading the bracketsThe syntax lines describe exactly how a command can be called. Get-Process has six forms, its parameter sets:
PS C:\Evidence> Get-Command Get-Process -Syntax
Get-Process [[-Name] <string[]>] [-Module] [-FileVersionInfo] [<CommonParameters>]
Get-Process [[-Name] <string[]>] -IncludeUserName [<CommonParameters>]
Get-Process -Id <int[]> [-Module] [-FileVersionInfo] [<CommonParameters>]
Get-Process -Id <int[]> -IncludeUserName [<CommonParameters>]
Get-Process -InputObject <Process[]> [-Module] [-FileVersionInfo] [<CommonParameters>]
Get-Process -InputObject <Process[]> -IncludeUserName [<CommonParameters>]
PS C:\Evidence> (Get-Command Get-Process).ParameterSets.Name -join ', '
Name, NameWithUserName, Id, IdWithUserName, InputObject, InputObjectWithUserName
Each line of the syntax is one parameter set: a combination of parameters that can be used together in a single call. The set names, Name, NameWithUserName and the rest, are what PowerShell uses internally to decide which set a call matches, and they appear in error messages when a call matches none.
Get-Process can select processes by name, by ID or by passing process objects in, each with or without -IncludeUserName. Parameters from different sets cannot be mixed, which is why Get-Process -Name x -Id 4 is refused.
The error PowerShell gives, that the parameter set cannot be resolved, is one of the most common a newcomer sees, and the syntax lines explain it every time: the two parameters you used never appear on the same line.
The brackets carry the rest of the meaning. They take a minute to learn and then read at a glance. Square brackets around a parameter mean it is optional. A parameter without them is required in that parameter set, and PowerShell asks for it if it is missing.
Square brackets around just its name, as in [[-Name]
Reading syntax this way is a small skill that pays off constantly. It is also the quickest way to compare the same command across versions: run -Syntax in Windows PowerShell and in PowerShell 7, and any parameter one has and the other lacks is visible at once.
It tells you whether a parameter takes many values, whether it can be piped, and which parameters conflict, all without reading the full help.
Get-Member
what an object actually holdsGet-Member lists an object's properties and methods, with each property's type. Piped a collection, it reports each distinct type once, so a pipeline carrying mixed objects, such as files and folders from Get-ChildItem, shows both types and their different members. It answers the question every filter depends on: what is this property actually called? Guessing gets the wrong answer in a way that is easy to miss:
PS> @($p | Where-Object User -like '*SYSTEM').CountPS> ($p | Get-Member -MemberType NoteProperty).Name
PS> @($p | Where-Object Owner -like '*SYSTEM').CountRun in full, with Get-Member first, then the filter with the property name it reported, then a look at a plain string's methods for comparison:
PS C:\Evidence> $p = Get-Content ./procs/proc-sharma.json -Raw | ConvertFrom-Json
PS C:\Evidence> ($p | Get-Member -MemberType NoteProperty).Name -join ', '
CommandLine, CreationDate, ExecutablePath, HandleCount, Name, Owner, ParentProcessId, ProcessId, SessionId, WorkingSetSize
PS C:\Evidence> @($p | Where-Object Owner -like '*SYSTEM').Count
16
PS C:\Evidence> ('text' | Get-Member -MemberType Method).Count
52
Ten data properties, among them Owner, not User. Get-Member lists them alphabetically, which makes the list easy to scan for the property you need, and NoteProperty in the command limits it to data rather than methods. With the right name, the count is 16, the number Lesson 0.1 found.
A plain string has 52 methods, from Contains to ToUpper, which Get-Member lists with their parameters whenever you need one. Nobody learns them in advance; Get-Member on a string, when a string needs cutting, finds the right one in seconds.
The wrong-property mistake deserves its own warning. It is the single most common way an analyst's correct-looking command returns a wrong answer. Where-Object, Select-Object and Sort-Object do not complain about a property that does not exist; they treat it as empty.
That leniency is deliberate, because evidence often mixes records where some have a property and others do not, and failing on the first missing one would make many real tasks impossible. The cost is that a typo looks exactly like a record that lacks the field.
A filter on a misspelled property returns nothing, a sort on one does nothing, and a selected misspelled column comes back blank. In evidence work, "nothing found" is often the most important answer you give, so it must never be produced by a typo.
Get-Member also shows whether a value is the type you expect.
When a property's type is wrong, a number stored as a string for example, Get-Member says so before a sort or a comparison gets it wrong. Each property's line includes its type, so the same command that confirms Owner exists also confirms that ProcessId is a number and CreationDate a date, the checks Lesson 0.3 made one at a time with GetType().
Get-Alias
expanding short namesGet-Alias says what a short name stands for, and with -Definition it works backwards, finding every short name defined for a command:
PS C:\Evidence> Get-Alias iex, iwr, gci, select | Format-Table Name, Definition
Name Definition
---- ----------
iex Invoke-Expression
iwr Invoke-WebRequest
gci Get-ChildItem
select Select-Object
PS C:\Evidence> (Get-Alias -Definition Get-ChildItem).Name -join ', '
dir, gci
PS C:\Evidence> (Get-Alias).Count
108
iex is Invoke-Expression and iwr is Invoke-WebRequest, the pair Lesson 11.8 found downloading and running code in the forum script. Together they form the classic download cradle, iex (iwr URL), which the course's Project meets in its older form, built on Net.WebClient. gci is Get-ChildItem and select is Select-Object. Get-ChildItem answers to dir and gci on this build, and to ls as well on Windows, as Lesson 0.5 explained.
Attacker scripts lean on aliases and on abbreviated parameter names to be short and hard to read. They also use odd capitalization, such as iNvOkE-eXpReSsIoN, and quotes or backticks inside command names, all of which PowerShell accepts because it ignores case and strips the characters before running the command.
Expanding every alias before judging a line is the first step of reading such a script, and Get-Alias does it in one command. Parameter names can be shortened too, to any unambiguous prefix: -W Hidden for -WindowStyle Hidden, -Enc for -EncodedCommand.
Get-Command -Syntax for the program or cmdlet shows the full names, which is how Module 7 decodes the case's command lines. PowerShell has over a hundred built-in aliases; there is no need to learn them, only to look up the ones you meet.
Aliases can also be defined at any time with Set-Alias or New-Alias, in a session, a script or a profile, and those user-defined ones are where Lesson 0.5's shadowing begins. Get-Alias lists them alongside the built-in ones, and a definition you did not expect, pointing a familiar name somewhere unusual, is a finding worth recording.
Strict Mode
misspellings as errorsThe silent empty result of Section 5 can be turned into a loud, immediate error. Set-StrictMode -Version Latest makes reading a property that does not exist fail loudly:
PS C:\Evidence> $p = Get-Content ./procs/proc-sharma.json -Raw | ConvertFrom-Json
PS C:\Evidence> Set-StrictMode -Version Latest
PS C:\Evidence> try { $p[1].User } catch { $_.Exception.Message }
The property 'User' cannot be found on this object. Verify that the property exists.
PS C:\Evidence> $p[1].Owner
NT AUTHORITY\SYSTEM
PS C:\Evidence> Set-StrictMode -Off
Under strict mode, reading User on the System process raised an error naming the missing property, while Owner returned NT AUTHORITY\SYSTEM as usual. Strict mode also catches uninitialized variables, another very common source of quietly wrong scripts in investigation work.
A variable misspelled once, $procs in one line and $proc in the next, is a classic: without strict mode the second is simply empty, and the script carries on with nothing.
Strict mode applies to the scope it is set in and below, so a script can switch it on at its top without changing your console. Here it was switched on and off again at the prompt only to show the difference; in a script, one line at the top is enough.
It is worth using in tools you write, from Module 11 onward. It does not change Where-Object's behavior with a property name given as a parameter, so Get-Member before filtering remains the habit that matters most.
Strict mode has versions because each adds checks: Version 1.0 catches uninitialized variables, 2.0 adds missing properties and some call mistakes, 3.0 and Latest add out-of-range array indexes. Latest means the newest rules of the PowerShell running the script, so a script written under one version may meet stricter rules under a newer one, which is a reason some tool authors pin a specific version.
Discovery, Checked
five factsThe facts of this lesson as checks: your function's help is read by Get-Help, iex expands to Invoke-Expression, Owner is a real property, six commands act on processes, and exactly one rundll32 is a user-owned orphan:
PS C:\Evidence> $p = Get-Content ./procs/proc-sharma.json -Raw | ConvertFrom-Json
PS C:\Evidence> function Get-UserOrphan {
>> <#
>> .SYNOPSIS
>> Lists user-owned processes whose parent is not in the same export.
>> .PARAMETER Path
>> A process export written with ConvertTo-Json.
>> .EXAMPLE
>> Get-UserOrphan -Path ./procs/proc-sharma.json
>> #>
>> param([Parameter(Mandatory)][string]$Path)
>> $all = Get-Content $Path -Raw | ConvertFrom-Json
>> $all | Where-Object { $_.Owner -notlike '*SYSTEM' -and $_.ParentProcessId -notin $all.ProcessId -and $_.Name -ne 'System Idle Process' }
>> }
PS C:\Evidence> @(
>> (Get-Help Get-UserOrphan).Synopsis -like "Lists user-owned*"
>> (Get-Alias iex).Definition -eq "Invoke-Expression"
>> ($p | Get-Member -MemberType NoteProperty).Name -contains "Owner"
>> @(Get-Command -Noun Process).Count -eq 6
>> @(Get-UserOrphan -Path ./procs/proc-sharma.json | Where-Object Name -eq "rundll32.exe").Count -eq 1
>> )
True
True
True
True
True
Five True. None of the five depended on any documentation outside the running session. The fifth ties discovery back to evidence: the function written with help in Section 2 found the same orphaned rundll32 that Module 11 scored as High.
Each came from asking the session, which is the theme of the lesson: the four discovery commands make PowerShell explain itself, so the answer you act on is true of the version, modules and objects actually in front of you, not of a blog post written for a different one.
With the shell understood and the discovery commands in hand, the next lesson turns to why PowerShell matters so much to defenders in the first place: it is the administrator's most powerful tool, and for the same reasons, an attacker's.
Practice
- Search. Find commands with Get-Command by verb, noun or wildcard.
- Read. Read syntax and parameter sets before guessing at parameters; install full help with Update-Help.
- Inspect. Run Get-Member before filtering on a property you have not confirmed.
- Expand. Look up every unfamiliar short name with Get-Alias before judging a line.
Then pick a command you have never used before, and learn to use it with only Get-Command -Syntax, Get-Help and Get-Member on its output, without searching the web.
Next: PowerShell as an administrator's tool and as an attacker's tool.