Getting started with Powershell

The Dark Lord
13 min readSep 3, 2023

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 command Get-Command -CommandType cmdlet -Name *process* Commands which are of type cmdlet which have process in their name Get-Command -CommandType cmdlet | Measure-Object Count the no of cmdlet commands Get-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 table Get-ChildItem | Format-List * formats the objects returned into a list (more-detailed) Get-Command -CommandType cmdlet -Name Format* List commands related to formatting Get-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.

Sessions enable us to track jobs across different powershell instances

As-Job parameter

We can do the same without using sessions, but using the -As-Job parameter.

An easier alternative to using sessions

Resources

Some great resources I recommend for learning more about powershell from a security standpoint (Besides the microsoft documentation)

--

--

The Dark Lord

Computer & N/w security enthusiast, cryptography fanatic. Exploiting things in a dimly lit room.