Getting started with Powershell
This is a blog that was born out of polishing part of my personal notes on powershell while learning it for Windows Security. This will cover the basics briefly, and the idea is to get you quickly started in learning the red-teaming and blue-teaming tactics via Powershell. If you have a good amount of programming experience, the basic commands would be pretty intuitive. If that is the case with you , skip to the “advanced functions and cmdlet” section
I have found that powershell is just one of those languages which when I come back to it, I tend to forget a good chunk of how things work — Hence I wrote this to serve as my quick reference and help others
Overview
Powershell is a powerful shell and scripting language present on Windows 7 and above. Based on the .NET framework, and trusted by countermeasures (AV solutions) and administrators. It can and has been often used by threat actors for nefarious purposes in the wild. It is a quintessential tool to know when pentesting windows systems and AD environments.
Understanding the basics of powershell, and powershell pentesting is one of the key cornerstones of windows security of systems starting from Windows 7 from both red and blue team security perspectives.
1. Basic commands
- Help command
Get-Help *regex
- Get-command (commands installed on system)
Get-Help Get-command
Get-Command
Lists Commands installed on the system. (Usually a large list of commands)Get-Help {Command} -Parameter * | more
Lists parameters required for commandGet-Command -CommandType cmdlet -Name *process*
Commands which are of type cmdlet which have process in their nameGet-Command -CommandType cmdlet | Measure-Object
Count the no of cmdlet commandsGet-Command -Verb stop
Search for verb stop in commands - Cmdlet is often used for powershell commands. According to microsoft , A cmdlet is a lightweight command that is used in the PowerShell environment. The PowerShell runtime invokes these cmdlets within the context of automation scripts that are provided at the command line. The PowerShell runtime also invokes them programmatically through PowerShell APIs.
- Get-Process/Get-Service Get running processes and services.
- Start/Stop process
Start-process
Get-process lssass | stop-process
Some powershell functions return objects instead of strings, so the output of 'Get-process' can directly be passed on to stop-process (in bash, we would need to cut,awk or parse the value id out of the output before being passed) - Output formatting
Get-ChildItem | Format-table *
Format output to a tableGet-ChildItem | Format-List *
formats the objects returned into a list (more-detailed)Get-Command -CommandType cmdlet -Name Format*
List commands related to formattingGet-Command -CommandType cmdlet -Name Out*
List commands related to output (redirecting output)Get-Process | Out-File -FilePath C:\Users\Admin\test1.txt
(redirect output to a file)
Overview of Operators
- Arithmetic:
_,-,*,%
- Assignment:
=,+=,-=,*=,/=,%=
- Comparison:
-ne,-eq,-gt,-lt,-ge,-le,-match,-notmatch,-like,-notlike,-in,-notin,-contains,-notcontains
(Note that -eq does a case insensitive comparison of two strings, -ceq is the string check for the case sensitive strings) - Redirection:
>,>>,2>,2>&1
- String:
-replace,-join,-split
When using double quote, it will expand variables inside string while single quote does not.
$str="Hello"
$str1="Bye"
"Hey, $str"
'Hey, $str1'
- Logical:
-and, -or,-xor,-not
- Type:
3-is "int"
(you may use "float","string") to check for types. Other operators ->isnot, as
(as is used for reassigning type/typecasting) Variable assessment:
$a = 3.2+3
$a.gettype
$axobj = 1,2,3,4,5 #declaring array
$axobj[0]
- Conditional operators:
if,elseif,else
if ( ((Get-Process).Count) -gt 40) {"More than 40 processes"} else {"40 or less processes"}
- switch (supports operators like
wildcard,regex and exact
)
switch(1) {1 {"ONE"}} 2 {"Two"}}
switch -wildcard ('abc') {ab* {"Yep"} bcdefg {"Nope"}}
- Loops:
While loop
$count=3
while ($count -ge 0)
{
">> $count"
$count -
}
- For Loop
$processes=Get-Process
foreach($i in $processes)
{
$i.Name
}
Get-Process | Foreach-Object {$_.name}
2. Functions, Advanced Functions and Cmdlets
Let us take a glance at the basics of powershell functions.
The powershell function definitions are pretty straightforward, and we can define switchable and multi-variable argument functions as well. The call site, or how the functions are called can take a bit of getting used to.
2.1 Functions
Defining a function that takes no arguments, and returns the sum of 2 numbers.
function add {1+2}
add
Defining a function that take two arguments and writes the output. Note how the function is called, the arguments are not enclosed within a parentheses but separated by space. Being a parsed and interpreted language, the powershell treats parentheses as a control structure. Parentheses when put next to a function are treated as an array foo (1,2)
is treated as an array being parsed to foo function with 2 elements in it.
function foo ($n1,$n2) {$n3=$n1+$n2
Write-Output $n3}
foo -n1 10 -n2 20
foo 30 40 // this works as well
Variable args
> function vararg($n1,$n2) { $n1, $n2, $args}
> vararg "Wubba" "Lubba" "dub" "Dub"
A note on Execution Policy: A policy to prevent users from accidentally executing powershell scripts. It is not a security feature. Microsoft states it and also on the cmdline help. It can be easily disabled. Restricted is the default execution policy , and it can have other values.
Advanced Functions
More on advanced function parameters can be found here .
Switchable
These don’t convey any parameter value. The value they convey to the functions is through their presence. If they are present, they convey a boolean value of true, and false otherwise.
function switchy ($n1,$n2,[switch]$on)
{
$c=$n1 + $n2
if ($on){$n1*$n2}
else { "$c"}}
}
PS C:\Users\Administrator> switchy 20 21
41
PS C:\Users\Administrator> switchy 20 21 -on
420
Making arguments mandatory
function fooBar {
param (
[Parameter (Mandatory =$True)]
$n1,
[Parameter ()]
$n2
)
Write-Output "$n1"
Write-Output "$n2"
}
To execute a script, first load it using . .\{name-of-file}.ps1
and then execute the function fooBar
Redefining positions You can make position assignment (only valid where arguments supplied are unnamed) fooBar 1 2
. If you specify name using -n1 1 -n2 2
, the position won't matter.
function fooBar {
param (
[Parameter (Mandatory =$True, Position=2)]
$n1,
[Parameter (Position=1)]
$n2
)
Write-Output "$n1"
Write-Output "$n2"
}
Pipelining arguments
We can supply arguments to our function using pipeline command. This will ensure that the commands are taken from the pipeline. If not named, we can only supply 1 argument at a time. The argument supplied by pipeline is appended to the end of your other arguments. Take a look at the function below.
function fooBar {
param ([Parameter (Mandatory =$True, ValueFromPipeline=$True)] $n1, [Parameter (ValueFromPipeline=$True)] $n2)
Write-Output "$n1"
Write-Output "$n2"
}
Output:
PS > 30 | fooBar 34
34
30
PS> 30 | fooBar -n2 34
30
34
‘Overloading’ Functions
Powershell sorta lets you overload functions (loosely, if we’re going by strict definition you can’t). The definition of function overloading is having ‘same’ function names with different parameter sets.
You can put Param n1 and n2 in different parameter sets , and they will be treated differently. Note that even though n1 is mandatory, it is not prompted as n2 is in a different parameter set now
function fooBar {
param ([Parameter (Mandatory =$True, ValueFromPipeline=$True, ParameterSetName="Set1")] $n1, [Parameter (ValueFromPipeline=$True, ParameterSetName="Set2")] $n2)
Write-Output "$n1"
Write-Output "$n2"
}
Output:
PS :> fooBar -n2 32
32
Another example (taken from stackoverflow)
# advanced function with 3 parameter sets
function Backup-UsersData
(
[Parameter(Position=0, ParameterSetName="user")]
[string]$user,
[Parameter(Position=0, ParameterSetName="array")]
[object[]]$array,
[Parameter(Position=0, ParameterSetName="all")]
[switch]$all
)
{
# use this to get the parameter set name
$PSCmdlet.ParameterSetName
}
Output
# test
Backup-UsersData -user 'John'
Backup-UsersData 1, 2
Backup-UsersData -all
1.2 Cmdlets and Cmdlet Binding
Cmdlets are like shorter version of commands used within the powershell environment. They return a .net object and partake in the pipeline design of the language and pass the object to the next cmdlet in the pipeline.
The CmdletBinding
attribute turns the vanilla PowerShell functions into advanced functions. The major changes from the vanilla functions include:
- Using $PSCmdlet automatic variable within function but $Args can no longer be used
- Unknown parameters will not be supported
- Pipeline processing “|” operator support (.NET object returned)
Creating custom cmdlet can be useful as it can grant access to certain extra functions. As a best practice we should name our functions in the cmdlet naming conventions like the ones we have been using so far. We can bind certain cmdlets which can give access to certain functions and features. One example being the SupportsShouldProcess, which allows us to understand what the program will do if run when supplied with the -WhatIf
Parameter and the -Confirm
parameter
Note that the cmdlet binding must happen before the parameter definition
function RemovePath-Custom
{
[CmdletBinding(SupportsShouldProcess= $True)]
param([Parameter()] $FilePath)
Write-Verbose "Deleting $FilePath"
Remove-Item $FilePath
}
We can now use the -WhatIf parameter with the cmdlet we created which will let us know what the outcome of running our script will be.
RemovePath-Custom -WhatIf .\text.txt
What if: Performing the operation "Remove File" on target "C:\Users\sample1\text.txt".
Coding an utility
Using our knowledge so far, we can program a utility. The utility below does have components which require some knowledge of AD infrastructure.
Sample Code to search for a Hotfix/patch in a workstation in an AD environment
The code checks for Computers in the active directory which have a logon in the last 90 days and then uses it to query it for hotfix. In a newer system, the absence of a hotfix doesn’t mean a vulnerability, it just means that the patch was rolled out with a major OS update instead of being a hotfix.
- The code defines a cutoff Date by subtracting 90 days from the current date. This value is later stored in a filter string which will be used with the cmdlet Get-ADComputer
- Get-ADComputer supplies the filter string, and looks for 3 values (OS, Description, Lastlogondate) and pipes it to a SELECT command. The select command pipes it to a For-Each Loop. Note that $_ is used to refer to a variable in the pipeline. The For-Each returns the name of the computer and stores it in $computerNames
- A for loop checks these computer names and queries them for the HotFix with a hot fix ID
- A try catch is required, as these computers might be offline (In the AD environment, a way to check that is to see if the RPC-Server is available on that workstation).
function CheckPatch
{
$cutoff = (Get-Date).AddDays(-90)
$filter = "LastLogonDate -gt '$cutoff'"
$computerNames = Get-ADComputer -Filter $filter -Properties OperatingSystem,Description,LastLogonDate |
Select Name,OperatingSystem,Description,LastLogonDate | Foreach-Object {$_.Name}
foreach ($i in $computerNames)
{
try {
$value = Get-HotFix -ComputerName $i | Where-Object -Property HotfixID -EQ "KB2871997"
if ($value)
{
Write-Output "Hotfix found on $i"
}
#$ErrorActionPreference = "Stop"; #Make all errors terminating
else
{
Write-Output "Hotfix not installed for $i"
}
}
catch{
Write-Host "RPC server unavailable for Workstation $i";
Write-Host $Error[0].Exception;
}
}
}
Modules
Microsoft documentation on powershell defines it as “A module is a package that contains PowerShell members, such as cmdlets, providers, functions, workflows, variables, and aliases. People who write commands can use modules to organize their commands and share them with others. People who receive modules can add the commands in the modules to their PowerShell sessions and use them just like the built-in commands.”
Like the familiar language python, powershell modules need to be installed (if not already or default) before they can be imported and used. In the later versions (3.0 onwards) of powershell, once the module is installed in the default module path, powershell will implicitly import the installed module when used. (At the time of me writing this blog, Win 11 uses Powershell version 5.1.x).
Importing Modules
You can write and import your own modules and libraries. Powershell modules have .psm1
extensions. You can copy a ps1 file to a psm1 file to make it behave like a module.
Get-Module -ListAvailable -All
From PSv3 onwards if a module sits in the env: PSModulePath directory there is no need to import that module, it gets auto imported
Import-Module {ModuleName}
Import-Module {Script-path}.psm1
You can see the commands available for a module by
Get-Command -Module {ModuleName or Path}
The above command shows output based on env variable so that might not show
You can change the PSModulePath variable to something where your custom modules are stored. This method however, would only persist in the current session. To make it permanent, change the environment variable via the control panel.
$env:PSModulePath= $env:PSModulePath+";{Path to your ps1 script}"
After specifying the path where to look for modules above, you can import your scripts in that directory by just specifying the name instead of the previously required full path. The module must be inside a parent folder with the same name as the Module file (psm) if you are using the Module name for the import function to work , or else powershell would not be able to find it
Deciding functions to be exported
By default all modules are exported, but that can be changed by adding this to the end of our module file which is covered in the subsequent section.
We can use the function. Once specified, the wildcard expression will determine which functions will be exported . The ones not matching the expression will not be exported and will not be available for use by the user after the module has been imported.
Export-ModuleMember -Function *-*
Manifest
This is used to fill in metadata information and called manifest modules. From the microsoft documentation,”A module manifest is a PowerShell data file (.psd1) that describes the contents of a module and determines how a module is processed. The manifest file is a text file that contains a hash table of keys and values. You link a manifest file to a module by naming the manifest the same as the module, and storing the manifest in the module’s root directory.”
We create a module by using the command. In earlier versions of powershell, some of these parameters could only be supplied via cmdlet argument prompts. In newer versions, we may supply them via cmdline args as well.
New-ModuleManifest .\ManifestForModule.psd1 -Author ysingh -CompanyName EVILCORP -ModuleToProcess .\ModuleFile.psm1
The above command will create a Manifest file for the Module file supplied by the -ModuleToProcess
argument, if there is no module file or you make a typo in the module name , the manifest file will still be created, but the function names and some other details will be left blank. You can test your manifest.
Test-ModuleManifest {name of module file (psd1)}
Make sure that your module manifest is located in the top level of the directory that contains your module.
The Manifest file also allows you to place multiple modules (.psm) files under 1 directory and have them be loaded by the Manifest File. There are other things, such as Nested Modules you can add and ask them to be processed. One thing to note here is that, if your root module has a different name than the folder , make sure you edit the Manifest file to reflect that
You can read more about how to write a manifest file here. TLDR; Manifest files allows you to provide more structure to your module and control over how it is processed.
Jobs
Powershell interactions to setup , create and monitor jobs which can run in the background. Jobs are based on the powershell computing infrastructure. There are several ways in which this can be achieved.
1. Invoke-Command[3] -scriptblock { codeblock/Filename } -asJob -computername localhost
2. Start-Job[2] -scriptblock { code }
3. Start-Process[1] powershell {script}
Invoke-Command starts a new session with the running job. So once this command is used , the current session won’t be able to access it. There are ways to do it, and this is covered later.
Start-Process You must redirect the standard output to a file[1].
Start-Job This creates a new job in the background on a new powershell instance
We are going to be focussing on the new job being run as a background in a new powershell instance.
Basics of Start-Job
Starting one
Start-Job -ScriptBlock {whoami}
Start-Job -FilePath script.ps1
See jobs
You may pipe the job to a Receive-Job to see the output. If you just run without parameters, Get-Job returns all jobs and a tabular output for all the jobs.
Get-Job | Receive-Job
Remove
Get-Job | Remove-job
You may fetch jobs by the id as well
Get-Job -id 4
Remote Jobs
Jobs can be started on remote machines as well.
Some examples to start jobs on remote as well as local machines are shown below
One of the ways to do this is to use the As-Job
parameter when invoking commands.
Invoke-Command
Invoke-Command -Scriptblock {ps} -ComputerName EXC01-ELLIOT -Credential evilcorp\dsinatra
We can start a job using invoke-command, but as mentioned earlier run by Invoke-Command are stateless (run in a new powershell session). So if we start a job , say using the command below, it will be started on the remote pc, but it won’t show in a subsequent Get-Job
as the 2 commands will be in different context
Invoke-Command -ScriptBlock {Start-Job -ScriptBlock{ps}} -Credential evilcorp\dsinatr
2nd command in the image above, will return empty as the 2 Invoke-commands don’t share any context and are stateless.
Sessions
So we can use the New-PSSession
to have states saved. So we can pass the same session in the 2 commands above, and get a response on the running job. Using session we can now track jobs in the same session.
As-Job parameter
We can do the same without using sessions, but using the -As-Job parameter.
Resources
Some great resources I recommend for learning more about powershell from a security standpoint (Besides the microsoft documentation)
- Adam the automator (one stop resource)
- A nice youtube playlist covering the basics
- A youtube playlist on powershell on AD
- @DonJonesConTech on youtube