Pester Testing Self Contained Scripts

Words: 1009

Time to read: ~ 5 minutes

    2019-02-22: Added a test for Jakub’s (don’t freak out, don’t freak out) comment.
    2019-06-13: Check out Jakub’s post on the topic – Such elegance

I’m going to (optimistically) say that you are all Pester testing your scripts.

It makes me feel better to believe that. If you aren’t, then you don’t even realise the amount of mental energy that you are spending trying to keep track of thoughts like this:

“oh can’t touch that in case that breaks that which would break this and feck up that…”

Once you start Pester-ing your tests, it gets easier since you know that any unwanted bugs introduced by your changes will be captured and raised up to you.

So you code along, happy in the knowledge that you can focus on the problem and free up some mental memory to work on the problem at hand.

The first few lines of a Pester “.tests.ps1” file…

…follow the same format:

$here = Split-Path Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
. "$here\$sut"

This is known as “dot-sourcing” the file. 

This normally isn’t a problem since the agreed best practice way of writing functions is to have a single file that just defines the function (preferably in a module) e.g. Do-Something.ps1.
That way you can dot-source the function . .\Do-Something.ps1 and then you can call the function normally from the console e.g. Do-Something -ToThis 'SQL Server'.

Where things differ…

…could be when you try to accommodate different people and create a .ps1 file that both defines and calls a function. Self Contained scripts, if you would call them that.

Normally the reason that I’ve heard from this is you’re trying to help a non-technical minded person and they just want a file that they can open, hit “run”, and everything is done for them.

Have you ever tried to Pester test those files though? It’s not recommended, especially if your function removes or modifies objects.

It’s pretty hard to test something when it does the work when you try and load it in…


I was watching the video of PSPowerHour that was done on the 18th Feb 2019 and one of the videos was from Jakub Jares ( blog | twitter ) on testing self contained scripts.

If there is a video about Pester from Jakub Jares, then I recommend you watch it. Here’s a link to the part I was watching.

It’s impressive! Yet after the part about Aliases it got to a level that I just haven’t been able to reach yet. Plus I’m pretty sure that if I use Invoke-Expression in my scripts then Joel Sallow ( blog | twitter ) will have very strong words with me…

Whether for good or for bad, I have my own way of testing my self contained scripts.

I use the Abstract Syntax Tree (AST).


Ah yes, the Abstract Syntax Tree! Honestly, I only know enough to know how to get what I want from it. It’s on my list of things to learn and get more comfortable with but then so is basically everything.

I’m still working on getting 30 hour days first 🙁

In case, you don’t know or want to figure out the AST then don’t worry! As most things the PowerShell community has you covered.

Mike Robbins ( blog | twitter ) has a great blog about getting into it and Chris Dent ( github ) has a function that does nearly all the work for you! Get-FunctionInfo

using namespace System.Management.Automation
using namespace System.Management.Automation.Language
using namespace System.Reflection
function Get-FunctionInfo {
Get an instance of FunctionInfo.
FunctionInfo does not present a public constructor. This function calls an internal / private constructor on FunctionInfo to create a description of a function from a script block or file containing one or more functions.
.PARAMETER IncludeNested
By default functions nested inside other functions are ignored. Setting this parameter will allow nested functions to be discovered.
The path to a file containing one or more functions.
.PARAMETER ScriptBlock
A script block containing one or more functions.
Get-ChildItem -Filter *.psm1 | Get-FunctionInfo
Get all functions declared within the *.psm1 file and construct FunctionInfo.
Get-ChildItem C:\Scripts -Filter *.ps1 -Recurse | Get-FunctionInfo
Get all functions declared in all ps1 files in C:\Scripts.
Change log:
10/12/2015 – Chris Dent – Improved error handling.
28/10/2015 – Chris Dent – Created.
[CmdletBinding(DefaultParameterSetName = 'FromPath')]
param (
[Parameter(Position = 1, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'FromPath')]
[Parameter(ParameterSetName = 'FromScriptBlock')]
begin {
$executionContextType = [PowerShell].Assembly.GetType('System.Management.Automation.ExecutionContext')
$constructor = [FunctionInfo].GetConstructor(
[BindingFlags]'NonPublic, Instance',
[CallingConventions]'Standard, HasThis',
([String], [ScriptBlock], $executionContextType),
process {
if ($pscmdlet.ParameterSetName -eq 'FromPath') {
try {
$scriptBlock = [ScriptBlock]::Create((Get-Content $Path Raw))
} catch {
$ErrorRecord = @{
Exception = $_.Exception.InnerException
ErrorId = 'InvalidScriptBlock'
Category = 'OperationStopped'
Write-Error @ErrorRecord
if ($scriptBlock) {
$scriptBlock.Ast.FindAll( {
param( $ast )
$ast -is [FunctionDefinitionAst]
) | ForEach-Object {
try {
$internalScriptBlock = $_.Body.GetScriptBlock()
} catch {
Write-Debug $_.Exception.Message
if ($internalScriptBlock) {
$constructor.Invoke(([String]$_.Name, $internalScriptBlock, $null))

I saved the above into a file called Get-FunctionInfo.ps1, because consistency is important, and now I can use that to help solve our little “testing self contained scripts” problem.

Our Self Contained Script

Let’s say we have a self contained script like the one below, saved in the file Get-Name.ps1.

function Get-Name {
        [String]$Name = 'you'

    'Hello, {0}' -f $Name

Get-Name -Name Shane

Now if we were to load Chris’ Get-FunctionInfo script, and run it against our function, we get…

# dot source the script to use it
. .\Get-FunctionInfo.ps1

Get-Function -Path .\Get-Name.ps1

Don’t worry, there’s more information then that available. Let’s save the information in a variable and run Get-Member on it.

$fInfo = Get-FunctionInfo -Path .\Get-Name.ps1
$fInfo | Get-Member
So much more!

We take a quick look at the Definition property and we see something very familiar.

Our function!

So we have our function definition but how to add it to our scope? Personally I use the below

# I'm going to add a "2" after the name to show it's ours!
$FakeFunction = "function $($fInfo.Name)2 { $($fInfo.Definition) }
$OurFakeFunction = [scriptblock]::create($FakeFunction)

# Now we can dot-source our variable
. $OurFakeFunction

Now our Get-Name2 runs and works just like our original script. Lets Pester test this now.

Now we can test it!

Let’s run a simple test in a brand new window to prove there’s no funny business.

describe 'testing our vGet-Name' {
     # Get our self containted script info
     . .\Get-FunctionInfo.ps1
     $fInfo = Get-FunctionInfo -Path .\Get-Name.ps1

     # Create something we can dot-source
     $Fake = "function $($fInfo.Name) { $($fInfo.Definition) }"
     $FakeFunc = [scriptblock]::Create($Fake)
     . $FakeFunc

     Context 'Results' {
         it 'should have the expected default result' {
             Get-Name | Should -Be 'Hello, you'

         it 'should have the expected result for passed in values' {
             Get-Name -Name 'Shane' | Should -Be 'Hello, Shane'         

UPDATE: 2019-02-22.

Jakub added a comment to this post saying that 1). he’s going to work his presentation into a module, and 2). …

I like your approach with ast as well, but I think it won’t work when you use $PSScriptRoot in your script.

Jakub Jares

So in the interest of completeness, I wanted to test it out and add it here. First of all, we have to add the $PSScriptRoot to our script.

function Get-Name {
[String]$Name = 'you'
'Hello, {0}. Script root is {1}' -f $Name, $PSScriptRoot
Get-Name Name Shane
view raw Get-Name.ps1 hosted with ❤ by GitHub

I’ve gone back to gists after seeing the default code formatter and having the automatic response of “eww”. 😐

Running this shows that the $PSScriptRoot variable works.

I already knew it was there…I typed it in…

Now let’s see what happens when we try to use our testing workaround with this function…

describe 'testing our function with scriptroot' {
# Get our self contained script info.
. .\Get-FunctionInfo.ps1
$fInfo = Get-FunctionInfo Path .\Get-NameScriptBlock.ps1
# Create something we can dot-source.
$Fake = "function $($fInfo.Name) { $($fInfo.Definition) }"
$FakeFunc = [scriptblock]::Create($Fake)
. $FakeFunc
Context 'Results' {
It 'should have the expected default result' {
Get-Name | Should Be 'Hello, you. Script root is C:\Users\shane.oneill\Git\Blog'
It 'should have the expected result for passed in values' {
Get-Name Name 'Shane' | Should Be 'Hello, Shane. Script root is C:\Users\shane.oneill\Git\Blog'

I call that a whole load of “nope”

And after that test, we’ve proved that Jakub was right!

Did you expect otherwise?

Now if you’ll excuse me…

… apparently I have to go learn a lot more about Scopes and Session Contexts and then re-watch Jakub’s video!

Author: Shane O'Neill

DBA, T-SQL and PowerShell admirer, Food, Coffee, Whiskey (not necessarily in that order)...

7 thoughts on “Pester Testing Self Contained Scripts”

  1. param([switch]$Testing)

    function Get-Name {
    [String]$Name = ‘you’

    ‘Hello, {0}’ -f $Name

    if(!$Testing){Get-Name -Name Shane}

  2. Thanks for the article 🙂 That invoke expression is just for the demo, in real life you’d use just an empty function. I’ll clean it up and publish it as a module. And add it to Pester v5. It already does a similar thing with Add-Dependency.

    I like your approach with ast as well, but I think it won’t work when you use $PSScriptRoot in your script.

Leave a Reply

%d bloggers like this: