Unpacking Software Livestream

Join our monthly Unpacking Software livestream to hear about the latest news, chat and opinion on packaging, software deployment and lifecycle management!

Learn More

Chocolatey Product Spotlight

Join the Chocolatey Team on our regular monthly stream where we put a spotlight on the most recent Chocolatey product releases. You'll have a chance to have your questions answered in a live Ask Me Anything format.

Learn More

Chocolatey Coding Livestream

Join us for the Chocolatey Coding Livestream, where members of our team dive into the heart of open source development by coding live on various Chocolatey projects. Tune in to witness real-time coding, ask questions, and gain insights into the world of package management. Don't miss this opportunity to engage with our team and contribute to the future of Chocolatey!

Learn More

Calling All Chocolatiers! Whipping Up Windows Automation with Chocolatey Central Management

Webinar from
Wednesday, 17 January 2024

We are delighted to announce the release of Chocolatey Central Management v0.12.0, featuring seamless Deployment Plan creation, time-saving duplications, insightful Group Details, an upgraded Dashboard, bug fixes, user interface polishing, and refined documentation. As an added bonus we'll have members of our Solutions Engineering team on-hand to dive into some interesting ways you can leverage the new features available!

Watch On-Demand
Chocolatey Community Coffee Break

Join the Chocolatey Team as we discuss all things Community, what we do, how you can get involved and answer your Chocolatey questions.

Watch The Replays
Chocolatey and Intune Overview

Webinar Replay from
Wednesday, 30 March 2022

At Chocolatey Software we strive for simple, and teaching others. Let us teach you just how simple it could be to keep your 3rd party applications updated across your devices, all with Intune!

Watch On-Demand
Chocolatey For Business. In Azure. In One Click.

Livestream from
Thursday, 9 June 2022

Join James and Josh to show you how you can get the Chocolatey For Business recommended infrastructure and workflow, created, in Azure, in around 20 minutes.

Watch On-Demand
The Future of Chocolatey CLI

Livestream from
Thursday, 04 August 2022

Join Paul and Gary to hear more about the plans for the Chocolatey CLI in the not so distant future. We'll talk about some cool new features, long term asks from Customers and Community and how you can get involved!

Watch On-Demand
Hacktoberfest Tuesdays 2022

Livestreams from
October 2022

For Hacktoberfest, Chocolatey ran a livestream every Tuesday! Re-watch Cory, James, Gary, and Rain as they share knowledge on how to contribute to open-source projects such as Chocolatey CLI.

Watch On-Demand

Downloads:

320,534

Downloads of v 4.2.0-alpha2:

435

Last Update:

12 Dec 2017

Package Maintainer(s):

Software Author(s):

  • Pester Team

Tags:

powershell unit testing bdd tdd mocking admin

Pester

This is a prerelease version of Pester.

  • 1
  • 2
  • 3

4.2.0-alpha2 | Updated: 12 Dec 2017

Downloads:

320,534

Downloads of v 4.2.0-alpha2:

435

Maintainer(s):

Software Author(s):

  • Pester Team

Pester 4.2.0-alpha2

This is a prerelease version of Pester.

Legal Disclaimer: Neither this package nor Chocolatey Software, Inc. are affiliated with or endorsed by Pester Team. The inclusion of Pester Team trademark(s), if any, upon this webpage is solely to identify Pester Team goods or services and not for commercial purposes.

  • 1
  • 2
  • 3

Some Checks Have Failed or Are Not Yet Complete

Not All Tests Have Passed


Validation Testing Failed


Verification Testing Passed

Details

Scan Testing Successful:

No detections found in any package files

Details
Learn More

Deployment Method: Individual Install, Upgrade, & Uninstall

To install Pester, run the following command from the command line or from PowerShell:

>

To upgrade Pester, run the following command from the command line or from PowerShell:

>

To uninstall Pester, run the following command from the command line or from PowerShell:

>

Deployment Method:

NOTE

This applies to both open source and commercial editions of Chocolatey.

1. Enter Your Internal Repository Url

(this should look similar to https://community.chocolatey.org/api/v2/)


2. Setup Your Environment

1. Ensure you are set for organizational deployment

Please see the organizational deployment guide

2. Get the package into your environment

  • Open Source or Commercial:
    • Proxy Repository - Create a proxy nuget repository on Nexus, Artifactory Pro, or a proxy Chocolatey repository on ProGet. Point your upstream to https://community.chocolatey.org/api/v2/. Packages cache on first access automatically. Make sure your choco clients are using your proxy repository as a source and NOT the default community repository. See source command for more information.
    • You can also just download the package and push it to a repository Download

3. Copy Your Script

choco upgrade pester -y --source="'INTERNAL REPO URL'" --version="'4.2.0-alpha2'" --prerelease [other options]

See options you can pass to upgrade.

See best practices for scripting.

Add this to a PowerShell script or use a Batch script with tools and in places where you are calling directly to Chocolatey. If you are integrating, keep in mind enhanced exit codes.

If you do use a PowerShell script, use the following to ensure bad exit codes are shown as failures:


choco upgrade pester -y --source="'INTERNAL REPO URL'" --version="'4.2.0-alpha2'" --prerelease
$exitCode = $LASTEXITCODE

Write-Verbose "Exit code was $exitCode"
$validExitCodes = @(0, 1605, 1614, 1641, 3010)
if ($validExitCodes -contains $exitCode) {
  Exit 0
}

Exit $exitCode

- name: Install pester
  win_chocolatey:
    name: pester
    version: '4.2.0-alpha2'
    source: INTERNAL REPO URL
    state: present
    allow_prerelease: yes

See docs at https://docs.ansible.com/ansible/latest/modules/win_chocolatey_module.html.


chocolatey_package 'pester' do
  action    :install
  source   'INTERNAL REPO URL'
  version  '4.2.0-alpha2'
  options  '--prerelease'
end

See docs at https://docs.chef.io/resource_chocolatey_package.html.


cChocoPackageInstaller pester
{
    Name        = "pester"
    Version     = "4.2.0-alpha2"
    Source      = "INTERNAL REPO URL"
    chocoParams = "--prerelease"
}

Requires cChoco DSC Resource. See docs at https://github.com/chocolatey/cChoco.


package { 'pester':
  ensure          => '4.2.0-alpha2',
  install_options => ['--prerelease'],
  provider        => 'chocolatey',
  source          => 'INTERNAL REPO URL',
}

Requires Puppet Chocolatey Provider module. See docs at https://forge.puppet.com/puppetlabs/chocolatey.


4. If applicable - Chocolatey configuration/installation

See infrastructure management matrix for Chocolatey configuration elements and examples.

WARNING

This package is exempt from moderation. While it is likely safe for you, there is more risk involved.

Description

Pester provides a framework for running BDD style Tests to execute and validate PowerShell commands inside of PowerShell and offers a powerful set of Mocking Functions that allow tests to mimic and mock the functionality of any command inside of a piece of PowerShell code being tested. Pester tests can execute any command or script that is accessible to a pester test file. This can include functions, Cmdlets, Modules and scripts. Pester can be run in ad hoc style in a console or it can be integrated into the Build scripts of a Continuous Integration system.


chocolateyInstall.ps1
[CmdletBinding()]
param ( )

end
{
    $modulePath      = Join-Path -Path $env:ProgramFiles -ChildPath WindowsPowerShell\Modules
    $targetDirectory = Join-Path -Path $modulePath -ChildPath Pester
    $scriptRoot      = Split-Path -Path $MyInvocation.MyCommand.Path -Parent
    $sourceDirectory = Join-Path -Path $scriptRoot -ChildPath Tools

    if ($PSVersionTable.PSVersion.Major -ge 5)
    {
        $manifestFile    = Join-Path -Path $sourceDirectory -ChildPath Pester.psd1
        $manifest        = Test-ModuleManifest -Path $manifestFile -WarningAction Ignore -ErrorAction Stop
        $targetDirectory = Join-Path -Path $targetDirectory -ChildPath $manifest.Version.ToString()
    }

    Update-Directory -Source $sourceDirectory -Destination $targetDirectory

    $binPath = Join-Path -Path $targetDirectory -ChildPath bin
    Install-ChocolateyPath $binPath

    if ($PSVersionTable.PSVersion.Major -lt 4)
    {
        $modulePaths = [Environment]::GetEnvironmentVariable('PSModulePath', 'Machine') -split ';'
        if ($modulePaths -notcontains $modulePath)
        {
            Write-Verbose -Message "Adding '$modulePath' to PSModulePath."

            $modulePaths = @(
                $modulePath
                $modulePaths
            )

            $newModulePath = $modulePaths -join ';'

            [Environment]::SetEnvironmentVariable('PSModulePath', $newModulePath, 'Machine')
            $env:PSModulePath += ";$modulePath"
        }
    }
}

begin
{
    function Update-Directory
    {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            [string] $Source,

            [Parameter(Mandatory = $true)]
            [string] $Destination
        )

        $Source = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Source)
        $Destination = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Destination)

        if (-not (Test-Path -LiteralPath $Destination))
        {
            $null = New-Item -Path $Destination -ItemType Directory -ErrorAction Stop
        }

        try
        {
            $sourceItem = Get-Item -LiteralPath $Source -ErrorAction Stop
            $destItem = Get-Item -LiteralPath $Destination -ErrorAction Stop

            if ($sourceItem -isnot [System.IO.DirectoryInfo] -or $destItem -isnot [System.IO.DirectoryInfo])
            {
                throw 'Not Directory Info'
            }
        }
        catch
        {
            throw 'Both Source and Destination must be directory paths.'
        }

        $sourceFiles = Get-ChildItem -Path $Source -Recurse |
                       Where-Object -FilterScript { -not $_.PSIsContainer }

        foreach ($sourceFile in $sourceFiles)
        {
            $relativePath = Get-RelativePath $sourceFile.FullName -RelativeTo $Source
            $targetPath = Join-Path -Path $Destination -ChildPath $relativePath

            $sourceHash = Get-FileHash -Path $sourceFile.FullName
            $destHash = Get-FileHash -Path $targetPath

            if ($sourceHash -ne $destHash)
            {
                $targetParent = Split-Path -Path $targetPath -Parent

                if (-not (Test-Path -Path $targetParent -PathType Container))
                {
                    $null = New-Item -Path $targetParent -ItemType Directory -ErrorAction Stop
                }

                Write-Verbose -Message "Updating file $relativePath to new version."
                Copy-Item -Path $sourceFile.FullName -Destination $targetPath -Force -ErrorAction Stop
            }
        }

        $targetFiles = Get-ChildItem -Path $Destination -Recurse |
                       Where-Object -FilterScript { -not $_.PSIsContainer }

        foreach ($targetFile in $targetFiles)
        {
            $relativePath = Get-RelativePath $targetFile.FullName -RelativeTo $Destination
            $sourcePath = Join-Path -Path $Source -ChildPath $relativePath

            if (-not (Test-Path $sourcePath -PathType Leaf))
            {
                Write-Verbose -Message "Removing unknown file $relativePath from module folder."
                Remove-Item -LiteralPath $targetFile.FullName -Force -ErrorAction Stop
            }
        }

    }

    function Get-RelativePath
    {
        param ( [string] $Path, [string] $RelativeTo )
        return $Path -replace "^$([regex]::Escape($RelativeTo))\\?"
    }

    function Get-FileHash
    {
        param ([string] $Path)

        if (-not (Test-Path -LiteralPath $Path -PathType Leaf))
        {
            return $null
        }

        $item = Get-Item -LiteralPath $Path
        if ($item -isnot [System.IO.FileSystemInfo])
        {
            return $null
        }

        $stream = $null

        try
        {
            $sha = New-Object -TypeName System.Security.Cryptography.SHA256CryptoServiceProvider
            $stream = $item.OpenRead()
            $bytes = $sha.ComputeHash($stream)
            return [convert]::ToBase64String($bytes)
        }
        finally
        {
            if ($null -ne $stream) { $stream.Close() }
            if ($null -ne $sha)    { $sha.Clear() }
        }
    }
}
tools\bin\pester.bat
@echo off
SET DIR=%~dp0%
SET ARGS=%*
if NOT '%1'=='' SET ARGS=%ARGS:"=\"%
if '%1'=='/?' goto usage
if '%1'=='-?' goto usage
if '%1'=='?' goto usage
if '%1'=='/help' goto usage
if '%1'=='help' goto usage

@PowerShell -NonInteractive -NoProfile -ExecutionPolicy Bypass -Command ^
 "& Import-Module '%DIR%..\Pester.psm1';  & { Invoke-Pester -EnableExit %ARGS%}"

goto finish
:usage
if NOT '%2'=='' goto help

echo To run pester for tests, just call pester or runtests with no arguments
echo.
echo Example: pester
echo.
echo For Detailed help information, call pester help with a help topic. See
echo help topic about_Pester for a list of all topics at the end
echo.
echo Example: pester help about_Pester
echo.
goto finish

:help
@PowerShell -NonInteractive -NoProfile -ExecutionPolicy Bypass -Command ^
  "& Import-Module '%DIR%..\Pester.psm1'; & { Get-Help %2}"

:finish
exit /B %errorlevel%
tools\en-US\about_BeforeEach_AfterEach.help.txt
TOPIC
    about_BeforeEach_AfterEach

SHORT DESCRIPTION
    Describes the BeforeEach and AfterEach commands, which run a set of commands that you specify
    before or after every It block.

LONG DESCRIPTION
    The the BeforeEach and AfterEach commands in the Pester module let you define setup and teardown
    tasks that are performed at the beginning and end of every It block. This can eliminate duplication of code
    in test scripts, ensure that each test is performed on a pristine state regardless of their
    order, and perform any necessary clean-up tasks after each test.

    BeforeEach and AfterEach blocks may be defined inside of any Describe or Context. If they
    are present in both a Context and its parent Describe, BeforeEach blocks in the Describe scope
    are executed first, followed by BeforeEach blocks in the Context scope. AfterEach blocks are
    the reverse of this, with the Context  AfterEach blocks executing before Describe.

    The script blocks assigned to BeforeEach and AfterEach are dot-sourced in the Context or Describe
    which contains the current It statement, so you don't have to worry about the scope of variable
    assignments. Any variables that are assigned values within a BeforeEach block can be used inside
    the body of the It block.

    BeforeAll and AfterAll are used the same way as BeforeEach and AfterEach, except that they are
    executed at the beginning and end of their containing Describe or Context block.  This is
    essentially syntactic sugar for the following arrangement of code:

      Describe 'Something' {
        try
        {
            <BeforeAll Code Here>

            <Describe Body>
        }
        finally
        {
            <AfterAll Code Here>
        }
      }


  SYNTAX AND PLACEMENT
    Unlike most of the commands in a Pester script, BeforeEach, AfterEach, BeforeAll and AfterAll blocks
    apply to the entire Describe or Context scope in which they are defined, regardless of the order of
    commands inside the Describe or Context. In other words, even if an It block appears before BeforeEach
    or AfterEach in the tests file, the BeforeEach and AfterEach will still be executed.  Likewise, BeforeAll
    code will be executed at the beginning of a Context or Describe block regardless of where it is found,
    and AfterAll code will execute at the end of the Context or Describe.


  EXAMPLES
    Describe 'Testing BeforeEach and AfterEach' {
        $afterEachVariable = 'AfterEach has not been executed yet'

        It 'Demonstrates that BeforeEach may be defined after the It command' {
            $beforeEachVariable | Should -Be 'Set in a describe-scoped BeforeEach'
            $afterEachVariable  | Should -Be 'AfterEach has not been executed yet'
            $beforeAllVariable  | Should -Be 'BeforeAll has been executed'
        }

        It 'Demonstrates that AfterEach has executed after the end of the first test' {
            $afterEachVariable | Should -Be 'AfterEach has been executed'
        }

        BeforeEach {
            $beforeEachVariable = 'Set in a describe-scoped BeforeEach'
        }

        AfterEach {
            $afterEachVariable = 'AfterEach has been executed'
        }

        BeforeAll {
            $beforeAllVariable = 'BeforeAll has been executed'
        }
      }

SEE ALSO
    about_Pester
    about_Should
    about_Mocking
    about_TestDrive
    about_about_Try_Catch_Finally
    Describe
    Context
    Should
    It
    Invoke-Pester
tools\en-US\about_Mocking.help.txt
TOPIC
    about_Mocking

SHORT DESCRIPTION
    Pester provides a set of Mocking functions making it easy to fake dependencies
    and also to verify behavior. Using these mocking functions can allow you to
    "shim" a data layer or mock other complex functions that already have their
    own tests.

LONG DESCRIPTION
    With the set of Mocking functions that Pester exposes, one can:

        - Mock the behavior of ANY PowerShell command.
        - Verify that specific commands were (or were not) called.
        - Verify the number of times a command was called with a set of specified
    parameters.

  MOCKING FUNCTIONS
  For detailed information about the functions in the Pester module, use Get-Help.

  Mock
    Mocks the behavior of an existing command with an alternate
    implementation.

  Assert-VerifiableMock
    Checks if any Verifiable Mock has not been invoked. If so, this will
    throw an exception.

  Assert-MockCalled
    Checks if a Mocked command has been called a certain number of times
    and throws an exception if it has not.

  EXAMPLE
    function Build ($version) {
        Write-Host "a build was run for version: $version"
    }

    function BuildIfChanged {
        $thisVersion = Get-Version
        $nextVersion = Get-NextVersion
        if ($thisVersion -ne $nextVersion) { Build $nextVersion }
        return $nextVersion
    }

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

    Describe "BuildIfChanged" {
      Context "When there are Changes" {
        Mock Get-Version {return 1.1}
        Mock Get-NextVersion {return 1.2}
        Mock Build {} -Verifiable -ParameterFilter {$version -eq 1.2}

        $result = BuildIfChanged

        It "Builds the next version" {
          Assert-VerifiableMock
        }
        It "returns the next version number" {
          $result | Should -Be 1.2
        }
      }
      Context "When there are no Changes" {
        Mock Get-Version { return 1.1 }
        Mock Get-NextVersion { return 1.1 }
        Mock Build {}

        $result = BuildIfChanged

        It "Should not build the next version" {
          Assert-MockCalled Build -Times 0 -ParameterFilter {$version -eq 1.1}
        }
      }
    }


  MOCKING CALLS TO COMMANDS MADE FROM INSIDE SCRIPT MODULES

  Let's say you have code like this inside a script module (.psm1 file):

    function BuildIfChanged {
      $thisVersion = Get-Version
      $nextVersion = Get-NextVersion
      if ($thisVersion -ne $nextVersion) { Build $nextVersion }
      return $nextVersion
    }

    function Build ($version) {
      Write-Host "a build was run for version: $version"
    }

    # Actual definitions of Get-Version and Get-NextVersion are not shown here,
    # since we'll just be mocking them anyway. However, the commands do need to
    # exist in order to be mocked, so we'll stick dummy functions here

    function Get-Version { return 0 }
    function Get-NextVersion { return 0 }

    Export-ModuleMember -Function BuildIfChanged

  Beginning in Pester 3.0, there are two ways to write a unit test for a module that
  mocks the calls to Get-Version and Get-NextVersion from the module's BuildIfChanged
  command. The first is to inject mocks into a module:

  In these examples, the PSM1 file, MyModule.psm1 is installed in $env:PSModulePath on
  the local computer.

    Import-Module MyModule

    Describe "BuildIfChanged" {
      Context "When there are Changes" {
        Mock -ModuleName MyModule Get-Version { return 1.1 }
        Mock -ModuleName MyModule Get-NextVersion { return 1.2 }

        # To demonstrate that you can mock calls to commands other than functions
        # defined in the same module, we'll mock a call to Write-Host.

        Mock -ModuleName MyModule Write-Host {} -Verifiable -ParameterFilter {
            $Object -eq 'a build was run for version: 1.2'
        }

        $result = BuildIfChanged

        It "Builds the next version and calls Write-Host" {
          Assert-VerifiableMock
        }

        It "returns the next version number" {
          $result | Should -Be 1.2
        }
      }

      Context "When there are no Changes" {
        Mock -ModuleName MyModule Get-Version { return 1.1 }
        Mock -ModuleName MyModule Get-NextVersion { return 1.1 }
        Mock -ModuleName MyModule Build { }

        $result = BuildIfChanged

        It "Should not build the next version" {
          Assert-MockCalled Build -ModuleName MyModule -Times 0 -ParameterFilter {
            $version -eq 1.1
          }
        }
      }
    }


  In this sample test script, all calls to Mock and Assert-MockCalled have the
  -ModuleName MyModule parameter added. This tells Pester to inject the mock into the module scope,
  which causes any calls to those commands from inside the module to execute the mock instead.

  When you write your test script this way, you can mock commands that are called by the module's
  internal functions. However, your test script is still limited to accessing the public, exported
  members of the module. For example, you could not call the Build function directly.

  The InModuleScope command causes entire sections of your test script to execute inside the targeted
  script module. This gives you access to unexported members of the module. For example:

    Import-Module MyModule

    Describe "Unit testing the module's internal Build function:" {
      InModuleScope MyModule {
        $testVersion = 5.0
        Mock Write-Host { }

        Build $testVersion

        It 'Outputs the correct message' {
          Assert-MockCalled Write-Host -ParameterFilter {
            $Object -eq "a build was run for version: $testVersion"
          }
        }
      }
    }

  When using InModuleScope, you no longer need to specify a ModuleName parameter when calling
  Mock or Assert-MockCalled for commands in the module. You can also directly call the Build
  function that the module does not export.

SEE ALSO
  Mock
  Assert-VerifiableMock
  Assert-MockCalled
  InModuleScope
  Describe
  Context
  It

  The following articles are useful for further understanding of Pester Mocks.
    Pester Mock and Test Drive, by Jakub Jareš:
      http://www.powershellmagazine.com/2014/09/30/pester-mock-and-testdrive/
    Pester and Mocking, by Mickey Gousset:
      http://www.systemcentercentral.com/day-53-pester-mocking/
    Mocking Missing Cmdlets with Pester, by Iain Brighton:
      http://virtualengine.co.uk/2015/mocking-missing-cmdlets-with-pester/
    Testing Mocked Output with Pester, by Steven Murawski:
      http://stevenmurawski.com/powershell/2014/02/testing-returned-objects-with-pester/

  The following articles are useful for deeper understanding of Mocking in general.
    Answer to the Question "What is the Purpose of Mock Objects" by Bert F:
      http://stackoverflow.com/a/3623574/5514075
    Mocks Aren't Stubs, by Martin Fowler:
      http://martinfowler.com/articles/mocksArentStubs.html
    The Art of Mocking, by Gil Zilberfeld:
      http://www.methodsandtools.com/archive/archive.php?id=122
tools\en-US\about_Pester.help.txt
TOPIC
    about_Pester

SHORT DESCRIPTION
    Pester is a test framework for Windows PowerShell. Use the Pester language
    and its commands to write and run tests that verify that your scripts and
    modules work as designed.

    Pester 3.4.0 supports Windows PowerShell 2.0 and greater.

LONG DESCRIPTION
    Pester introduces a professional test framework for Windows PowerShell
    commands. You can use Pester to test commands of any supported type,
    including scripts, cmdlets, functions, CIM commands, workflows, and DSC
    resources, and test these commands in modules of all types.

    Each Pester test compares actual to expected output using a collection of
    comparison operators that mirror the familiar operators in Windows
    PowerShell. In this way, Pester supports "dynamic testing", that is, it
    tests the code while it's running, instead of just evaluating code syntax
    ("static testing").

    Once your Pester tests are written are verified to work correctly, you can
    run them automatically or on demand to verify that the output didn't change
    and that any code changes did not introduce errors. You can also add your
    tests to the build scripts of a continuous integration system, and add new
    tests at any time.


 WHAT CAN PESTER TEST?
    Pester is designed to support "test-driven development" (TDD), in which you
    write and run tests before writing your code, thereby using the test as a
    code specification.

    It also supports "behavior-driven development" (BDD), in which the tests
    verify the behavior and output of the code, and the user experience,
    independent of its implementation. This lets you change the implementation
    and use the test to verify that the behavior is unchanged.

    You can use Pester to write "unit tests" that test individual functions in
    isolation and "integration tests" that verify that functions can be used
    together to generate expected results.

    Pester creates and manages a temporary drive (PSDrive named TestDrive:) that
    you can use to simulate a file system. For more information, see
    about_TestDrive.

    Pester also has "mocking" commands that replace the actual output of
    commands with output that you specify. Mocking lets you test your commands
    with varied input without creating and maintaining fake entries in a file
    or database, or commenting-out and inserting code just for testing. For more
    information, see about_Mocking.


 THE PESTER LANGUAGE
    To make it easier to write tests, Pester uses a language especially designed
    for testing. This "domain-specific language" (DSL) hides the standard
    verb-noun syntax of PowerShell commands.

    To make the language more fluent, the command parameters are positional, so
    you don't typically use parameter names.

    For example, this "gets all widgets" test uses the Pester language,
    including its "It", "Should", and "Be" commands. The test verifies that the
    actual output of the Get-Widget cmdlet is the same as the expected value in
    the $allWidgets variables.

        It "gets all widgets" {
	        Get-Widget | Should -Be $allWidgets
        }


    To learn the Pester language, start by reading the following About and
    cmdlet help topics:

    -- Describe:     Creates a required test container.
    -- Context:      Creates an optional scoped test sub-container.
    -- It:           Creates a test.
    -- about_Should  Compares actual to expected values. This topic also
                     lists all valid values of Be, which specify the
                     comparison operator used in the test.



 HOW TO CREATE TEST FILES
    To start using Pester, create a script and a test file that tests the
    script. If you already have a script, you can create a test file for it.

    Pester test files are Windows PowerShell scripts with a .Tests.ps1 file name
    extension. The distinctive file name extension enables Pester to identify
    tests and distinguish them from other scripts.

    Typically, the test file and file it tests have the same base file name,
    such as:

        New-Log.ps1
        New-Log.Tests.ps1

    For a quick start, use the New-Fixture cmdlet in the Pester module. It
    creates a script with an empty function and a matching test file with a
    valid test.

    For example, this command creates a New-Log.ps1 script and a
    New-Log.Tests.ps1 test script in the C:\Scripts\LogScripts directory.

    New-Fixture -Path C:\Scripts\LogScripts -Name New-Log

        Directory: C:\Scripts\LogScripts


        Mode                LastWriteTime     Length Name
        ----                -------------     ------ ----
        -a----        4/18/2016   9:51 AM         30 New-Log.ps1
        -a----        4/18/2016   9:51 AM        262 New-Log.Tests.ps1


    The similar names do not automatically associate the test file and script
    file. The test file must include code to import ("dot-source") the
    functions, aliases, and variables in the script being tested into the scope
    of the test script.

    For example:
       . .\New-Log.ps1
    -or-
       . C:\Scripts\LogScripts\New-Log.ps1


    Many Pester test files, including the files that New-Fixture creates, begin with these
    statements.

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

    This code finds the current path of the test file at run time and saves it
    in the $here variable. Then, it finds the script based on the path in $here.
    This code assumes that the script has the same base name and is located in
    the same directory as the test file.

    You can use any code in the test file that finds the script, but be sure
    that the test file has the required *.Tests.ps1 file name extension.



 HOW TO RUN PESTER TESTS
    Pester tests are Windows PowerShell scripts (.ps1 files), so you can run
    them at the command line, or in any editor.

    Pester also has an Invoke-Pester cmdlet with useful parameters. By default,
    Invoke-Pester runs all the tests in a directory and all of its subdirectories
    recursively, but you can run selected tests by specifying a script name or
    name pattern, a test name, or a test tag.

    Invoke-Pester parameters also let you save the test output in NUnitXml or
    LegacyNUnitXml formats that are commonly used by reporting tools.

    For example, the following command runs all tests in the current directory
    and all subdirectories recursively. It writes output to the host, but does
    not generate any objects.

    Invoke-Pester

    In contrast, this command runs only the tests in the New-Log.Tests.ps1 file
    that have the 'EventVwr' tag. It writes the test results as custom objects
    and saves them in NUnitXml format in the NewLogTests.xml file. It also runs
    an optional code coverage test to verify that all lines in the script ran
    at least once during the tests.

    Invoke-Pester -Script C:\Tests\New-Log.Tests.ps1 `
          -Tag EventVwr -OutputFile .\NewLogTests.xml -OutputFormat NUnitXml `
          -CodeCoverage


    To run the New-Log.Tests.ps1 file that New-Fixture created, change to its
    local directory or a parent directory, and run Invoke-Pester. You can also
    use the Script parameter of Invoke-Pester to run only the New-Log.Tests.ps1
    test.

        PS C:\Scripts> Invoke-Pester -Script .\New-Log.Tests.ps1

    For more information about Invoke-Pester, type: Get-Help Invoke-Pester


 EXAMPLE
    For your first Pester test, use the New-Fixture cmdlet to create a script
    file and matching test file.

    For example:

        New-Fixture -Path C:\TestPester -Name Get-Hello

        Directory: C:\TestPester


        Mode                LastWriteTime         Length Name
        ----                -------------         ------ ----
        -a----        4/18/2016   9:51 AM             30 Get-Hello.ps1
        -a----        4/18/2016   9:51 AM            262 Get-Hello.Tests.ps1


    The Get-Hello.ps1 script contains an empty Get-Hello.ps1 function.

        function Get-Hello {}

    The Get-Hello.Tests.ps1 file contains an empty Pester test that is named
    for the Get-Hello function.

        Describe "Get-Hello" {
            It "does something useful" {
                $true | Should -Be $false
            }
        }

    To run the test, use Invoke-Pester. For example,

       Invoke-Pester C:\TestPester

    When you run the test, it fails by design, because Should compares $True to
    $False using the equal operator ("Be") and $True doesn't equal $False.


    To start testing the Get-Hello function, change $True to Get-Hello and
    $False to "Hello". Now, the test compares the output of Get-Hello output to
    'hello'.

    It should still fail, because Get-Hello doesn't return anything.

        Describe "New-Log" {
            It "does something useful" {
                Get-Hello | Should -Be 'Hello'
            }
        }


    To make the test pass, change the Get-Hello function so it returns 'hello'.
    Then, in steps, change $False to more interesting values, then change the
    Get-Hello function output to make the test pass.

    You can also experiment with other comparison operators, such as the BeLike
    (supports wildcards) and BeExactly (case sensitive), and BeLikeExactly
    operators. For more, information about comparison operators in Pester, see
    about_Should.


 PESTER TEST OUTPUT
    When you run a test, Pester use a variation of Write-Host to write
    color-coded text to the console. You'll quickly learn to recognize the
    purple test names and green (passing) and red (failing) test results with
    the elapsed time of the test.

         Describing Get-Profile
          [+] Gets all profiles 156ms
          [+] Gets only profiles 24ms

    The output ends with a summary of the test results.

         Tests completed in 3.47s
         Passed: 20 Failed: 1 Skipped: 0 Pending: 0 Inconclusive: 0

    However, because Pester uses Write-Host, it does not write to the output
    stream (stdout), so there are no output objects to save in a variable or
    redirect to a file.

    To direct Pester to create custom objects, use its PassThru parameter. The
    result is a single PSCustomObject with a TestResult property that one
    TestResult custom object for each test in the test file.

    To save the custom objects to a file, use the OutputFile and OutputFormat
    parameters of Invoke-Pester, which save the output in NUnitXml and
    LegacyNUnitXml formats that are easy to parse and commonly used by reporting
    tools.



  REAL-WORLD EXAMPLES
    For help in writing Pester tests, examine the extensive collection of tests
    that Pester uses to verify its Windows PowerShell code.

    To find the Pester tests in the Pester module directory, type:

        dir <Pester_module_path>\*Tests.ps1 -Recurse

       -or-

    dir (Get-Module Pester -ListAvailable).ModuleBase -Include *Tests.ps1 -Recurse


SEE ALSO
    Pester wiki: https://github.com/pester/pester/wiki
    Describe
    Context
    It
    New-Fixture
    Invoke-Pester
    about_Mocking
    about_Should
    about_TestDrive
tools\en-US\about_Should.help.txt
TOPIC
    about_Should

SHORT DESCRIPTION
    Provides assertion convenience methods for comparing objects and throwing
    test failures when test expectations fail.

LONG DESCRIPTION
    Should is an Extension of System.Object and can be used as a native type
    inside Describe blocks. The various Should member methods can be invoked
    directly from an object being compared. It is typically used in individual
    It blocks to verify the results of an expectation. The Should method is
    typically called from the "actual" object being compared and takes the
    expected" object as a parameter. Should includes several members that
    perform various comparisons of objects and will throw a PesterFailure when
    the objects do not evaluate to be comparable.

  SHOULD MEMBERS
    Be
        Compares one object with another for equality and throws if the two
        objects are not the same.

        $actual="Actual value"
        $actual | Should -Be "actual value" # Test will pass
        $actual | Should -Be "not actual value"  # Test will fail

    BeExactly
        Compares one object with another for equality and throws if the two objects are not the same.  This comparison is case sensitive.

        $actual="Actual value"
        $actual | Should -BeExactly "Actual value" # Test will pass
        $actual | Should -BeExactly "actual value" # Test will fail

    BeGreaterThan
        Asserts that a number is greater than an expected value. Uses PowerShell's -gt operator to compare the two values.

        $Error.Count | Should -BeGreaterThan 0

    BeIn
        Asserts that a collection of values contain a specific value. Uses PowerShell's -contains operator to confirm.

        1 | Should -BeIn @(1,2,3,'a','b','c')

    BeLessThan
        Asserts that a number is less than an expected value. Uses PowerShell's -gt operator to compare the two values.

        $Error.Count | Should -BeLessThan 1

    BeLike
        Asserts that the actual value matches a wildcard pattern using PowerShell's -like operator.  This comparison is not case-sensitive.

        $actual="Actual value"
        $actual | Should -BeLike "actual *" # Test will pass
        $actual | Should -BeLike "not actual *" # Test will fail

    BeLikeExactly

        Asserts that the actual value matches a wildcard pattern using PowerShell's -like operator.  This comparison is case-sensitive.

        $actual="Actual value"
        $actual | Should -BeLikeExactly "Actual *" # Test will pass
        $actual | Should -BeLikeExactly "actual *" # Test will fail

    BeOfType
        Asserts that the actual value should be an object of a specified type (or a subclass of the specified type) using PowerShell's -is operator:

        $actual = Get-Item $env:SystemRoot
        $actual | Should -BeOfType System.IO.DirectoryInfo   # Test will pass; object is a DirectoryInfo
        $actual | Should -BeOfType System.IO.FileSystemInfo  # Test will pass; DirectoryInfo base class is FileSystemInfo

        $actual | Should -BeOfType System.IO.FileInfo        # Test will fail; FileInfo is not a base class of DirectoryInfo

    BeNullOrEmpty
        Checks values for null or empty (strings). The static [String]::IsNullOrEmpty() method is used to do the comparison.

        $null | Should -BeNullOrEmpty # Test will pass
        $null | Should -Not -BeNullOrEmpty # Test will fail
        @()   | Should -BeNullOrEmpty # Test will pass
        ""    | Should -BeNullOrEmpty # Test will pass
    Exist
        Does not perform any comparison but checks if the object calling Exist
        is present in a PS Provider. The object must have valid path syntax. It
        essentially must pass a Test-Path call.

        $actual=(Dir . )[0].FullName
        Remove-Item $actual
        $actual | Should -Exist # Test will fail

    FileContentMatch
        Checks to see if a file contains the specified text.  This search is not case sensitive and uses regular expressions.

        Set-Content -Path TestDrive:\file.txt -Value 'I am a file.'
        'TestDrive:\file.txt' | Should -FileContentMatch 'I Am' # Test will pass
        'TestDrive:\file.txt' | Should -FileContentMatch '^I.*file$' # Test will pass

        'TestDrive:\file.txt' | Should -FileContentMatch 'I Am Not' # Test will fail

        Tip: Use [regex]::Escape("pattern") to match the exact text.

        Set-Content -Path TestDrive:\file.txt -Value 'I am a file.'
        'TestDrive:\file.txt' | Should -FileContentMatch 'I.am.a.file' # Test will pass
        'TestDrive:\file.txt' | Should -FileContentMatch ([regex]::Escape('I.am.a.file')) # Test will fail

    FileContentMatchExactly
        Checks to see if a file contains the specified text.  This search is case sensitive and uses regular expressions to match the text.

        Set-Content -Path TestDrive:\file.txt -Value 'I am a file.'
        'TestDrive:\file.txt' | Should -FileContentMatch 'I am' # Test will pass
        'TestDrive:\file.txt' | Should -FileContentMatch 'I Am' # Test will fail

    Match
        Uses a regular expression to compare two objects. This comparison is not case sensitive.

        "I am a value" | Should -Match "I Am" # Test will pass
        "I am a value" | Should -Match "I am a bad person" # Test will fail

        Tip: Use [regex]::Escape("pattern") to match the exact text.

        "Greg" | Should -Match ".reg" # Test will pass
        "Greg" | Should -Match ([regex]::Escape(".reg")) # Test will fail

    MatchExactly
        Uses a regular expression to compare two objects.  This comparison is case sensitive.

        "I am a value" | Should -MatchExactly "I am" # Test will pass
        "I am a value" | Should -MatchExactly "I Am" # Test will fail

    Throw
        Checks if an exception was thrown. Enclose input in a script block.

        { foo } | Should -Throw # Test will pass
        { $foo = 1 } | Should -Throw # Test will fail
        { foo } | Should -Not -Throw # Test will fail
        { $foo = 1 } | Should -Not -Throw # Test will pass

        Warning: The input object must be a ScriptBlock, otherwise it is processed outside of the assertion.

        Get-Process -Name "process" -ErrorAction Stop | Should -Throw # Should pass, but the exception thrown by Get-Process causes the test to fail.

  NEGATIVE ASSERTIONS
    Any of the Should operators described above can be negated by using the word "Not" before the operator.  For example:

    'one' | Should -Not -Be 'Two'
    { Get-Item $env:SystemRoot } | Should -Not -Throw

  USING SHOULD IN A TEST

    function Add-Numbers($a, $b) {
        return $a + $b
    }

    Describe "Add-Numbers" {

        It "adds positive numbers" {
            $sum = Add-Numbers 2 3
            $sum | Should -Be 3
        }
    }

    This test will fail since 3 will not be equal to the sum of 2 and 3.

SEE ALSO
  Describe
  Context
  It
tools\en-US\about_TestDrive.help.txt
TOPIC
    about_TestDrive

SHORT DESCRIPTION
    A PSDrive for file activity limited to the scope of a singe Describe or
    Context block.

LONG DESCRIPTION
    A test may need to work with file operations and validate certain types of
    file activities. It is usually desirable not to perform file activity tests
    that will produce side effects outside of an individual test. Pester
    creates a PSDrive inside the user's temporary drive that is accessible via a
    names PSDrive TestDrive:. Pester will remove this drive after the test
    completes. You may use this drive to isolate the file operations of your
    test to a temporary store.

EXAMPLE
    function Add-Footer($path, $footer) {
        Add-Content $path -Value $footer
    }

    Describe "Add-Footer" {
        $testPath="TestDrive:\test.txt"
        Set-Content $testPath -value "my test text."
        Add-Footer $testPath "-Footer"
        $result = Get-Content $testPath

        It "adds a footer" {
            (-join $result).Should.Be("my test text.-Footer")
        }
    }

    When this test completes, the contents of the TestDrive PSDrive will
    be removed.

SEE ALSO
    Context
    Describe
    It
    about_Should
tools\en-US\Gherkin.psd1
@{
    StartMessage = "Testing all features in '{0}'"
    FilterMessage = " for scenarios matching '{0}'"
    TagMessage = " with tags: '{0}'"
    MessageOfs = "', '"

    CoverageTitle   = "Code coverage report:"
    CoverageMessage = "Covered {2:P2} of {3:N0} analyzed {0} in {4:N0} {1}."
    MissedSingular  = 'Missed command:'
    MissedPlural    = 'Missed commands:'
    CommandSingular = 'Command'
    CommandPlural   = 'Commands'
    FileSingular    = 'File'
    FilePlural      = 'Files'

    Describe = "Feature: {0}"
    Context  = "Scenario: {0}"
    Margin   = "  "
    Timing   = "Testing completed in {0}"

    # If this is set to an empty string, the count won't be printed
    ContextsPassed    = "Scenarios Passed: {0} "
    ContextsFailed    = "Failed: {0}"
    TestsPassed       = "Steps Passed: {0} "
    TestsFailed       = "Failed: {0} "
    TestsSkipped      = 'Skipped: {0} '
    TestsPending      = 'Pending: {0} '
    TestsInconclusive = 'Inconclusive: {0} '
}
tools\en-US\RSpec.psd1
@{
    StartMessage   = "Executing all tests in '{0}'"
    FilterMessage  = ' matching test name {0}'
    TagMessage     = ' with Tags {0}'
    MessageOfs     = "', '"

    CoverageTitle   = 'Code coverage report:'
    CoverageMessage = 'Covered {2:P2} of {3:N0} analyzed {0} in {4:N0} {1}.'
    MissedSingular  = 'Missed command:'
    MissedPlural    = 'Missed commands:'
    CommandSingular = 'Command'
    CommandPlural   = 'Commands'
    FileSingular    = 'File'
    FilePlural      = 'Files'

    Describe = 'Describing {0}'
    Script   = 'Executing script {0}'
    Context  = 'Context {0}'
    Margin   = '  '
    Timing   = 'Tests completed in {0}'

    # If this is set to an empty string, the count won't be printed
    ContextsPassed = ''
    ContextsFailed = ''

    TestsPassed       = 'Tests Passed: {0}, '
    TestsFailed       = 'Failed: {0}, '
    TestsSkipped      = 'Skipped: {0}, '
    TestsPending      = 'Pending: {0}, '
    TestsInconclusive = 'Inconclusive: {0} '
}
tools\Functions\Assertions\Be.ps1
#Be
function PesterBe($ActualValue, $ExpectedValue, [switch] $Negate) {
    [bool] $succeeded = ArraysAreEqual $ActualValue $ExpectedValue

    if ($Negate) { $succeeded = -not $succeeded }

    $failureMessage = ''

    if (-not $succeeded)
    {
        if ($Negate)
        {
            $failureMessage = NotPesterBeFailureMessage -ActualValue $ActualValue -Expected $ExpectedValue
        }
        else
        {
            $failureMessage = PesterBeFailureMessage -ActualValue $ActualValue -Expected $ExpectedValue
        }
    }

    return & $SafeCommands['New-Object'] psobject -Property @{
        Succeeded      = $succeeded
        FailureMessage = $failureMessage
    }
}

function PesterBeFailureMessage($ActualValue, $ExpectedValue) {
    # This looks odd; it's to unroll single-element arrays so the "-is [string]" expression works properly.
    $ActualValue = $($ActualValue)
    $ExpectedValue = $($ExpectedValue)

    if (-not (($ExpectedValue -is [string]) -and ($ActualValue -is [string])))
    {
        return "Expected: {$ExpectedValue}`nBut was:  {$ActualValue}"
    }
    <#joining the output strings to a single string here, otherwise I get
       Cannot find an overload for "Exception" and the argument count: "4".
       at line: 63 in C:\Users\nohwnd\github\pester\Functions\Assertions\Should.ps1

    This is a quickwin solution, doing the join in the Should directly might be better
    way of doing this. But I don't want to mix two problems.
    #>
    ( Get-CompareStringMessage -Expected $ExpectedValue -Actual $ActualValue ) -join "`n"
}

function NotPesterBeFailureMessage($ActualValue, $ExpectedValue) {
    return "Expected: value was {$ActualValue}, but should not have been the same"
}

Add-AssertionOperator -Name               Be `
                      -Test               $function:PesterBe `
                      -Alias              'EQ' `
                      -SupportsArrayInput

#BeExactly
function PesterBeExactly($ActualValue, $ExpectedValue) {
    [bool] $succeeded = ArraysAreEqual $ActualValue $ExpectedValue -CaseSensitive

    if ($Negate) { $succeeded = -not $succeeded }

    $failureMessage = ''

    if (-not $succeeded)
    {
        if ($Negate)
        {
            $failureMessage = NotPesterBeExactlyFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
        }
        else
        {
            $failureMessage = PesterBeExactlyFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
        }
    }

    return New-Object psobject -Property @{
        Succeeded      = $succeeded
        FailureMessage = $failureMessage
    }
}

function PesterBeExactlyFailureMessage($ActualValue, $ExpectedValue) {
    # This looks odd; it's to unroll single-element arrays so the "-is [string]" expression works properly.
    $ActualValue = $($ActualValue)
    $ExpectedValue = $($ExpectedValue)

    if (-not (($ExpectedValue -is [string]) -and ($ActualValue -is [string])))
    {
        return "Expected exactly: {$ExpectedValue}`nBut was: {$ActualValue}"
    }
    <#joining the output strings to a single string here, otherwise I get
       Cannot find an overload for "Exception" and the argument count: "4".
       at line: 63 in C:\Users\nohwnd\github\pester\Functions\Assertions\Should.ps1

    This is a quickwin solution, doing the join in the Should directly might be better
    way of doing this. But I don't want to mix two problems.
    #>
    ( Get-CompareStringMessage -Expected $ExpectedValue -Actual $ActualValue -CaseSensitive ) -join "`n"
}

function NotPesterBeExactlyFailureMessage($ActualValue, $ExpectedValue) {
    return "Expected: value was {$ActualValue}, but should not have been exactly the same"
}

Add-AssertionOperator -Name               BeExactly `
                      -Test               $function:PesterBeExactly `
                      -Alias              'CEQ' `
                      -SupportsArrayInput


#common functions
function Get-CompareStringMessage {
    param(
        [Parameter(Mandatory=$true)]
        [AllowEmptyString()]
        [String]$ExpectedValue,
        [Parameter(Mandatory=$true)]
        [AllowEmptyString()]
        [String]$Actual,
        [switch]$CaseSensitive
    )

    $ExpectedValueLength = $ExpectedValue.Length
    $actualLength = $actual.Length
    $maxLength = $ExpectedValueLength,$actualLength | & $SafeCommands['Sort-Object'] -Descending | & $SafeCommands['Select-Object'] -First 1

    $differenceIndex = $null
    for ($i = 0; $i -lt $maxLength -and ($null -eq $differenceIndex); ++$i){
        $differenceIndex = if ($CaseSensitive -and ($ExpectedValue[$i] -cne $actual[$i]))
        {
            $i
        }
        elseif ($ExpectedValue[$i] -ne $actual[$i])
        {
            $i
        }
    }

    [string]$output = $null
    if ($null -ne $differenceIndex)
    {
        if ($ExpectedValue.Length -ne $actual.Length) {
           "Expected string length $ExpectedValueLength but was $actualLength. Strings differ at index $differenceIndex."
        }
        else
        {
           "String lengths are both $ExpectedValueLength. Strings differ at index $differenceIndex."
        }

        "Expected: {{{0}}}" -f ( $ExpectedValue | Expand-SpecialCharacters )
        "But was:  {{{0}}}" -f ( $actual | Expand-SpecialCharacters )

        $specialCharacterOffset = $null
        if ($differenceIndex -ne 0)
        {
            #count all the special characters before the difference
            $specialCharacterOffset = ($actual[0..($differenceIndex-1)] |
                & $SafeCommands['Where-Object'] {"`n","`r","`t","`b","`0" -contains $_} |
                & $SafeCommands['Measure-Object'] |
                & $SafeCommands['Select-Object'] -ExpandProperty Count)
        }

        '-'*($differenceIndex+$specialCharacterOffset+11)+'^'
    }
}

function Expand-SpecialCharacters {
    param (
    [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
    [AllowEmptyString()]
    [string[]]$InputObject)
    process {
        $InputObject -replace "`n","\n" -replace "`r","\r" -replace "`t","\t" -replace "`0", "\0" -replace "`b","\b"
    }
}

function ArraysAreEqual
{
    param (
        [object[]] $First,
        [object[]] $Second,
        [switch] $CaseSensitive,
        [int] $RecursionDepth = 0,
        [int] $RecursionLimit = 100
    )
    $RecursionDepth++

    if ($RecursionDepth -gt $RecursionLimit)
    {
        throw "Reached the recursion depth limit of $RecursionLimit when comparing arrays $First and $Second. Is one of your arrays cyclic?"
    }

    # Do not remove the subexpression @() operators in the following two lines; doing so can cause a
    # silly error in PowerShell v3.  (Null Reference exception from the PowerShell engine in a
    # method called CheckAutomationNullInCommandArgumentArray(System.Object[]) ).
    $firstNullOrEmpty  = ArrayOrSingleElementIsNullOrEmpty -Array @($First)
    $secondNullOrEmpty = ArrayOrSingleElementIsNullOrEmpty -Array @($Second)

    if ($firstNullOrEmpty -or $secondNullOrEmpty)
    {
        return $firstNullOrEmpty -and $secondNullOrEmpty
    }

    if ($First.Count -ne $Second.Count) { return $false }

    for ($i = 0; $i -lt $First.Count; $i++)
    {
        if ((IsArray $First[$i]) -or (IsArray $Second[$i]))
        {
            if (-not (ArraysAreEqual -First $First[$i] -Second $Second[$i] -CaseSensitive:$CaseSensitive -RecursionDepth $RecursionDepth -RecursionLimit $RecursionLimit))
            {
                return $false
            }
        }
        else
        {
            if ($CaseSensitive)
            {
                $comparer = { param($Actual, $Expected) $Expected -ceq $Actual }
            }
            else
            {
                $comparer = { param($Actual, $Expected) $Expected -eq $Actual }
            }

            if (-not (& $comparer $First[$i] $Second[$i]))
            {
                return $false
            }
        }
    }

    return $true
}

function ArrayOrSingleElementIsNullOrEmpty
{
    param ([object[]] $Array)

    return $null -eq $Array -or $Array.Count -eq 0 -or ($Array.Count -eq 1 -and $null -eq $Array[0])
}

function IsArray
{
    param ([object] $InputObject)

    # Changing this could cause infinite recursion in ArraysAreEqual.
    # see https://github.com/pester/Pester/issues/785#issuecomment-322794011
    return $InputObject -is [Array]
}

function ReplaceValueInArray
{
    param (
        [object[]] $Array,
        [object] $Value,
        [object] $NewValue
    )

    foreach ($object in $Array)
    {
        if ($Value -eq $object)
        {
            $NewValue
        }
        elseif (@($object).Count -gt 1)
        {
            ReplaceValueInArray -Array @($object) -Value $Value -NewValue $NewValue
        }
        else
        {
            $object
        }
    }
}

tools\Functions\Assertions\BeGreaterThan.ps1
function PesterBeGreaterThan($ActualValue, $ExpectedValue, [switch] $Negate)
{
    [bool] $succeeded = $ActualValue -gt $ExpectedValue
    if ($Negate) { $succeeded = -not $succeeded }

    $failureMessage = ''

    if (-not $succeeded)
    {
        if ($Negate)
        {
            $failureMessage = NotPesterBeGreaterThanFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
        }
        else
        {
            $failureMessage = PesterBeGreaterThanFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
        }
    }

    return New-Object psobject -Property @{
        Succeeded      = $succeeded
        FailureMessage = $failureMessage
    }
}

function PesterBeGreaterThanFailureMessage($ActualValue,$ExpectedValue)
{
    return "Expected {$ActualValue} to be greater than {$ExpectedValue}"
}

function NotPesterBeGreaterThanFailureMessage($ActualValue,$ExpectedValue)
{
    return "Expected {$ActualValue} to be less than or equal to {$ExpectedValue}"
}

Add-AssertionOperator -Name  BeGreaterThan `
                      -Test  $function:PesterBeGreaterThan `
                      -Alias 'GT'
tools\Functions\Assertions\BeIn.ps1
function PesterBeIn($ActualValue, $ExpectedValue, [switch] $Negate)
{
    [bool] $succeeded = $ExpectedValue -contains $ActualValue
    if ($Negate) { $succeeded = -not $succeeded }

    $failureMessage = ''

    if (-not $succeeded)
    {
        if ($Negate)
        {
            $failureMessage = NotPesterBeInFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
        }
        else
        {
            $failureMessage = PesterBeInFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
        }
    }

    return New-Object psobject -Property @{
        Succeeded      = $succeeded
        FailureMessage = $failureMessage
    }
}

function PesterBeInFailureMessage($ActualValue, $ExpectedValue) {
    if(-not ([bool]($ExpectedValue -contains $ActualValue))) {
        return "Expected: ${ActualValue} to be in collection [$($ExpectedValue -join ',')] but was not found."
    }
}

function NotPesterBeInFailureMessage($ActualValue, $ExpectedValue) {
    if([bool]($ExpectedValue -contains $ActualValue)) {
        return "Expected: ${ActualValue} to not be in collection [$($ExpectedValue -join ',')] but was found."
    }
}

Add-AssertionOperator -Name BeIn -Test $function:PesterBeIn
tools\Functions\Assertions\BeLessThan.ps1
function PesterBeLessThan($ActualValue, $ExpectedValue)
{
    [bool] $succeeded = $ActualValue -lt $ExpectedValue
    if ($Negate) { $succeeded = -not $succeeded }

    $failureMessage = ''

    if (-not $succeeded)
    {
        if ($Negate)
        {
            $failureMessage = NotPesterBeLessThanFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
        }
        else
        {
            $failureMessage = PesterBeLessThanFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
        }
    }

    return New-Object psobject -Property @{
        Succeeded      = $succeeded
        FailureMessage = $failureMessage
    }
}

function PesterBeLessThanFailureMessage($ActualValue,$ExpectedValue)
{
    return "Expected {$ActualValue} to be less than {$ExpectedValue}"
}

function NotPesterBeLessThanFailureMessage($ActualValue,$ExpectedValue)
{
    return "Expected {$ActualValue} to be greater than or equal to {$ExpectedValue}"
}

Add-AssertionOperator -Name  BeLessThan `
                      -Test  $function:PesterBeLessThan `
                      -Alias 'LT'
tools\Functions\Assertions\BeLike.ps1
function PesterBeLike($ActualValue, $ExpectedValue, [switch] $Negate)
{
    [bool] $succeeded = $ActualValue -like $ExpectedValue
    if ($Negate) { $succeeded = -not $succeeded }

    $failureMessage = ''

    if (-not $succeeded)
    {
        if ($Negate)
        {
            $failureMessage = NotPesterBeLikeFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
        }
        else
        {
            $failureMessage = PesterBeLikeFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
        }
    }

    return New-Object psobject -Property @{
        Succeeded      = $succeeded
        FailureMessage = $failureMessage
    }
}

function PesterBeLikeFailureMessage($ActualValue, $ExpectedValue) {
    return "Expected: {$ActualValue} to be like the wildcard {$ExpectedValue}"
}

function NotPesterBeLikeFailureMessage($ActualValue, $ExpectedValue) {
    return "Expected: ${ActualValue} to not be like the wildcard ${ExpectedValue}"
}

Add-AssertionOperator -Name BeLike -Test  $function:PesterBeLike
tools\Functions\Assertions\BeLikeExactly.ps1

function PesterBeLikeExactly($value, $expectedMatch) {
    return ($value -clike $expectedMatch)
}

function PesterBeLikeExactlyFailureMessage($value, $expectedMatch) {
    return "Expected: {$value} to be exactly like the wildcard {$expectedMatch}"
}

function NotPesterBeLikeExactlyFailureMessage($value, $expectedMatch) {
    return "Expected: ${value} to not be exactly like the wildcard ${expectedMatch}"
}

tools\Functions\Assertions\BeNullOrEmpty.ps1

function PesterBeNullOrEmpty([object[]] $ActualValue, [switch] $Negate) {
    if ($null -eq $ActualValue -or $ActualValue.Count -eq 0)
    {
        $succeeded = $true
    }
    elseif ($ActualValue.Count -eq 1)
    {
        $expandedValue = $ActualValue[0]
        if ($expandedValue -is [hashtable])
        {
            $succeeded = $expandedValue.Count -eq 0
        }
        else
        {
            $succeeded = [String]::IsNullOrEmpty($expandedValue)
        }
    }
    else
    {
        $succeeded = $false
    }

    if ($Negate) { $succeeded = -not $succeeded }

    $failureMessage = ''

    if (-not $succeeded)
    {
        if ($Negate)
        {
            $failureMessage = NotPesterBeNullOrEmptyFailureMessage -ActualValue $ActualValue
        }
        else
        {
            $failureMessage = PesterBeNullOrEmptyFailureMessage -ActualValue $ActualValue
        }
    }

    return New-Object psobject -Property @{
        Succeeded      = $succeeded
        FailureMessage = $failureMessage
    }
}

function PesterBeNullOrEmptyFailureMessage($ActualValue) {
    return "Expected: value to be empty but it was {$ActualValue}"
}

function NotPesterBeNullOrEmptyFailureMessage {
    return "Expected: value to not be empty"
}

Add-AssertionOperator -Name               BeNullOrEmpty `
                      -Test               $function:PesterBeNullOrEmpty `
                      -SupportsArrayInput
tools\Functions\Assertions\BeOfType.ps1

function PesterBeOfType($ActualValue, $ExpectedType, [switch] $Negate) {
    $hash = @{ Succeeded = $true }

    trap [System.Management.Automation.PSInvalidCastException] { $hash['Succeeded'] = $false; continue }

    if($ExpectedType -is [string] -and !($ExpectedType -as [Type])) {
        $ExpectedType = $ExpectedType -replace '^\[(.*)\]$','$1'
    }

    $hash['Succeeded'] = $ActualValue -is $ExpectedType

    if ($Negate) { $hash['Succeeded'] = -not $hash['Succeeded'] }

    $failureMessage = ''

    if (-not $hash['Succeeded'])
    {
        if ($Negate)
        {
            $failureMessage = NotPesterBeOfTypeFailureMessage -ActualValue $ActualValue -ExpectedType $ExpectedType
        }
        else
        {
            $failureMessage = PesterBeOfTypeFailureMessage -ActualValue $ActualValue -ExpectedType $ExpectedType
        }
    }

    return New-Object psobject -Property @{
        Succeeded      = $hash['Succeeded']
        FailureMessage = $failureMessage
    }
}

function PesterBeOfTypeFailureMessage($ActualValue, $ExpectedType) {
    if($ExpectedType -is [string] -and !($ExpectedType -as [Type])) {
        $ExpectedType = $ExpectedType -replace '^\[(.*)\]$','$1'
    }

    if($Type = $ExpectedType -as [type]) {
        return "Expected: {$ActualValue} to be of type [$Type]"
    } else {
        return "Expected: {$ActualValue} to be of type [$ExpectedType], but unable to find type [$ExpectedType]. Make sure that the assembly that contains that type is loaded."
    }
}

function NotPesterBeOfTypeFailureMessage($ActualValue, $ExpectedType) {
    if($ExpectedType -is [string] -and -not $ExpectedType -as [Type]) {
        $ExpectedType = $ExpectedType -replace '^\[(.*)\]$','$1'
    }
    if($Type = $ExpectedType -as [type]) {
        return "Expected: {$ActualValue} to be of any type except [${Type}], but it's a [${Type}]"
    } else {
        return "Expected: {$ActualValue} to be of any type except [$ExpectedType], but unable to find type [$ExpectedType]. Make sure that the assembly that contains that type is loaded."
    }
}

Add-AssertionOperator -Name BeOfType `
                      -Test $function:PesterBeOfType
tools\Functions\Assertions\Exist.ps1
function PesterExist($ActualValue, [switch] $Negate) {
    [bool] $succeeded = & $SafeCommands['Test-Path'] $ActualValue

    if ($Negate) { $succeeded = -not $succeeded }

    $failureMessage = ''

    if (-not $succeeded)
    {
        if ($Negate)
        {
            $failureMessage = NotPesterExistFailureMessage -ActualValue $ActualValue
        }
        else
        {
            $failureMessage = PesterExistFailureMessage -ActualValue $ActualValue
        }
    }

    return & $SafeCommands['New-Object'] psobject -Property @{
        Succeeded      = $succeeded
        FailureMessage = $failureMessage
    }
}

function PesterExistFailureMessage($ActualValue) {
    return "Expected: {$ActualValue} to exist"
}

function NotPesterExistFailureMessage($ActualValue) {
    return "Expected: {$ActualValue} to not exist, but it was found"
}

Add-AssertionOperator -Name Exist `
                      -Test $function:PesterExist
tools\Functions\Assertions\FileContentMatch.ps1
function PesterFileContentMatch($ActualValue, $ExpectedContent, [switch] $Negate) {
    $succeeded = (@(& $SafeCommands['Get-Content'] -Encoding UTF8 $ActualValue) -match $ExpectedContent).Count -gt 0

    if ($Negate) { $succeeded = -not $succeeded }

    $failureMessage = ''

    if (-not $succeeded)
    {
        if ($Negate)
        {
            $failureMessage = NotPesterFileContentMatchFailureMessage -ActualValue $ActualValue -ExpectedContent $ExpectedContent
        }
        else
        {
            $failureMessage = PesterFileContentMatchFailureMessage -ActualValue $ActualValue -ExpectedContent $ExpectedContent
        }
    }

    return & $SafeCommands['New-Object'] psobject -Property @{
        Succeeded      = $succeeded
        FailureMessage = $failureMessage
    }
}

function PesterFileContentMatchFailureMessage($ActualValue, $ExpectedContent) {
    return "Expected: file {$ActualValue} to contain {$ExpectedContent}"
}

function NotPesterFileContentMatchFailureMessage($ActualValue, $ExpectedContent) {
    return "Expected: file {$ActualValue} to not contain {$ExpectedContent} but it did"
}

Add-AssertionOperator -Name FileContentMatch `
                      -Test $function:PesterFileContentMatch
tools\Functions\Assertions\FileContentMatchExactly.ps1
function PesterFileContentMatchExactly($ActualValue, $ExpectedContent, [switch] $Negate) {
    $succeeded = (@(& $SafeCommands['Get-Content'] -Encoding UTF8 $ActualValue) -cmatch $ExpectedContent).Count -gt 0

    if ($Negate) { $succeeded = -not $succeeded }

    $failureMessage = ''

    if (-not $succeeded)
    {
        if ($Negate)
        {
            $failureMessage = NotPesterFileContentMatchExactlyFailureMessage -ActualValue $ActualValue -ExpectedContent $ExpectedContent
        }
        else
        {
            $failureMessage = PesterFileContentMatchExactlyFailureMessage -ActualValue $ActualValue -ExpectedContent $ExpectedContent
        }
    }

    return & $SafeCommands['New-Object'] psobject -Property @{
        Succeeded      = $succeeded
        FailureMessage = $failureMessage
    }
}

function PesterFileContentMatchExactlyFailureMessage($ActualValue, $ExpectedContent) {
    return "Expected: file {$ActualValue} to contain exactly {$ExpectedContent}"
}

function NotPesterFileContentMatchExactlyFailureMessage($ActualValue, $ExpectedContent) {
    return "Expected: file {$ActualValue} to not contain exactly {$ExpectedContent} but it did"
}

Add-AssertionOperator -Name FileContentMatchExactly `
                      -Test $function:PesterFileContentMatchExactly
tools\Functions\Assertions\FileContentMatchMultiline.ps1
function PesterFileContentMatchMultiline($ActualValue, $ExpectedContent, [switch] $Negate) {
    $succeeded = [bool] ((Get-Content $ActualValue -delim ([char]0)) -match $ExpectedContent)

    if ($Negate) { $succeeded = -not $succeeded }

    $failureMessage = ''

    if (-not $succeeded)
    {
        if ($Negate)
        {
            $failureMessage = NotPesterFileContentMatchMultilineFailureMessage -ActualValue $ActualValue -ExpectedContent $ExpectedContent
        }
        else
        {
            $failureMessage = PesterFileContentMatchMultilineFailureMessage -ActualValue $ActualValue -ExpectedContent $ExpectedContent
        }
    }

    return New-Object psobject -Property @{
        Succeeded      = $succeeded
        FailureMessage = $failureMessage
    }
}

function PesterFileContentMatchMultilineFailureMessage($ActualValue, $ExpectedContent) {
    return "Expected: file {$ActualValue} to contain {$ExpectedContent}"
}

function NotPesterFileContentMatchMultilineFailureMessage($ActualValue, $ExpectedContent) {
    return "Expected: file {$ActualValue} to not contain {$ExpectedContent} but it did"
}

Add-AssertionOperator -Name  FileContentMatchMultiline `
                      -Test  $function:PesterFileContentMatchMultiline
tools\Functions\Assertions\Match.ps1
function PesterMatch($ActualValue, $RegularExpression, [switch] $Negate) {
    [bool] $succeeded = $ActualValue -match $RegularExpression

    if ($Negate) { $succeeded = -not $succeeded }

    $failureMessage = ''

    if (-not $succeeded)
    {
        if ($Negate)
        {
            $failureMessage = NotPesterMatchFailureMessage -ActualValue $ActualValue -RegularExpression $RegularExpression
        }
        else
        {
            $failureMessage = PesterMatchFailureMessage -ActualValue $ActualValue -RegularExpression $RegularExpression
        }
    }

    return New-Object psobject -Property @{
        Succeeded      = $succeeded
        FailureMessage = $failureMessage
    }
}

function PesterMatchFailureMessage($ActualValue, $RegularExpression) {
    return "Expected: {$ActualValue} to match the expression {$RegularExpression}"
}

function NotPesterMatchFailureMessage($ActualValue, $RegularExpression) {
    return "Expected: {$ActualValue} to not match the expression {$RegularExpression}"
}

Add-AssertionOperator -Name Match `
                      -Test $function:PesterMatch
tools\Functions\Assertions\MatchExactly.ps1
function PesterMatchExactly($ActualValue, $RegularExpression, [switch] $Negate) {
    [bool] $succeeded = $ActualValue -cmatch $RegularExpression

    if ($Negate) { $succeeded = -not $succeeded }

    $failureMessage = ''

    if (-not $succeeded)
    {
        if ($Negate)
        {
            $failureMessage = NotPesterMatchExactlyFailureMessage -ActualValue $ActualValue -RegularExpression $RegularExpression
        }
        else
        {
            $failureMessage = PesterMatchExactlyFailureMessage -ActualValue $ActualValue -RegularExpression $RegularExpression
        }
    }

    return New-Object psobject -Property @{
        Succeeded      = $succeeded
        FailureMessage = $failureMessage
    }
}

function PesterMatchExactlyFailureMessage($ActualValue, $RegularExpression) {
    return "Expected: {$ActualValue} to exactly match the expression {$RegularExpression}"
}

function NotPesterMatchExactlyFailureMessage($ActualValue, $RegularExpression) {
    return "Expected: {$ActualValue} to not match the expression {$RegularExpression} exactly"
}

Add-AssertionOperator -Name  MatchExactly `
                      -Test  $function:PesterMatchExactly `
                      -Alias 'CMATCH'
tools\Functions\Assertions\PesterThrow.ps1
function PesterThrow([scriptblock] $ActualValue, $ExpectedMessage, $ErrorId, [type]$ExceptionType, [switch] $Negate) {
    $actualExceptionMessage = ""
    $actualExceptionWasThrown = $false
    $actualException = $null
    $actualExceptionLine = $null

    if ($null -eq $ActualValue) {
        throw (New-Object -TypeName ArgumentNullException -ArgumentList "ActualValue","Scriptblock not found. Input to 'Throw' and 'Not Throw' must be enclosed in curly braces.")
    }

    try {
        do {
            $null = & $ActualValue
        } until ($true)
    } catch {
        $actualExceptionWasThrown = $true
        $actualException = $_.Exception
        $actualExceptionMessage = $_.Exception.Message
        $actualErrorId = $_.FullyQualifiedErrorId
        $actualExceptionLine = (Get-ExceptionLineInfo $_.InvocationInfo) -replace [System.Environment]::NewLine,"$([System.Environment]::NewLine)    "
    }

    [bool] $succeeded = $false

    if ($Negate) {
        # this is for Should -Not -Throw. Once *any* exception was thrown we should fail the assertion
        # there is no point in filtering the exception, because there should be none
        $succeeded = -not $actualExceptionWasThrown
        if (-not $succeeded) {
            $failureMessage = "Expected no exception to be thrown, but an exception was thrown $actualExceptionLine."
            return New-Object psobject -Property @{
                Succeeded      = $succeeded
                FailureMessage = $failureMessage
            }
        } else {
            return New-Object psobject -Property @{
                Succeeded      = $true
            }
        }
    }

    # the rest is for Should -Throw, we must fail the assertion when no exception is thrown
    # or when the exception does not match our filter

    function Join-And ($Items, $Threshold=2) {

        if ($null -eq $items -or $items.count -lt $Threshold)
        {
            $items -join ', '
        }
        else
        {
            $c = $items.count
            ($items[0..($c-2)] -join ', ') + ' and ' + $items[-1]
        }
    }

    function Add-SpaceToNonEmptyString ([string]$Value) {
        if ($Value)
        {
            " $Value"
        }
    }

    $buts = @()
    $filters = @()

    $filterOnExceptionType = $null -ne $ExceptionType
    if ($filterOnExceptionType) {
        $filters += "with type {$ExceptionType}"

        if ($actualExceptionWasThrown -and $actualException -isnot $ExceptionType) {
            $buts += "the exception type was '{$($actualException.GetType())}'"
        }
    }

    $filterOnMessage = -not [string]::IsNullOrEmpty($ExpectedMessage -replace "\s")
    if ($filterOnMessage) {
        $filters += "with message '$ExpectedMessage'"
        if ($actualExceptionWasThrown -and (-not (Get-DoValuesMatch $actualExceptionMessage $ExpectedMessage))) {
            $buts += "the message was '$($actualExceptionMessage)'"
        }
    }

    $filterOnId = -not [string]::IsNullOrEmpty($ErrorId -replace "\s")
    if ($filterOnId) {
        $filters += "with FullyQualifiedErrorId '$ErrorId'"
        if ($actualExceptionWasThrown -and (-not (Get-DoValuesMatch $actualErrorId $ErrorId))) {
            $buts += "the FullyQualifiedErrorId was '$($actualErrorId)'"
        }
    }

    if (-not $actualExceptionWasThrown)
    {
        $buts += "no exception was thrown"
    }

    if ($buts.Count -ne 0) {
        $filter = Add-SpaceToNonEmptyString ( Join-And $filters -Threshold 3 )
        $but = Join-And $buts
        $failureMessage = "Expected an exception,$filter to be thrown, but $but. $actualExceptionLine".Trim()

        return New-Object psobject -Property @{
            Succeeded      = $false
            FailureMessage = $failureMessage
        }
    }

    return New-Object psobject -Property @{
        Succeeded      = $true
    }
}

function Get-DoValuesMatch($ActualValue, $ExpectedValue) {
    #user did not specify any message filter, so any message matches
    if ($null -eq $ExpectedValue ) { return $true }

    return $ActualValue.ToString().IndexOf($ExpectedValue, [System.StringComparison]::InvariantCultureIgnoreCase) -ge 0
}

function Get-ExceptionLineInfo($info) {
    # $info.PositionMessage has a leading blank line that we need to account for in PowerShell 2.0
    $positionMessage = $info.PositionMessage -split '\r?\n' -match '\S' -join [System.Environment]::NewLine
    return ($positionMessage -replace "^At ","from ")
}

function PesterThrowFailureMessage {
    # to make the should tests happy, for now
}

function NotPesterThrowFailureMessage {
    # to make the should tests happy, for now
}

Add-AssertionOperator -Name Throw `
                      -Test $function:PesterThrow
tools\Functions\Assertions\Set-TestInconclusive.ps1
function New-InconclusiveErrorRecord ([string] $Message, [string] $File, [string] $Line, [string] $LineText) {
    $exception = New-Object Exception $Message
    $errorID = 'PesterTestInconclusive'
    $errorCategory = [Management.Automation.ErrorCategory]::InvalidResult
    # we use ErrorRecord.TargetObject to pass structured information about the error to a reporting system.
    $targetObject = @{Message = $Message; File = $File; Line = $Line; LineText = $LineText}
    $errorRecord = New-Object Management.Automation.ErrorRecord $exception, $errorID, $errorCategory, $targetObject
    return $errorRecord
}

function Set-TestInconclusive {
<#

    .SYNOPSIS
    Set-TestInclusive used inside the It block will cause that the test will be
    considered as inconclusive.

    .DESCRIPTION
    If an Set-TestInconclusive is used inside It block, the test will always fails
    with an Inconclusive result. It's not a passed result, nor a failed result,
    but something in between � Inconclusive. It indicates that the results
    of the test could not be verified.

    .PARAMETER Message
    Value assigned to the Message parameter will be displayed in the the test result.

    .EXAMPLE

    Invoke-Pester

    Describe "Example" {

        It "Test what is inconclusive" {

            Set-TestInconclusive -Message "I'm inconclusive because I can."

        }

    }

    The test result.

    Describing Example
    [?] Test what is inconclusive 96ms
      I'm inconclusive because I can
      at line: 10 in C:\Users\<SOME_FOLDER>\Example.Tests.ps1
      10:         Set-TestInconclusive -Message "I'm inconclusive because I can"
    Tests completed in 408ms
    Tests Passed: 0, Failed: 0, Skipped: 0, Pending: 0, Inconclusive: 1

    .LINK
    https://github.com/pester/Pester/wiki/Set%E2%80%90TestInconclusive
#>
    [CmdletBinding()]
    param (
        [string] $Message
    )

    Assert-DescribeInProgress -CommandName Set-TestInconclusive
    $lineText = $MyInvocation.Line.TrimEnd($([System.Environment]::NewLine))
    $line = $MyInvocation.ScriptLineNumber
    $file = $MyInvocation.ScriptName

    throw ( New-InconclusiveErrorRecord -Message $Message -File $file -Line $line -LineText $lineText)
}
tools\Functions\Assertions\Should.ps1
function Parse-ShouldArgs([object[]] $shouldArgs) {
    if ($null -eq $shouldArgs) { $shouldArgs = @() }

    $parsedArgs = @{
        PositiveAssertion = $true
        ExpectedValue = $null
    }

    $assertionMethodIndex = 0
    $expectedValueIndex   = 1

    if ($shouldArgs.Count -gt 0 -and $shouldArgs[0] -eq "not") {
        $parsedArgs.PositiveAssertion = $false
        $assertionMethodIndex += 1
        $expectedValueIndex   += 1
    }

    if ($assertionMethodIndex -lt $shouldArgs.Count)
    {
        $parsedArgs.AssertionMethod = "$($shouldArgs[$assertionMethodIndex])"
    }
    else
    {
        throw 'You cannot call Should without specifying an assertion method.'
    }

    if ($expectedValueIndex -lt $shouldArgs.Count)
    {
        $parsedArgs.ExpectedValue = $shouldArgs[$expectedValueIndex]
    }

    return $parsedArgs
}

function Get-FailureMessage($assertionEntry, $negate, $value, $expected) {
    if ($negate)
    {
        $failureMessageFunction = $assertionEntry.GetNegativeFailureMessage
    }
    else
    {
        $failureMessageFunction = $assertionEntry.GetPositiveFailureMessage
    }

    return (& $failureMessageFunction $value $expected)
}

function New-ShouldErrorRecord ([string] $Message, [string] $File, [string] $Line, [string] $LineText) {
    $exception = & $SafeCommands['New-Object'] Exception $Message
    $errorID = 'PesterAssertionFailed'
    $errorCategory = [Management.Automation.ErrorCategory]::InvalidResult
    # we use ErrorRecord.TargetObject to pass structured information about the error to a reporting system.
    $targetObject = @{Message = $Message; File = $File; Line = $Line; LineText = $LineText}
    $errorRecord = & $SafeCommands['New-Object'] Management.Automation.ErrorRecord $exception, $errorID, $errorCategory, $targetObject
    return $errorRecord
}

function Should {
<#
    .SYNOPSIS
    Should is a keyword what is used to define an assertion inside It block.

    .DESCRIPTION
    Should is a keyword what is used to define an assertion inside the It block.
    Should provides assertion methods for verify assertion e.g. comparing objects.
    If assertion is not met the test fails and an exception is throwed up.

    Should can be used more than once in the It block if more than one assertion
    need to be verified. Each Should keywords need to be located in a new line.
    Test will be passed only when all assertion will be met (logical conjuction).

    .LINK
    about_Should
    about_Pester
#>

    [CmdletBinding(DefaultParameterSetName = 'Legacy')]
    param (
        [Parameter(ParameterSetName = 'Legacy', Position = 0)]
        [object] $LegacyArg1,

        [Parameter(ParameterSetName = 'Legacy', Position = 1)]
        [object] $LegacyArg2,

        [Parameter(ParameterSetName = 'Legacy', Position = 2)]
        [object] $LegacyArg3,

        [Parameter(ValueFromPipeline = $true)]
        [object] $ActualValue
    )

    dynamicparam
    {
        Get-AssertionDynamicParams
    }

    begin
    {
        #Assert-DescribeInProgress -CommandName Should

        $inputArray = New-Object System.Collections.ArrayList

        if ($PSCmdlet.ParameterSetName -eq 'Legacy')
        {
            $parsedArgs = Parse-ShouldArgs ($LegacyArg1, $LegacyArg2, $LegacyArg3)
            $entry = Get-AssertionOperatorEntry -Name $parsedArgs.AssertionMethod
            if ($null -eq $entry)
            {
                throw "'$($parsedArgs.AssertionMethod)' is not a valid Should operator."
            }
        }
    }

    process
    {
        $null = $inputArray.Add($ActualValue)
    }

    end
    {
        $lineNumber = $MyInvocation.ScriptLineNumber
        $lineText   = $MyInvocation.Line.TrimEnd("$([System.Environment]::NewLine)")
        $file       = $MyInvocation.ScriptName

        if ($PSCmdlet.ParameterSetName -eq 'Legacy')
        {
            if ($inputArray.Count -eq 0)
            {
                Invoke-LegacyAssertion $entry $parsedArgs $null $file $lineNumber $lineText
            }
            elseif ($entry.SupportsArrayInput)
            {
                Invoke-LegacyAssertion $entry $parsedArgs $inputArray.ToArray() $file $lineNumber $lineText
            }
            else
            {
                foreach ($object in $inputArray)
                {
                    Invoke-LegacyAssertion $entry $parsedArgs $object $file $lineNumber $lineText
                }
            }
        }
        else
        {
            $negate = $false
            if ($PSBoundParameters.ContainsKey('Not'))
            {
                $negate = [bool]$PSBoundParameters['Not']
            }

            $null = $PSBoundParameters.Remove('ActualValue')
            $null = $PSBoundParameters.Remove($PSCmdlet.ParameterSetName)
            $null = $PSBoundParameters.Remove('Not')

            $entry = Get-AssertionOperatorEntry -Name $PSCmdlet.ParameterSetName

            if ($inputArray.Count -eq 0)
            {
                Invoke-Assertion $entry $PSBoundParameters $null $file $lineNumber $lineText -Negate:$negate
            }
            elseif ($entry.SupportsArrayInput)
            {
                Invoke-Assertion $entry $PSBoundParameters $inputArray.ToArray() $file $lineNumber $lineText -Negate:$negate
            }
            else
            {
                foreach ($object in $inputArray)
                {
                    Invoke-Assertion $entry $PSBoundParameters $object $file $lineNumber $lineText -Negate:$negate
                }
            }
        }
    }
}

function Invoke-LegacyAssertion($assertionEntry, $shouldArgs, $valueToTest, $file, $lineNumber, $lineText)
{
    # $expectedValueSplat = @(
    #     if ($null -ne $shouldArgs.ExpectedValue)
    #     {
    #         ,$shouldArgs.ExpectedValue
    #     }
    # )

    $negate = -not $shouldArgs.PositiveAssertion

    $testResult = (& $assertionEntry.Test $valueToTest $shouldArgs.ExpectedValue -Negate:$negate)
    if (-not $testResult.Succeeded)
    {
        throw ( New-ShouldErrorRecord -Message $testResult.FailureMessage -File $file -Line $lineNumber -LineText $lineText )
    }
}

function Invoke-Assertion
{
    param (
        [object] $AssertionEntry,
        [System.Collections.IDictionary] $BoundParameters,
        [object] $valuetoTest,
        [string] $File,
        [int] $LineNumber,
        [string] $LineText,
        [switch] $Negate
    )

    $testResult = & $AssertionEntry.Test -ActualValue $valuetoTest -Negate:$Negate @BoundParameters
    if (-not $testResult.Succeeded)
    {
        throw ( New-ShouldErrorRecord -Message $testResult.FailureMessage -File $file -Line $lineNumber -LineText $lineText )
    }
}
tools\Functions\Context.ps1
function Context {
<#
.SYNOPSIS
Provides logical grouping of It blocks within a single Describe block.

.DESCRIPTION
Provides logical grouping of It blocks within a single Describe block.
Any Mocks defined inside a Context are removed at the end of the Context scope,
as are any files or folders added to the TestDrive during the Context block's
execution. Any BeforeEach or AfterEach blocks defined inside a Context also only
apply to tests within that Context .

.PARAMETER Name
The name of the Context. This is a phrase describing a set of tests within a describe.

.PARAMETER Tag
Optional parameter containing an array of strings.  When calling Invoke-Pester,
it is possible to specify a -Tag parameter which will only execute Context blocks
containing the same Tag.

.PARAMETER Fixture
Script that is executed. This may include setup specific to the context
and one or more It blocks that validate the expected outcomes.

.EXAMPLE
function Add-Numbers($a, $b) {
    return $a + $b
}

Describe "Add-Numbers" {

    Context "when root does not exist" {
         It "..." { ... }
    }

    Context "when root does exist" {
        It "..." { ... }
        It "..." { ... }
        It "..." { ... }
    }
}

.LINK
Describe
It
BeforeEach
AfterEach
about_Should
about_Mocking
about_TestDrive

#>
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string] $Name,

        [Alias('Tags')]
        [string[]] $Tag=@(),

        [Parameter(Position = 1)]
        [ValidateNotNull()]
        [ScriptBlock] $Fixture = $(Throw "No test script block is provided. (Have you put the open curly brace on the next line?)")
    )

    if ($null -eq (& $SafeCommands['Get-Variable'] -Name Pester -ValueOnly -ErrorAction $script:IgnoreErrorPreference))
    {
        # User has executed a test script directly instead of calling Invoke-Pester
        $Pester = New-PesterState -Path (& $SafeCommands['Resolve-Path'] .) -TestNameFilter $null -TagFilter @() -SessionState $PSCmdlet.SessionState
        $script:mockTable = @{}
    }

    DescribeImpl @PSBoundParameters -CommandUsed 'Context' -Pester $Pester -DescribeOutputBlock ${function:Write-Describe} -TestOutputBlock ${function:Write-PesterResult}
}
tools\Functions\Coverage.ps1
if ($PSVersionTable.PSVersion.Major -le 2)
{
    function Exit-CoverageAnalysis { }
    function Get-CoverageReport { }
    function Write-CoverageReport { }
    function Enter-CoverageAnalysis {
        param ( $CodeCoverage )

        if ($CodeCoverage) { & $SafeCommands['Write-Error'] 'Code coverage analysis requires PowerShell 3.0 or later.' }
    }

    return
}

function Enter-CoverageAnalysis
{
    [CmdletBinding()]
    param (
        [object[]] $CodeCoverage,
        [object] $PesterState
    )

    $coverageInfo =
    foreach ($object in $CodeCoverage)
    {
        Get-CoverageInfoFromUserInput -InputObject $object
    }

    $PesterState.CommandCoverage = @(Get-CoverageBreakpoints -CoverageInfo $coverageInfo)
}

function Exit-CoverageAnalysis
{
    param ([object] $PesterState)

    & $SafeCommands['Set-StrictMode'] -Off

    $breakpoints = @($PesterState.CommandCoverage.Breakpoint) -ne $null
    if ($breakpoints.Count -gt 0)
    {
        & $SafeCommands['Remove-PSBreakpoint'] -Breakpoint $breakpoints
    }
}

function Get-CoverageInfoFromUserInput
{
    param (
        [Parameter(Mandatory = $true)]
        [object]
        $InputObject
    )

    if ($InputObject -is [System.Collections.IDictionary])
    {
        $unresolvedCoverageInfo = Get-CoverageInfoFromDictionary -Dictionary $InputObject
    }
    else
    {
        $unresolvedCoverageInfo = New-CoverageInfo -Path ([string]$InputObject)
    }

    Resolve-CoverageInfo -UnresolvedCoverageInfo $unresolvedCoverageInfo
}

function New-CoverageInfo
{
    param ([string] $Path, [string] $Function = $null, [int] $StartLine = 0, [int] $EndLine = 0)

    return [pscustomobject]@{
        Path = $Path
        Function = $Function
        StartLine = $StartLine
        EndLine = $EndLine
    }
}

function Get-CoverageInfoFromDictionary
{
    param ([System.Collections.IDictionary] $Dictionary)

    [string] $path = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'Path', 'p'
    if ([string]::IsNullOrEmpty($path))
    {
        throw "Coverage value '$Dictionary' is missing required Path key."
    }

    $startLine = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'StartLine', 'Start', 's'
    $endLine = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'EndLine', 'End', 'e'
    [string] $function = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'Function', 'f'

    $startLine = Convert-UnknownValueToInt -Value $startLine -DefaultValue 0
    $endLine = Convert-UnknownValueToInt -Value $endLine -DefaultValue 0

    return New-CoverageInfo -Path $path -StartLine $startLine -EndLine $endLine -Function $function
}

function Convert-UnknownValueToInt
{
    param ([object] $Value, [int] $DefaultValue = 0)

    try
    {
        return [int] $Value
    }
    catch
    {
        return $DefaultValue
    }
}

function Resolve-CoverageInfo
{
    param ([psobject] $UnresolvedCoverageInfo)

    $path = $UnresolvedCoverageInfo.Path

    try
    {
        $resolvedPaths = & $SafeCommands['Resolve-Path'] -Path $path -ErrorAction Stop
    }
    catch
    {
        & $SafeCommands['Write-Error'] "Could not resolve coverage path '$path': $($_.Exception.Message)"
        return
    }

    $filePaths =
    foreach ($resolvedPath in $resolvedPaths)
    {
        $item = & $SafeCommands['Get-Item'] -LiteralPath $resolvedPath
        if ($item -is [System.IO.FileInfo] -and ('.ps1','.psm1') -contains $item.Extension)
        {
            $item.FullName
        }
        elseif (-not $item.PsIsContainer)
        {
            & $SafeCommands['Write-Warning'] "CodeCoverage path '$path' resolved to a non-PowerShell file '$($item.FullName)'; this path will not be part of the coverage report."
        }
    }

    $params = @{
        StartLine = $UnresolvedCoverageInfo.StartLine
        EndLine = $UnresolvedCoverageInfo.EndLine
        Function = $UnresolvedCoverageInfo.Function
    }

    foreach ($filePath in $filePaths)
    {
        $params['Path'] = $filePath
        New-CoverageInfo @params
    }
}

function Get-CoverageBreakpoints
{
    [CmdletBinding()]
    param (
        [object[]] $CoverageInfo
    )

    $fileGroups = @($CoverageInfo | & $SafeCommands['Group-Object'] -Property Path)
    foreach ($fileGroup in $fileGroups)
    {
        & $SafeCommands['Write-Verbose'] "Initializing code coverage analysis for file '$($fileGroup.Name)'"
        $totalCommands = 0
        $analyzedCommands = 0

        :commandLoop
        foreach ($command in Get-CommandsInFile -Path $fileGroup.Name)
        {
            $totalCommands++

            foreach ($coverageInfoObject in $fileGroup.Group)
            {
                if (Test-CoverageOverlapsCommand -CoverageInfo $coverageInfoObject -Command $command)
                {
                    $analyzedCommands++
                    New-CoverageBreakpoint -Command $command
                    continue commandLoop
                }
            }
        }

        & $SafeCommands['Write-Verbose'] "Analyzing $analyzedCommands of $totalCommands commands in file '$($fileGroup.Name)' for code coverage"
    }
}

function Get-CommandsInFile
{
    param ([string] $Path)

    $errors = $null
    $tokens = $null
    $ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref] $tokens, [ref] $errors)

    if ($PSVersionTable.PSVersion.Major -ge 5)
    {
        # In PowerShell 5.0, dynamic keywords for DSC configurations are represented by the DynamicKeywordStatementAst
        # class.  They still trigger breakpoints, but are not a child class of CommandBaseAst anymore.

        $predicate = {
            $args[0] -is [System.Management.Automation.Language.DynamicKeywordStatementAst] -or
            $args[0] -is [System.Management.Automation.Language.CommandBaseAst]
        }
    }
    else
    {
        $predicate = { $args[0] -is [System.Management.Automation.Language.CommandBaseAst] }
    }

    $searchNestedScriptBlocks = $true
    $ast.FindAll($predicate, $searchNestedScriptBlocks)
}

function Test-CoverageOverlapsCommand
{
    param ([object] $CoverageInfo, [System.Management.Automation.Language.Ast] $Command)

    if ($CoverageInfo.Function)
    {
        Test-CommandInsideFunction -Command $Command -Function $CoverageInfo.Function
    }
    else
    {
        Test-CoverageOverlapsCommandByLineNumber @PSBoundParameters
    }

}

function Test-CommandInsideFunction
{
    param ([System.Management.Automation.Language.Ast] $Command, [string] $Function)

    for ($ast = $Command; $null -ne $ast; $ast = $ast.Parent)
    {
        $functionAst = $ast -as [System.Management.Automation.Language.FunctionDefinitionAst]
        if ($null -ne $functionAst -and $functionAst.Name -like $Function)
        {
            return $true
        }
    }

    return $false
}

function Test-CoverageOverlapsCommandByLineNumber
{
    param ([object] $CoverageInfo, [System.Management.Automation.Language.Ast] $Command)

    $commandStart = $Command.Extent.StartLineNumber
    $commandEnd = $Command.Extent.EndLineNumber
    $coverStart = $CoverageInfo.StartLine
    $coverEnd = $CoverageInfo.EndLine

    # An EndLine value of 0 means to cover the entire rest of the file from StartLine
    # (which may also be 0)
    if ($coverEnd -le 0) { $coverEnd = [int]::MaxValue }

    return (Test-RangeContainsValue -Value $commandStart -Min $coverStart -Max $coverEnd) -or
           (Test-RangeContainsValue -Value $commandEnd -Min $coverStart -Max $coverEnd)
}

function Test-RangeContainsValue
{
    param ([int] $Value, [int] $Min, [int] $Max)
    return $Value -ge $Min -and $Value -le $Max
}

function New-CoverageBreakpoint
{
    param ([System.Management.Automation.Language.Ast] $Command)

    if (IsIgnoredCommand -Command $Command) { return }

    $params = @{
        Script = $Command.Extent.File
        Line   = $Command.Extent.StartLineNumber
        Column = $Command.Extent.StartColumnNumber
        Action = { }
    }

    $breakpoint = & $SafeCommands['Set-PSBreakpoint'] @params

    [pscustomobject] @{
        File       = $Command.Extent.File
        Function   = Get-ParentFunctionName -Ast $Command
        Line       = $Command.Extent.StartLineNumber
        Command    = Get-CoverageCommandText -Ast $Command
        Breakpoint = $breakpoint
    }
}

function IsIgnoredCommand
{
    param ([System.Management.Automation.Language.Ast] $Command)

    if (-not $Command.Extent.File)
    {
        # This can happen if the script contains "configuration" or any similarly implemented
        # dynamic keyword.  PowerShell modifies the script code and reparses it in memory, leading
        # to AST elements with no File in their Extent.
        return $true
    }

    if ($PSVersionTable.PSVersion.Major -ge 4)
    {
        if ($Command.Extent.Text -eq 'Configuration')
        {
            # More DSC voodoo.  Calls to "configuration" generate breakpoints, but their HitCount
            # stays zero (even though they are executed.)  For now, ignore them, unless we can come
            # up with a better solution.
            return $true
        }

        if (IsChildOfHashtableDynamicKeyword -Command $Command)
        {
            # The lines inside DSC resource declarations don't trigger their breakpoints when executed,
            # just like the "configuration" keyword itself.  I don't know why, at this point, but just like
            # configuration, we'll ignore it so it doesn't clutter up the coverage analysis with useless junk.
            return $true
        }
    }

    if (IsClosingLoopCondition -Command $Command)
    {
        # For some reason, the closing expressions of do/while and do/until loops don't trigger their breakpoints.
        # To avoid useless clutter, we'll ignore those lines as well.
        return $true
    }

    return $false
}

function IsChildOfHashtableDynamicKeyword
{
    param ([System.Management.Automation.Language.Ast] $Command)

    for ($ast = $Command.Parent; $null -ne $ast; $ast = $ast.Parent)
    {
        if ($PSVersionTable.PSVersion.Major -ge 5)
        {
            # The ast behaves differently for DSC resources with version 5+.  There's a new DynamicKeywordStatementAst class,
            # and they no longer are represented by CommandAst objects.

            if ($ast -is [System.Management.Automation.Language.DynamicKeywordStatementAst] -and
                $ast.CommandElements[-1] -is [System.Management.Automation.Language.HashtableAst])
            {
                return $true
            }
        }
        else
        {
            if ($ast -is [System.Management.Automation.Language.CommandAst] -and
                $null -ne $ast.DefiningKeyword -and
                $ast.DefiningKeyword.BodyMode -eq [System.Management.Automation.Language.DynamicKeywordBodyMode]::Hashtable)
            {
                return $true
            }
        }
    }

    return $false
}

function IsClosingLoopCondition
{
    param ([System.Management.Automation.Language.Ast] $Command)

    $ast = $Command

    while ($null -ne $ast.Parent)
    {
        if (($ast.Parent -is [System.Management.Automation.Language.DoWhileStatementAst] -or
            $ast.Parent -is [System.Management.Automation.Language.DoUntilStatementAst]) -and
            $ast.Parent.Condition -eq $ast)
        {
            return $true
        }

        $ast = $ast.Parent
    }

    return $false
}

function Get-ParentFunctionName
{
    param ([System.Management.Automation.Language.Ast] $Ast)

    $parent = $Ast.Parent

    while ($null -ne $parent -and $parent -isnot [System.Management.Automation.Language.FunctionDefinitionAst])
    {
        $parent = $parent.Parent
    }

    if ($null -eq $parent)
    {
        return ''
    }
    else
    {
        return $parent.Name
    }
}

function Get-CoverageCommandText
{
    param ([System.Management.Automation.Language.Ast] $Ast)

    $reportParentExtentTypes = @(
        [System.Management.Automation.Language.ReturnStatementAst]
        [System.Management.Automation.Language.ThrowStatementAst]
        [System.Management.Automation.Language.AssignmentStatementAst]
        [System.Management.Automation.Language.IfStatementAst]
    )

    $parent = Get-ParentNonPipelineAst -Ast $Ast

    if ($null -ne $parent)
    {
        if ($parent -is [System.Management.Automation.Language.HashtableAst])
        {
            return Get-KeyValuePairText -HashtableAst $parent -ChildAst $Ast
        }
        elseif ($reportParentExtentTypes -contains $parent.GetType())
        {
            return $parent.Extent.Text
        }
    }

    return $Ast.Extent.Text
}

function Get-ParentNonPipelineAst
{
    param ([System.Management.Automation.Language.Ast] $Ast)

    $parent = $null
    if ($null -ne $Ast) { $parent = $Ast.Parent }

    while ($parent -is [System.Management.Automation.Language.PipelineAst])
    {
        $parent = $parent.Parent
    }

    return $parent
}

function Get-KeyValuePairText
{
    param (
        [System.Management.Automation.Language.HashtableAst] $HashtableAst,
        [System.Management.Automation.Language.Ast] $ChildAst
    )

    & $SafeCommands['Set-StrictMode'] -Off

    foreach ($keyValuePair in $HashtableAst.KeyValuePairs)
    {
        if ($keyValuePair.Item2.PipelineElements -contains $ChildAst)
        {
            return '{0} = {1}' -f $keyValuePair.Item1.Extent.Text, $keyValuePair.Item2.Extent.Text
        }
    }

    # This shouldn't happen, but just in case, default to the old output of just the expression.
    return $ChildAst.Extent.Text
}

function Get-CoverageMissedCommands
{
    param ([object[]] $CommandCoverage)
    $CommandCoverage | & $SafeCommands['Where-Object'] { $_.Breakpoint.HitCount -eq 0 }
}

function Get-CoverageHitCommands
{
    param ([object[]] $CommandCoverage)
    $CommandCoverage | & $SafeCommands['Where-Object'] { $_.Breakpoint.HitCount -gt 0 }
}

function Get-CoverageReport
{
    param ([object] $PesterState)

    $totalCommandCount = $PesterState.CommandCoverage.Count

    $missedCommands = @(Get-CoverageMissedCommands -CommandCoverage $PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command)
    $hitCommands = @(Get-CoverageHitCommands -CommandCoverage $PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command)
    $analyzedFiles = @($PesterState.CommandCoverage | & $SafeCommands['Select-Object'] -ExpandProperty File -Unique)
    $fileCount = $analyzedFiles.Count

    $executedCommandCount = $totalCommandCount - $missedCommands.Count

    [pscustomobject] @{
        NumberOfCommandsAnalyzed = $totalCommandCount
        NumberOfFilesAnalyzed    = $fileCount
        NumberOfCommandsExecuted = $executedCommandCount
        NumberOfCommandsMissed   = $missedCommands.Count
        MissedCommands           = $missedCommands
        HitCommands              = $hitCommands
        AnalyzedFiles            = $analyzedFiles
    }
}

function Get-CommonParentPath
{
    param ([string[]] $Path)

    $pathsToTest = @(
        $Path |
        Normalize-Path |
        & $SafeCommands['Select-Object'] -Unique
    )

    if ($pathsToTest.Count -gt 0)
    {
        $parentPath = & $SafeCommands['Split-Path'] -Path $pathsToTest[0] -Parent

        while ($parentPath.Length -gt 0)
        {
            $nonMatches = $pathsToTest -notmatch "^$([regex]::Escape($parentPath))"

            if ($nonMatches.Count -eq 0)
            {
                return $parentPath
            }
            else
            {
                $parentPath = & $SafeCommands['Split-Path'] -Path $parentPath -Parent
            }
        }
    }

    return [string]::Empty
}

function Get-RelativePath
{
    param ( [string] $Path, [string] $RelativeTo )
    return $Path -replace "^$([regex]::Escape("$RelativeTo$([System.IO.Path]::DirectorySeparatorChar)"))?"
}

function Normalize-Path
{
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('PSPath', 'FullName')]
        [string[]] $Path
    )

    # Split-Path and Join-Path will replace any AltDirectorySeparatorChar instances with the DirectorySeparatorChar
    # (Even if it's not the one that the split / join happens on.)  So splitting / rejoining a path will give us
    # consistent separators for later string comparison.

    process
    {
        if ($null -ne $Path)
        {
            foreach ($p in $Path)
            {
                $normalizedPath = & $SafeCommands['Split-Path'] $p -Leaf

                if ($normalizedPath -ne $p)
                {
                    $parent = & $SafeCommands['Split-Path'] $p -Parent
                    $normalizedPath = & $SafeCommands['Join-Path'] $parent $normalizedPath
                }

                $normalizedPath
            }
        }
    }
}

function Get-JaCoCoReportXml {
    param (
        [parameter(Mandatory=$true)]
        $PesterState,
        [parameter(Mandatory=$true)]
        [object] $CoverageReport
    )

    if ($null -eq $CoverageReport -or ($pester.Show -eq [Pester.OutputTypes]::None) -or $CoverageReport.NumberOfCommandsAnalyzed -eq 0)
    {
        return
    }

    $allCommands = $CoverageReport.MissedCommands + $CoverageReport.HitCommands
    [long]$totalFunctions = ($allCommands | ForEach-Object {$_.File+$_.Function} | Select-Object -Unique ).Count
    [long]$hitFunctions = ($CoverageReport.HitCommands | ForEach-Object {$_.File+$_.Function} | Select-Object -Unique ).Count
    [long]$missedFunctions = $totalFunctions - $hitFunctions

    [long]$totalLines = ($allCommands | ForEach-Object {$_.File+$_.Line} | Select-Object -Unique ).Count
    [long]$hitLines = ($CoverageReport.HitCommands | ForEach-Object {$_.File+$_.Line} | Select-Object -Unique ).Count
    [long]$missedLines = $totalLines - $hitLines

    [long]$totalFiles = $CoverageReport.NumberOfFilesAnalyzed

    [long]$hitFiles = $(Measure-Object -InputObject $($CoverageReport.HitCommands | ForEach-Object {$_.File} | Select-Object -Unique )).Count
    [long]$missedFiles = $totalFiles - $hitFiles

    $now = & $SafeCommands['Get-Date']
    $nineteenseventy = & $SafeCommands['Get-Date'] -Date "01/01/1970"
    [long]$endTime =  [math]::Floor((new-timespan -start $nineteenseventy -end $now).TotalSeconds * 1000)
    [long]$startTime = [math]::Floor($endTime - $PesterState.Time.TotalSeconds*1000)

    # the JaCoCo xml format without the doctype, as the XML stuff does not like DTD's.
    $jaCoCoReport = "<?xml version=""1.0"" encoding=""UTF-8"" standalone=""no""?>$([System.Environment]::NewLine)"
    $jaCoCoReport += "<report name="""">$([System.Environment]::NewLine)"
    $jaCoCoReport += "<sessioninfo id=""this"" start="""" dump="""" />$([System.Environment]::NewLine)"
    $jaCoCoReport += "<counter type=""INSTRUCTION"" missed="""" covered=""""/>$([System.Environment]::NewLine)"
    $jaCoCoReport += "<counter type=""LINE"" missed="""" covered=""""/>$([System.Environment]::NewLine)"
    $jaCoCoReport += "<counter type=""METHOD"" missed="""" covered=""""/>$([System.Environment]::NewLine)"
    $jaCoCoReport += "<counter type=""CLASS"" missed="""" covered=""""/>$([System.Environment]::NewLine)"
    $jaCoCoReport += "</report>"

    [xml] $jaCoCoReportXml = $jaCoCoReport
    $jaCoCoReportXml.report.name = "Pester ($now)"
    $jaCoCoReportXml.report.sessioninfo.start=$startTime.ToString()
    $jaCoCoReportXml.report.sessioninfo.dump=$endTime.ToString()
    $jaCoCoReportXml.report.counter[0].missed = $CoverageReport.MissedCommands.Count.ToString()
    $jaCoCoReportXml.report.counter[0].covered = $CoverageReport.HitCommands.Count.ToString()
    $jaCoCoReportXml.report.counter[1].missed = $missedLines.ToString()
    $jaCoCoReportXml.report.counter[1].covered = $hitLines.ToString()
    $jaCoCoReportXml.report.counter[2].missed = $missedFunctions.ToString()
    $jaCoCoReportXml.report.counter[2].covered = $hitFunctions.ToString()
    $jaCoCoReportXml.report.counter[3].missed = $missedFiles.ToString()
    $jaCoCoReportXml.report.counter[3].covered = $hitFiles.ToString()
    # There is no pretty way to insert the Doctype, as microsoft has deprecated the DTD stuff.
    $jaCoCoReportDocType = "<!DOCTYPE report PUBLIC ""-//JACOCO//DTD Report 1.0//EN"" ""report.dtd"">$([System.Environment]::NewLine)"
    return $jaCocoReportXml.OuterXml.Insert(54, $jaCoCoReportDocType)
}
tools\Functions\Describe.ps1
function Describe {
<#
.SYNOPSIS
Creates a logical group of tests.

.DESCRIPTION
Creates a logical group of tests. All Mocks and TestDrive contents

defined within a Describe block are scoped to that Describe; they
will no longer be present when the Describe block exits.  A Describe
block may contain any number of Context and It blocks.

.PARAMETER Name
The name of the test group. This is often an expressive phrase describing
the scenario being tested.

.PARAMETER Fixture
The actual test script. If you are following the AAA pattern (Arrange-Act-Assert),
this typically holds the arrange and act sections. The Asserts will also lie
in this block but are typically nested each in its own It block. Assertions are
typically performed by the Should command within the It blocks.

.PARAMETER Tag
Optional parameter containing an array of strings.  When calling Invoke-Pester,
it is possible to specify a -Tag parameter which will only execute Describe blocks
containing the same Tag.

.EXAMPLE
function Add-Numbers($a, $b) {
    return $a + $b
}

Describe "Add-Numbers" {
    It "adds positive numbers" {
        $sum = Add-Numbers 2 3
        $sum | Should -Be 5
    }

    It "adds negative numbers" {
        $sum = Add-Numbers (-2) (-2)
        $sum | Should -Be (-4)
    }

    It "adds one negative number to positive number" {
        $sum = Add-Numbers (-2) 2
        $sum | Should -Be 0
    }

    It "concatenates strings if given strings" {
        $sum = Add-Numbers two three
        $sum | Should -Be "twothree"
    }
}

.LINK
It
Context
Invoke-Pester
about_Should
about_Mocking
about_TestDrive

#>

    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string] $Name,

        [Alias('Tags')]
        [string[]] $Tag=@(),

        [Parameter(Position = 1)]
        [ValidateNotNull()]
        [ScriptBlock] $Fixture = $(Throw "No test script block is provided. (Have you put the open curly brace on the next line?)")
    )

    if ($null -eq (& $SafeCommands['Get-Variable'] -Name Pester -ValueOnly -ErrorAction $script:IgnoreErrorPreference))
    {
        # User has executed a test script directly instead of calling Invoke-Pester
        $Pester = New-PesterState -Path (& $SafeCommands['Resolve-Path'] .) -TestNameFilter $null -TagFilter @() -SessionState $PSCmdlet.SessionState
        $script:mockTable = @{}
    }

    DescribeImpl @PSBoundParameters -CommandUsed 'Describe' -Pester $Pester -DescribeOutputBlock ${function:Write-Describe} -TestOutputBlock ${function:Write-PesterResult}
}

function DescribeImpl {
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string] $Name,

        [Alias('Tags')]
        $Tag=@(),

        [Parameter(Position = 1)]
        [ValidateNotNull()]
        [ScriptBlock] $Fixture = $(Throw "No test script block is provided. (Have you put the open curly brace on the next line?)"),

        [string] $CommandUsed = 'Describe',

        $Pester,

        [scriptblock] $DescribeOutputBlock,

        [scriptblock] $TestOutputBlock,

        [switch] $NoTestDrive
    )

    Assert-DescribeInProgress -CommandName $CommandUsed

    if ($Pester.TestGroupStack.Count -eq 2)
    {
        if($Pester.TestNameFilter-and -not ($Pester.TestNameFilter | & $SafeCommands['Where-Object'] { $Name -like $_ }))
        {
            #skip this test
            return
        }

        if($Pester.TagFilter -and @(& $SafeCommands['Compare-Object'] $Tag $Pester.TagFilter -IncludeEqual -ExcludeDifferent).count -eq 0) {return}
        if($Pester.ExcludeTagFilter -and @(& $SafeCommands['Compare-Object'] $Tag $Pester.ExcludeTagFilter -IncludeEqual -ExcludeDifferent).count -gt 0) {return}
    }
    else
    {
        if ($PSBoundParameters.ContainsKey('Tag'))
        {
            Write-Warning "${CommandUsed} '$Name': Tags are only effective on the outermost test group, for now."
        }
    }

    $Pester.EnterTestGroup($Name, $CommandUsed)

    if ($null -ne $DescribeOutputBlock)
    {
        & $DescribeOutputBlock $Name $CommandUsed
    }

    $testDriveAdded = $false
    try
    {
        try
        {
            if (-not $NoTestDrive)
            {
                if (-not (Test-Path TestDrive:\))
                {
                    New-TestDrive
                    $testDriveAdded = $true
                }
                else
                {
                    $TestDriveContent = Get-TestDriveChildItem
                }
            }

            Add-SetupAndTeardown -ScriptBlock $Fixture
            Invoke-TestGroupSetupBlocks

            do
            {
                $null = & $Fixture
            } until ($true)
        }
        finally
        {
            Invoke-TestGroupTeardownBlocks
            if (-not $NoTestDrive)
            {
                if ($testDriveAdded)
                {
                    Remove-TestDrive
                }
                else
                {
                    Clear-TestDrive -Exclude ($TestDriveContent | & $SafeCommands['Select-Object'] -ExpandProperty FullName)
                }
            }
        }
    }
    catch
    {
        $firstStackTraceLine = $_.InvocationInfo.PositionMessage.Trim() -split "$([System.Environment]::NewLine)" | & $SafeCommands['Select-Object'] -First 1
        $Pester.AddTestResult("Error occurred in $CommandUsed block", "Failed", $null, $_.Exception.Message, $firstStackTraceLine, $null, $null, $_)
        if ($null -ne $TestOutputBlock)
        {
            & $TestOutputBlock $Pester.TestResult[-1]
        }
    }

    Exit-MockScope

    $Pester.LeaveTestGroup($Name, $CommandUsed)
}

# Name is now misleading; rename later.  (Many files touched to change this.)
function Assert-DescribeInProgress
{
    param ($CommandName)
    if ($null -eq $Pester)
    {
        throw "The $CommandName command may only be used from a Pester test script."
    }
}
tools\Functions\Environment.ps1
function GetPesterPsVersion
{
    # accessing the value indirectly so it can be mocked
    (Get-Variable 'PSVersionTable' -ValueOnly).PsVersion.Major
}

function GetPesterOs
{
    # Prior to v6, PowerShell was solely on Windows. In v6, the $IsWindows variable was introduced.
    if ((GetPesterPsVersion) -lt 6)
    {
        'Windows'
    }
    elseif (Get-Variable -Name 'IsWindows' -ErrorAction 'SilentlyContinue' -ValueOnly )
    {
        'Windows'
    }
    elseif (Get-Variable -Name 'IsMacOS' -ErrorAction 'SilentlyContinue' -ValueOnly )
    {
        'macOS'
    }
    elseif (Get-Variable -Name 'IsLinux' -ErrorAction 'SilentlyContinue' -ValueOnly )
    {
        'Linux'
    }
    else
    {
        throw "Unsupported Operating system!"
    }
}

function Get-TempDirectory
{
    if ((GetPesterOs) -eq 'Windows')
    {
        $env:TEMP
    }
    else
    {
        '/tmp'
    }
}
tools\Functions\Gherkin.ps1
if (($PSVersionTable.ContainsKey('PSEdition')) -and ($PSVersionTable.PSEdition -eq 'Core')) {
    & $SafeCommands["Add-Type"] -Path "${Script:PesterRoot}/lib/core/Gherkin.dll"
} else {
    & $SafeCommands["Import-Module"] -Name "${Script:PesterRoot}/lib/legacy/Gherkin.dll"
}

$GherkinSteps = @{}
$GherkinHooks = @{
    BeforeEachFeature  = @()
    BeforeEachScenario = @()
    AfterEachFeature   = @()
    AfterEachScenario  = @()
}

function Invoke-GherkinHook {
    <#
        .SYNOPSIS
            Internal function to run the various gherkin hooks

        .PARAMETER Hook
            The name of the hook to run

        .PARAMETER Name
            The name of the feature or scenario the hook is being invoked for

        .PARAMETER Tags
            Tags for filtering hooks
    #>
    [CmdletBinding()]
    param([string]$Hook, [string]$Name, [string[]]$Tags)

    if ($GherkinHooks.${Hook}) {
        foreach ($GherkinHook in $GherkinHooks.${Hook}) {
            if ($GherkinHook.Tags -and $Tags) {
                :tags foreach ($hookTag in $GherkinHook.Tags) {
                    foreach ($testTag in $Tags) {
                        if ($testTag -match "^($hookTag)$") {
                            & $hook.Script $Name
                            break :tags
                        }
                    }
                }
            } elseif ($GherkinHook.Tags) {
                # If the hook has tags, it can't run if the step doesn't
            } else {
                & $GherkinHook.Script $Name
            }
        } # @{ Tags = $Tags; Script = $Test }
    }
}

function Invoke-Gherkin {
    <#
        .SYNOPSIS
            Invokes Pester to run all tests defined in .feature files

        .DESCRIPTION
            Upon calling Invoke-Gherkin, all files that have a name matching *.feature in the current folder (and child folders recursively), will be parsed and executed.

            If ScenarioName is specified, only scenarios which match the provided name(s) will be run.
            If FailedLast is specified, only scenarios which failed the previous run will be re-executed.

            Optionally, Pester can generate a report of how much code is covered by the tests, and information about any commands which were not executed.
        .PARAMETER FailedLast
            Rerun only the scenarios which failed last time
        .PARAMETER Path
            This parameter indicates which feature files should be tested.

            Aliased to 'Script' for compatibility with Pester, but does not support hashtables, since feature files don't take parameters.

        .PARAMETER ScenarioName
            When set, invokes testing of scenarios which match this name.

            Aliased to 'Name' and 'TestName' for compatibility with Pester.

        .PARAMETER EnableExit
            Will cause Invoke-Gherkin to exit with a exit code equal to the number of failed tests once all tests have been run.
            Use this to "fail" a build when any tests fail.

        .PARAMETER Tag
            Filters Scenarios and Features and runs only the ones tagged with the specified tags.

        .PARAMETER ExcludeTag
            Informs Invoke-Gherkin to not run blocks tagged with the tags specified.

        .PARAMETER CodeCoverage
            Instructs Pester to generate a code coverage report in addition to running tests.  You may pass either hashtables or strings to this parameter.

            If strings are used, they must be paths (wildcards allowed) to source files, and all commands in the files are analyzed for code coverage.

            By passing hashtables instead, you can limit the analysis to specific lines or functions within a file.
            Hashtables must contain a Path key (which can be abbreviated to just "P"), and may contain Function (or "F"), StartLine (or "S"),
            and EndLine ("E") keys to narrow down the commands to be analyzed.
            If Function is specified, StartLine and EndLine are ignored.

            If only StartLine is defined, the entire script file starting with StartLine is analyzed.
            If only EndLine is present, all lines in the script file up to and including EndLine are analyzed.

            Both Function and Path (as well as simple strings passed instead of hashtables) may contain wildcards.

        .PARAMETER Strict
            Makes Pending and Skipped tests to Failed tests. Useful for continuous integration where you need
            to make sure all tests passed.

        .PARAMETER OutputFile
            The path to write a report file to. If this path is not provided, no log will be generated.

        .PARAMETER OutputFormat
            The format for output (LegacyNUnitXml or NUnitXml), defaults to NUnitXml

        .PARAMETER Quiet
            Disables the output Pester writes to screen. No other output is generated unless you specify PassThru,
            or one of the Output parameters.

        .PARAMETER PesterOption
            Sets advanced options for the test execution. Enter a PesterOption object,
            such as one that you create by using the New-PesterOption cmdlet, or a hash table
            in which the keys are option names and the values are option values.
            For more information on the options available, see the help for New-PesterOption.

        .PARAMETER Show
            Customizes the output Pester writes to the screen. Available options are None, Default,
            Passed, Failed, Pending, Skipped, Inconclusive, Describe, Context, Summary, Header, All, Fails.

            The options can be combined to define presets.
            Common use cases are:

            None - to write no output to the screen.
            All - to write all available information (this is default option).
            Fails - to write everything except Passed (but including Describes etc.).

            A common setting is also Failed, Summary, to write only failed tests and test summary.

            This parameter does not affect the PassThru custom object or the XML output that
            is written when you use the Output parameters.

        .PARAMETER PassThru
            Returns a custom object (PSCustomObject) that contains the test results.
            By default, Invoke-Gherkin writes to the host program, not to the output stream (stdout).
            If you try to save the result in a variable, the variable is empty unless you
            use the PassThru parameter.
            To suppress the host output, use the Quiet parameter.

        .EXAMPLE
            Invoke-Gherkin

            This will find all *.feature specifications and execute their tests. No exit code will be returned and no log file will be saved.

        .EXAMPLE
            Invoke-Gherkin -Path ./tests/Utils*

            This will run all *.feature specifications under ./Tests that begin with Utils.

        .EXAMPLE
            Invoke-Gherkin -ScenarioName "Add Numbers"

            This will only run the Scenario named "Add Numbers"

        .EXAMPLE
            Invoke-Gherkin -EnableExit -OutputXml "./artifacts/TestResults.xml"

            This runs all tests from the current directory downwards and writes the results according to the NUnit schema to artifacts/TestResults.xml just below the current directory. The test run will return an exit code equal to the number of test failures.

        .EXAMPLE
            Invoke-Gherkin -CodeCoverage 'ScriptUnderTest.ps1'

            Runs all *.feature specifications in the current directory, and generates a coverage report for all commands in the "ScriptUnderTest.ps1" file.

        .EXAMPLE
            Invoke-Gherkin -CodeCoverage @{ Path = 'ScriptUnderTest.ps1'; Function = 'FunctionUnderTest' }

            Runs all *.feature specifications in the current directory, and generates a coverage report for all commands in the "FunctionUnderTest" function in the "ScriptUnderTest.ps1" file.

        .EXAMPLE
            Invoke-Gherkin -CodeCoverage @{ Path = 'ScriptUnderTest.ps1'; StartLine = 10; EndLine = 20 }

            Runs all *.feature specifications in the current directory, and generates a coverage report for all commands on lines 10 through 20 in the "ScriptUnderTest.ps1" file.

        .LINK
            Invoke-Pester
            https://kevinmarquette.github.io/2017-03-17-Powershell-Gherkin-specification-validation/
            https://kevinmarquette.github.io/2017-04-30-Powershell-Gherkin-advanced-features/
    #>
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param(
        [Parameter(Mandatory = $True, ParameterSetName = "RetestFailed")]
        [switch]$FailedLast,

        [Parameter(Position = 0, Mandatory = $False)]
        [Alias('Script', 'relative_path')]
        [string]$Path = $Pwd,

        [Parameter(Position = 1, Mandatory = $False)]
        [Alias("Name", "TestName")]
        [string[]]$ScenarioName,

        [Parameter(Position = 2, Mandatory = $False)]
        [switch]$EnableExit,

        [Parameter(Position = 4, Mandatory = $False)]
        [Alias('Tags')]
        [string[]]$Tag,

        [string[]]$ExcludeTag,

        [object[]] $CodeCoverage = @(),

        [Switch]$Strict,

        [string] $OutputFile,

        [ValidateSet('NUnitXml')]
        [string] $OutputFormat = 'NUnitXml',

        [Switch]$Quiet,

        [object]$PesterOption,

        [Pester.OutputTypes]$Show = 'All',

        [switch]$PassThru
    )
    begin {
        & $SafeCommands["Import-LocalizedData"] -BindingVariable Script:ReportStrings -BaseDirectory $PesterRoot -FileName Gherkin.psd1 -ErrorAction SilentlyContinue

        #Fallback to en-US culture strings
        If ([String]::IsNullOrEmpty($ReportStrings)) {

            & $SafeCommands["Import-LocalizedData"] -BaseDirectory $PesterRoot -BindingVariable Script:ReportStrings -UICulture 'en-US' -FileName Gherkin.psd1 -ErrorAction Stop

        }

        # Make sure broken tests don't leave you in space:
        $CWD = [Environment]::CurrentDirectory
        $Location = & $SafeCommands["Get-Location"]
        [Environment]::CurrentDirectory = & $SafeCommands["Get-Location"] -PSProvider FileSystem

        $script:GherkinSteps = @{}
        $script:GherkinHooks = @{
            BeforeEachFeature  = @()
            BeforeEachScenario = @()
            AfterEachFeature   = @()
            AfterEachScenario  = @()
        }
    }
    end {
        if ($PSBoundParameters.ContainsKey('Quiet')) {
            & $SafeCommands["Write-Warning"] 'The -Quiet parameter has been deprecated; please use the new -Show parameter instead. To get no output use -Show None.'
            & $SafeCommands["Start-Sleep"] -Seconds 2

            if (!$PSBoundParameters.ContainsKey('Show')) {
                $Show = [Pester.OutputTypes]::None
            }
        }

        if ($PSCmdlet.ParameterSetName -eq "RetestFailed" -and $FailedLast) {
            $ScenarioName = $script:GherkinFailedLast
            if (!$ScenarioName) {
                throw "There are no existing failed tests to re-run."
            }
        }

        $pester = New-PesterState -TagFilter @($Tag -split "\s+") -ExcludeTagFilter ($ExcludeTag -split "\s") -TestNameFilter $ScenarioName -SessionState $PSCmdlet.SessionState -Strict $Strict  -Show $Show -PesterOption $PesterOption |
            & $SafeCommands["Add-Member"] -MemberType NoteProperty -Name Features -Value (& $SafeCommands["New-Object"] System.Collections.Generic.List[PSObject] ) -PassThru |
            & $SafeCommands["Add-Member"] -MemberType ScriptProperty -Name FailedScenarios -PassThru -Value {
                $Names = $this.TestResult | & $SafeCommands["Group-Object"] Describe |
                                            & $SafeCommands["Where-Object"] { $_.Group |
                                            & $SafeCommands["Where-Object"] { -not $_.Passed } } |
                                            & $SafeCommands["Select-Object"] -ExpandProperty Name
                $this.Features | Select-Object -ExpandProperty Scenarios | & $SafeCommands["Where-Object"] { $Names -contains $_.Name }
        } |
            & $SafeCommands["Add-Member"] -MemberType ScriptProperty -Name PassedScenarios -PassThru -Value {
                $Names = $this.TestResult | & $SafeCommands["Group-Object"] Describe |
                                            & $SafeCommands["Where-Object"] { -not ($_.Group |
                                            & $SafeCommands["Where-Object"] { -not $_.Passed }) } |
                                            & $SafeCommands["Select-Object"] -ExpandProperty Name
                $this.Features | Select-Object -ExpandProperty Scenarios | & $SafeCommands["Where-Object"] { $Names -contains $_.Name }
        }

        Write-PesterStart $pester $Path

        Enter-CoverageAnalysis -CodeCoverage $CodeCoverage -PesterState $pester

        foreach ($FeatureFile in & $SafeCommands["Get-ChildItem"] $Path -Filter "*.feature" -Recurse ) {
            Invoke-GherkinFeature $FeatureFile -Pester $pester
        }

        # Remove all the steps
        $Script:GherkinSteps.Clear()

        $Location | & $SafeCommands["Set-Location"]
        [Environment]::CurrentDirectory = $CWD

        $pester | Write-PesterReport
        $coverageReport = Get-CoverageReport -PesterState $pester
        Write-CoverageReport -CoverageReport $coverageReport
        Exit-CoverageAnalysis -PesterState $pester

        if (& $SafeCommands["Get-Variable"]-Name OutputFile -ValueOnly -ErrorAction $script:IgnoreErrorPreference) {
            Export-PesterResults -PesterState $pester -Path $OutputFile -Format $OutputFormat
        }

        if ($PassThru) {
            # Remove all runtime properties like current* and Scope
            $properties = @(
                "Path", "Features", "TagFilter", "TestNameFilter", "TotalCount", "PassedCount", "FailedCount", "Time", "TestResult", "PassedScenarios", "FailedScenarios"

                if ($CodeCoverage) {
                    @{ Name = 'CodeCoverage'; Expression = { $coverageReport } }
                }
            )
            $result = $pester | & $SafeCommands["Select-Object"] -Property $properties
            $result.PSTypeNames.Insert(0, "Pester.Gherkin.Results")
            $result
        }
        $script:GherkinFailedLast = @($pester.FailedScenarios.Name)
        if ($EnableExit) {
            Exit-WithCode -FailedCount $pester.FailedCount
        }
    }
}

function Import-GherkinSteps {
    <#
        .SYNOPSIS
            Internal function for importing the script steps from a directory tree
        .PARAMETER StepPath
            The folder which contains step files
        .PARAMETER Pester
            Pester
    #>

    [CmdletBinding()]
    param(

        [Alias("PSPath")]
        [Parameter(Mandatory = $True, Position = 0, ValueFromPipelineByPropertyName = $True)]
        $StepPath,

        [PSObject]$Pester
    )
    begin {
        # Remove all existing steps
        $Script:GherkinSteps.Clear()
        # Remove all existing hooks
        $Script:GherkinHooks.Clear()
    }
    process {
        foreach ($StepFile in & $SafeCommands["Get-ChildItem"] $StepPath -Filter "*.?teps.ps1" -Include "*.[sS]teps.ps1" -Recurse) {
            $invokeTestScript = {
                [CmdletBinding()]
                param (
                    [Parameter(Position = 0)]
                    [string] $Path
                )

                & $Path
            }

            Set-ScriptBlockScope -ScriptBlock $invokeTestScript -SessionState $Pester.SessionState

            & $invokeTestScript $StepFile.FullName
        }

        & $SafeCommands["Write-Verbose"] "Loaded $($Script:GherkinSteps.Count) step definitions from $(@($StepFiles).Count) steps file(s)"
    }
}

function Import-GherkinFeature {
    <#
        .SYNOPSIS
            Internal function to import a Gherkin feature file. Wraps Gherkin.Parse

        .PARAMETER Path
            The path to the feature file to import

        .PARAMETER Pester
            Internal Pester object. For internal use only
    #>
    [CmdletBinding()]
    param($Path, [PSObject]$Pester)
    $Background = $null

    $parser = & $SafeCommands["New-Object"] Gherkin.Parser
    $Feature = $parser.Parse($Path).Feature | Convert-Tags
    $Scenarios = $(
        :scenarios foreach ($Child in $Feature.Children) {
            $null = & $SafeCommands["Add-Member"] -MemberType "NoteProperty" -InputObject $Child.Location -Name "Path" -Value $Path
            foreach ($Step in $Child.Steps) {
             $null = & $SafeCommands["Add-Member"] -MemberType "NoteProperty" -InputObject $Step.Location -Name "Path" -Value $Path
        }

        switch ($Child.Keyword.Trim()) {
            "Scenario" {
                $Scenario = Convert-Tags -InputObject $Child -BaseTags $Feature.Tags
            }
            "Scenario Outline" {
                $Scenario = Convert-Tags -InputObject $Child -BaseTags $Feature.Tags
            }
            "Background" {
                $Background = Convert-Tags -InputObject $Child -BaseTags $Feature.Tags
                continue scenarios
            }
            default {
                & $SafeCommands["Write-Warning"] "Unexpected Feature Child: $_"
            }
        }

        if ($Scenario.Examples) {
            foreach ($ExampleSet in $Scenario.Examples) {
                ${Column Names} = @($ExampleSet.TableHeader.Cells | & $SafeCommands["Select-Object"] -ExpandProperty Value)
                $NamesPattern = "<(?:" + (${Column Names} -join "|") + ")>"
                $Steps = foreach ($Example in $ExampleSet.TableBody) {
                    foreach ($Step in $Scenario.Steps) {
                        [string]$StepText = $Step.Text
                        if ($StepText -match $NamesPattern) {
                            for ($n = 0; $n -lt ${Column Names}.Length; $n++) {
                                $Name = ${Column Names}[$n]
                                if ($Example.Cells[$n].Value -and $StepText -match "<${Name}>") {
                                    $StepText = $StepText -replace "<${Name}>", $Example.Cells[$n].Value
                                }
                            }
                        }
                        if ($StepText -ne $Step.Text) {
                                    & $SafeCommands["New-Object"] Gherkin.Ast.Step $Step.Location, $Step.Keyword.Trim(), $StepText, $Step.Argument
                        } else {
                            $Step
                        }
                    }
                }
                $ScenarioName = $Scenario.Name
                if ($ExampleSet.Name) {
                    $ScenarioName = $ScenarioName + "`n  Examples:" + $ExampleSet.Name.Trim()
                }
                & $SafeCommands["New-Object"] Gherkin.Ast.Scenario $ExampleSet.Tags, $Scenario.Location, $Scenario.Keyword.Trim(), $ScenarioName, $Scenario.Description, $Steps | Convert-Tags $Scenario.Tags
            }
        } else {
            $Scenario
        }
    }
    )

    & $SafeCommands["Add-Member"] -MemberType NoteProperty -InputObject $Feature -Name Scenarios -Value $Scenarios -Force
    return $Feature, $Background, $Scenarios
}

function Invoke-GherkinFeature {
    <#
        .SYNOPSIS
            Internal function to (parse and) run a whole feature file
    #>
    [CmdletBinding()]
    param(
        [Alias("PSPath")]
        [Parameter(Mandatory = $True, Position = 0, ValueFromPipelineByPropertyName = $True)]
        [IO.FileInfo]$FeatureFile,

        [PSObject]$Pester
    )
    $Pester.EnterTestGroup($FeatureFile.FullName, 'Script')
    # Make sure broken tests don't leave you in space:
    $CWD = [Environment]::CurrentDirectory
    $Location = & $SafeCommands["Get-Location"]
    [Environment]::CurrentDirectory = & $SafeCommands["Get-Location"] -PSProvider FileSystem

    try {
        $Parent = & $SafeCommands["Split-Path"] $FeatureFile.FullName
        Import-GherkinSteps -StepPath $Parent -Pester $pester
        $Feature, $Background, $Scenarios = Import-GherkinFeature -Path $FeatureFile.FullName -Pester $Pester
    } catch [Gherkin.ParserException] {
        & $SafeCommands["Write-Error"] -Exception $_.Exception -Message "Skipped '$($FeatureFile.FullName)' because of parser error.`n$(($_.Exception.Errors | & $SafeCommands["Select-Object"] -Expand Message) -join "`n`n")"
        continue
    }

    $null = $Pester.Features.Add($Feature)
    Invoke-GherkinHook BeforeEachFeature $Feature.Name $Feature.Tags

    # Test the name filter first, since it will probably return one single item
    if ($Pester.TestNameFilter) {
        $Scenarios = foreach ($nameFilter in $Pester.TestNameFilter) {
            $Scenarios | & $SafeCommands["Where-Object"] { $_.Name -like $NameFilter }
        }
        $Scenarios = $Scenarios | & $SafeCommands["Get-Unique"]
    }

    # if($Pester.TagFilter -and @(Compare-Object $Tags $Pester.TagFilter -IncludeEqual -ExcludeDifferent).count -eq 0) {return}
    if ($Pester.TagFilter) {
        $Scenarios = $Scenarios | & $SafeCommands["Where-Object"] { & $SafeCommands["Compare-Object"] $_.Tags $Pester.TagFilter -IncludeEqual -ExcludeDifferent }
    }

    # if($Pester.ExcludeTagFilter -and @(Compare-Object $Tags $Pester.ExcludeTagFilter -IncludeEqual -ExcludeDifferent).count -gt 0) {return}
    if ($Pester.ExcludeTagFilter) {
        $Scenarios = $Scenarios | & $SafeCommands["Where-Object"] { !(& $SafeCommands["Compare-Object"] $_.Tags $Pester.ExcludeTagFilter -IncludeEqual -ExcludeDifferent) }
    }

    if ($Scenarios) {
        Write-Describe $Feature
    }

    try {
        foreach ($Scenario in $Scenarios) {
            Invoke-GherkinScenario $Pester $Scenario $Background
        }
    } catch {
        $firstStackTraceLine = $_.ScriptStackTrace -split '\r?\n' | & $SafeCommands["Select-Object"] -First 1
        $Pester.AddTestResult("Error occurred in test script '$($Feature.Path)'", "Failed", $null, $_.Exception.Message, $firstStackTraceLine, $null, $null, $_)

        # This is a hack to ensure that XML output is valid for now.  The test-suite names come from the Describe attribute of the TestResult
        # objects, and a blank name is invalid NUnit XML.  This will go away when we promote test scripts to have their own test-suite nodes,
        # planned for v4.0
        $Pester.TestResult[-1].Describe = "Error in $($Feature.Path)"

        $Pester.TestResult[-1] | Write-PesterResult
    } finally {
        $Location | & $SafeCommands["Set-Location"]
        [Environment]::CurrentDirectory = $CWD
    }

    Invoke-GherkinHook AfterEachFeature $Feature.Name $Feature.Tags

    $Pester.LeaveTestGroup($FeatureFile.FullName, 'Script')

}

function Invoke-GherkinScenario {
    <#
        .SYNOPSIS
            Internal function to (parse and) run a single scenario
    #>
    [CmdletBinding()]
    param(
        $Pester, $Scenario, $Background
    )
    $Pester.EnterTestGroup($Scenario.Name, 'Scenario')
    try {
        Write-Context $Scenario

        $script:mockTable = @{}

        # Create a clean variable scope in each scenario
        $script:GherkinScenarioScope = New-Module NestedGherkin { }
        $script:GherkinSessionState = $Script:GherkinScenarioScope.SessionState

        #Wait-Debugger

        New-TestDrive
        Invoke-GherkinHook BeforeEachScenario $Scenario.Name $Scenario.Tags

        # If there's a background, run that before the test, but after hooks
        if ($Background) {
            foreach ($Step in $Background.Steps) {
                # Run Background steps -Background so they don't output in each scenario
                Invoke-GherkinStep -Step $Step -Pester $Pester -Scenario $GherkinSessionState -Visible
            }
        }

        foreach ($Step in $Scenario.Steps) {
            Invoke-GherkinStep -Step $Step -Pester $Pester -Scenario $GherkinSessionState -Visible
        }

        Invoke-GherkinHook AfterEachScenario $Scenario.Name $Scenario.Tags
    } catch {
        $firstStackTraceLine = $_.ScriptStackTrace -split '\r?\n' | & $SafeCommands["Select-Object"] -First 1
        $Pester.AddTestResult("Error occurred in scenario '$($Scenario.Name)'", "Failed", $null, $_.Exception.Message, $firstStackTraceLine, $null, $null, $_)

        # This is a hack to ensure that XML output is valid for now.  The test-suite names come from the Describe attribute of the TestResult
        # objects, and a blank name is invalid NUnit XML.  This will go away when we promote test scripts to have their own test-suite nodes,
        # planned for v4.0
        $Pester.TestResult[-1].Describe = "Error in $($Scenario.Name)"

        $Pester.TestResult[-1] | Write-PesterResult
    }

    Remove-TestDrive
    $Pester.LeaveTestGroup($Scenario.Name, 'Scenario')
    Exit-MockScope
}

function Find-GherkinStep {
    <#
        .SYNOPSIS
            Find a step implmentation that matches a given step

        .DESCRIPTION
            Searches the *.Steps.ps1 files in the BasePath (current working directory, by default)
            Returns the step(s) that match

        .PARAMETER Step
            The text from feature file

        .PARAMETER BasePath
            The path to search for step implementations.
    #>

    [CmdletBinding()]
    param(

        [string]$Step,

        [string]$BasePath = $Pwd
    )

    $OriginalGherkinSteps = $Script:GherkinSteps
    try {
        Import-GherkinSteps $BasePath -Pester $PSCmdlet

        $KeyWord, $StepText = $Step -split "(?<=^(?:Given|When|Then|And|But))\s+"
        if (!$StepText) {
            $StepText = $KeyWord
        }

        & $SafeCommands["Write-Verbose"] "Searching for '$StepText' in $($Script:GherkinSteps.Count) steps"
        $(
            foreach ($StepCommand in $Script:GherkinSteps.Keys) {
                & $SafeCommands["Write-Verbose"] "... $StepCommand"
                if ($StepText -match "^${StepCommand}$") {
                    & $SafeCommands["Write-Verbose"] "Found match: $StepCommand"
                    $StepCommand | & $SafeCommands["Add-Member"] -MemberType NoteProperty -Name MatchCount -Value $Matches.Count -PassThru
                }
            }
        ) | & $SafeCommands["Sort-Object"] MatchCount | & $SafeCommands["Select-Object"] @{
            Name       = 'Step'
            Expression = { $Step }
        }, @{
            Name       = 'Source'
            Expression = { $Script:GherkinSteps["$_"].Source }
        }, @{
            Name       = 'Implementation'
            Expression = { $Script:GherkinSteps["$_"] }
        } -First 1

        # $StepText = "{0} {1} {2}" -f $Step.Keyword.Trim(), $Step.Text, $Script:GherkinSteps[$StepCommand].Source

    } finally {
        $Script:GherkinSteps = $OriginalGherkinSteps
    }
}

function Invoke-GherkinStep {
    <#
        .SYNOPSIS
            Run a single gherkin step, given the text from the feature file

        .PARAMETER Step
            The text of the step for matching against regex patterns in step implementations

        .PARAMETER Visible
            If Visible is true, the results of this step will be shown in the test report

        .PARAMETER Pester
            Pester state object. For internal use only

        .PARAMETER ScenarioState
            Gherkin state object. For internal use only
    #>
    [CmdletBinding()]
    param (
        $Step,

        [Switch]$Visible,

        $Pester,

        $ScenarioState
    )
    if ($Step -is [string]) {
        $KeyWord, $StepText = $Step -split "(?<=^(?:Given|When|Then|And|But))\s+"
        if (!$StepText) {
            $StepText = $KeyWord
            $Keyword = "Step"
        }
        $Step = @{ Text = $StepText; Keyword = $Keyword }
    }
    $DisplayText = "{0} {1}" -f $Step.Keyword.Trim(), $Step.Text

    $PesterErrorRecord = $null
    $Source = $null
    $Elapsed = $null
    $NamedArguments = @{}

    try {
        #  Pick the match with the least grouping wildcards in it...
        $StepCommand = $(
            foreach ($StepCommand in $Script:GherkinSteps.Keys) {
                if ($Step.Text -match "^${StepCommand}$") {
                    $StepCommand | & $SafeCommands["Add-Member"] -MemberType NoteProperty -Name MatchCount -Value $Matches.Count -PassThru
                }
            }
        ) | & $SafeCommands["Sort-Object"] MatchCount | & $SafeCommands["Select-Object"] -First 1

        if (!$StepCommand) {
            $PesterErrorRecord = New-InconclusiveErrorRecord -Message "Could not find implementation for step!" -File $Step.Location.Path -Line $Step.Location.Line -LineText $DisplayText
        } else {

            $NamedArguments, $Parameters = Get-StepParameters $Step $StepCommand
            $watch = & $SafeCommands["New-Object"] System.Diagnostics.Stopwatch
            $watch.Start()
            try {
                # Invoke-GherkinHook BeforeStep $Step.Text $Step.Tags

                if ($NamedArguments.Count) {
                    if ($NamedArguments.ContainsKey("Table")) {
                        $DisplayText += "..."
                    }
                    $ScriptBlock = { . $Script:GherkinSteps.$StepCommand @NamedArguments @Parameters }
                } else {
                    $ScriptBlock = { . $Script:GherkinSteps.$StepCommand @Parameters }
                }
                Set-ScriptBlockScope -ScriptBlock $Script:GherkinSteps.$StepCommand -SessionState $ScenarioState

                $null = & $ScriptBlock
            } catch {
                $PesterErrorRecord = $_
            }
            $watch.Stop()
            $Elapsed = $watch.Elapsed
            $Source = $Script:GherkinSteps[$StepCommand].Source
        }
    } catch {
        $PesterErrorRecord = $_
    }

    if ($Pester -and $Visible) {
        for ($p = 0; $p -lt $Parameters.Count; $p++) {
            $NamedArguments."Unnamed-$p" = $Parameters[$p]
        }

        # Normally, PesterErrorRecord is an ErrorRecord. Sometimes, it's an exception which HAS A ErrorRecord
        if ($PesterErrorRecord.ErrorRecord) {
            $PesterErrorRecord = $PesterErrorRecord.ErrorRecord
        }

        ${Pester Result} = ConvertTo-PesterResult -ErrorRecord $PesterErrorRecord

        # For Gherkin, we want to show the step, but not pretend to be a StackTrace
        if (${Pester Result}.Result -eq 'Inconclusive') {
            ${Pester Result}.StackTrace = "At " + $Step.Keyword.Trim() + ', ' + $Step.Location.Path + ': line ' + $Step.Location.Line
        } else {
            # Unless we really are a StackTrace...
            ${Pester Result}.StackTrace += "`nFrom " + $Step.Location.Path + ': line ' + $Step.Location.Line
        }
        $Pester.AddTestResult($DisplayText, ${Pester Result}.Result, $Elapsed, $PesterErrorRecord.Exception.Message, ${Pester Result}.StackTrace, $Source, $NamedArguments, $PesterErrorRecord )
        $Pester.TestResult[-1] | Write-PesterResult
    }
}

function Get-StepParameters {
    <#
        .SYNOPSIS
            Internal function for determining parameters for a step implementation
        .PARAMETER Step
            The parsed step from the feature file

        .PARAMETER CommandName
            The text of the best matching step
    #>
    param($Step, $CommandName)
    $Null = $Step.Text -match $CommandName

    $NamedArguments = @{}
    $Parameters = @{}
    foreach ($kv in $Matches.GetEnumerator()) {
        switch ($kv.Name -as [int]) {
            0       {  } # toss zero (where it matches the whole string)
            $null   { $NamedArguments.($kv.Name) = $ExecutionContext.InvokeCommand.ExpandString($kv.Value)       }
            default { $Parameters.([int]$kv.Name) = $ExecutionContext.InvokeCommand.ExpandString($kv.Value) }
        }
    }
    $Parameters = @($Parameters.GetEnumerator() | & $SafeCommands["Sort-Object"] Name | & $SafeCommands["Select-Object"] -ExpandProperty Value)

    # TODO: Convert parsed tables to tables....
    if ($Step.Argument -is [Gherkin.Ast.DataTable]) {
        $NamedArguments.Table = $Step.Argument.Rows | ConvertTo-HashTableArray
    }
    if ($Step.Argument -is [Gherkin.Ast.DocString]) {
        # trim empty matches if we're attaching DocStringArgument
        $Parameters = @( $Parameters | & $SafeCommands["Where-Object"] { $_.Length } ) + $Step.Argument.Content
    }

    return @($NamedArguments, $Parameters)
}

function Convert-Tags {
    <#
        .SYNOPSIS
            Internal function for tagging Gherkin feature files (including inheritance from the feature)
    #>
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $true)]
        $InputObject,

        [Parameter(Position = 0)]
        [string[]]$BaseTags = @()
    )
    process {
        # Adapt the Gherkin .Tags property to the way we prefer it...
        [string[]]$Tags = foreach ($tag in $InputObject.Tags | Where-Object { $_ }) {
            $tag.Name.TrimStart("@")
        }
        & $SafeCommands["Add-Member"] -MemberType NoteProperty -InputObject $InputObject -Name Tags -Value ([string[]]($Tags + $BaseTags)) -Force
        $InputObject
    }
}

function ConvertTo-HashTableArray {
    <#
        .SYNOPSIS
            Internal function for converting Gherkin AST tables to arrays of hashtables for splatting
    #>
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $true)]
        [Gherkin.Ast.TableRow[]]$InputObject
    )
    begin {
        ${Column Names} = @()
        ${Result Table} = @()
    }
    process {
        # Convert the first table row into headers:
        ${InputObject Rows} = @($InputObject)
        if (!${Column Names}) {
            & $SafeCommands["Write-Verbose"] "Reading Names from Header"
            ${InputObject Header}, ${InputObject Rows} = ${InputObject Rows}
            ${Column Names} = ${InputObject Header}.Cells | & $SafeCommands["Select-Object"] -ExpandProperty Value
        }

        & $SafeCommands["Write-Verbose"] "Processing $(${InputObject Rows}.Length) Rows"
        foreach (${InputObject row} in ${InputObject Rows}) {
            ${Pester Result} = @{}
            for ($n = 0; $n -lt ${Column Names}.Length; $n++) {
                ${Pester Result}.Add(${Column Names}[$n], ${InputObject row}.Cells[$n].Value)
            }
            ${Result Table} += @(${Pester Result})
        }
    }
    end {
        ${Result Table}
    }
}

tools\Functions\GherkinHook.ps1
function BeforeEachFeature {
    <#
        .SYNOPSIS
        Defines a ScriptBlock hook to run before each feature to set up the test environment

        .DESCRIPTION
        BeforeEachFeature hooks are run before each feature that is in (or above) the folder where the hook is defined.

        This is a convenience method, provided because unlike traditional RSpec Pester,
        there is not a simple test script where you can put setup and clean up.

        .PARAMETER Tags
        Optional tags. If set, this hook only runs for features with matching tags

        .PARAMETER Script
        The ScriptBlock to run for the hook

        .LINK
        AfterEachFeature
        BeforeEachScenario
        AfterEachScenario
    #>
    [CmdletBinding(DefaultParameterSetName="All")]
    param(

        [Parameter(Mandatory=$True, Position=0, ParameterSetName="Tags")]
        [String[]]$Tags = @(),

        [Parameter(Mandatory=$True, Position=1, ParameterSetName="Tags")]
        [Parameter(Mandatory=$True, Position=0, ParameterSetName="All")]
        [ScriptBlock]$Script
    )

    # This shouldn't be necessary, but PowerShell 2 is BAF
    if ($PSCmdlet.ParameterSetName -eq "Tags") {
        ${Script:GherkinHooks}.BeforeEachFeature += @( @{ Tags = $Tags; Script = $Script } )
    } else {
        ${Script:GherkinHooks}.BeforeEachFeature += @( @{ Tags = @(); Script = $Script } )
    }
}

function AfterEachFeature {
    <#
        .SYNOPSIS
        Defines a ScriptBlock hook to run at the very end of a test run

        .DESCRIPTION
        AfterEachFeature hooks are run after each feature that is in (or above) the folder where the hook is defined.

        This is a convenience method, provided because unlike traditional RSpec Pester,
        there is not a simple test script where you can put setup and clean up.

        .PARAMETER Tags
        Optional tags. If set, this hook only runs for features with matching tags.

        .PARAMETER Script
        The ScriptBlock to run for the hook

        .LINK
            BeforeEachFeature
            BeforeEachScenario
            AfterEachScenario
    #>
    [CmdletBinding(DefaultParameterSetName="All")]
    param(

        [Parameter(Mandatory=$True, Position=0, ParameterSetName="Tags")]
        [String[]]$Tags = @(),

        [Parameter(Mandatory=$True, Position=1, ParameterSetName="Tags")]
        [Parameter(Mandatory=$True, Position=0, ParameterSetName="All")]
        [ScriptBlock]$Script
    )

    # This shouldn't be necessary, but PowerShell 2 is BAF
    if ($PSCmdlet.ParameterSetName -eq "Tags") {
        ${Script:GherkinHooks}.AfterEachFeature += @( @{ Tags = $Tags; Script = $Script } )
    } else {
        ${Script:GherkinHooks}.AfterEachFeature += @( @{ Tags = @(); Script = $Script } )
    }
}

function BeforeEachScenario {
    <#
        .SYNOPSIS
        Defines a ScriptBlock hook to run before each scenario to set up the test environment

        .DESCRIPTION
        BeforeEachScenario hooks are run before each scenario that is in (or above) the folder where the hook is defined.

        You should not normally need this, because it overlaps significantly with the "Background" feature in the gherkin language.

        This is a convenience method, provided because unlike traditional RSpec Pester,
        there is not a simple test script where you can put setup and clean up.

        .PARAMETER Tags
        Optional tags. If set, this hook only runs for features with matching tags

        .PARAMETER Script
        The ScriptBlock to run for the hook

        .LINK
        AfterEachFeature
        BeforeEachScenario
        AfterEachScenario
    #>
    [CmdletBinding(DefaultParameterSetName="All")]
    param(

        [Parameter(Mandatory=$True, Position=0, ParameterSetName="Tags")]
        [String[]]$Tags = @(),

        [Parameter(Mandatory=$True, Position=1, ParameterSetName="Tags")]
        [Parameter(Mandatory=$True, Position=0, ParameterSetName="All")]
        [ScriptBlock]$Script
    )

    # This shouldn't be necessary, but PowerShell 2 is BAF
    if ($PSCmdlet.ParameterSetName -eq "Tags") {
        ${Script:GherkinHooks}.BeforeEachScenario += @( @{ Tags = $Tags; Script = $Script } )
    } else {
        ${Script:GherkinHooks}.BeforeEachScenario += @( @{ Tags = @(); Script = $Script } )
    }
}

function AfterEachScenario {
    <#
        .SYNOPSIS
        Defines a ScriptBlock hook to run after each scenario to set up the test environment

        .DESCRIPTION
        AfterEachScenario hooks are run after each Scenario that is in (or above) the folder where the hook is defined.

        This is a convenience method, provided because unlike traditional RSpec Pester,
        there is not a simple test script where you can put setup and clean up.

        .PARAMETER Tags
        Optional tags. If set, this hook only runs for features with matching tags

        .PARAMETER Script
        The ScriptBlock to run for the hook

        .LINK
            BeforeEachFeature
            BeforeEachScenario
            AfterEachScenario
    #>
    [CmdletBinding(DefaultParameterSetName="All")]
    param(
        [Parameter(Mandatory=$True, Position=0, ParameterSetName="Tags")]
        [String[]]$Tags = @(),

        [Parameter(Mandatory=$True, Position=1, ParameterSetName="Tags")]
        [Parameter(Mandatory=$True, Position=0, ParameterSetName="All")]
        [ScriptBlock]$Script
    )

    # This shouldn't be necessary, but PowerShell 2 is BAF
    if($PSCmdlet.ParameterSetName -eq "Tags") {
        ${Script:GherkinHooks}.AfterEachScenario += @( @{ Tags = $Tags; Script = $Script } )
    } else {
        ${Script:GherkinHooks}.AfterEachScenario += @( @{ Tags = @(); Script = $Script } )
    }
}
tools\Functions\GherkinStep.ps1
function GherkinStep {
<#

.SYNOPSIS
A step in a test, also known as a Given, When, or Then
.DESCRIPTION
Pester doesn't technically distinguish between the three kinds of steps.
However, we strongly recommend that you do!
These words were carefully selected to convey meaning which is crucial to get you into the BDD mindset.

In BDD, we drive development by not first stating the requirements, and then defining steps which can be
executed in a manner that is similar to unit tests.

.PARAMETER Name
The name of a gherkin step is actually a regular expression, from which capturing groups
are cast and passed to the parameters in the ScriptBlock

.PARAMETER Test
The ScriptBlock which defines this step. May accept parameters from regular expression
capturing groups (named or not), or from tables or multiline strings.

.LINK
about_gherkin
Invoke-GherkinStep
https://sites.google.com/site/unclebobconsultingllc/the-truth-about-bdd

#>
    param(

        [Parameter(Mandatory=$True, Position=0)]
        [String]$Name,


        [Parameter(Mandatory=$True, Position=1)]
        [ScriptBlock]$Test
    )
    # We need to be able to look up where this step is defined
    $Definition = (& $SafeCommands["Get-PSCallStack"])[1]
    $RelativePath = & $SafeCommands["Resolve-Path"] $Definition.ScriptName -relative
    $Source = "{0}: line {1}" -f $RelativePath, $Definition.ScriptLineNumber

    $Script:GherkinSteps.${Name} = $Test | & $SafeCommands["Add-Member"] -MemberType NoteProperty -Name Source -Value $Source -PassThru
}

Set-Alias Given GherkinStep
Set-Alias When GherkinStep
Set-Alias Then GherkinStep
Set-Alias And GherkinStep
Set-Alias But GherkinStep
tools\Functions\In.ps1
function In {
<#
.SYNOPSIS
A convenience function that executes a script from a specified path.

.DESCRIPTION
Before the script block passed to the execute parameter is invoked,
the current location is set to the path specified. Once the script
block has been executed, the location will be reset to the location
the script was in prior to calling In.

.PARAMETER Path
The path that the execute block will be executed in.

.PARAMETER execute
The script to be executed in the path provided.

#>
    [CmdletBinding()]
    param(
        $path,
        [ScriptBlock] $execute
    )
    Assert-DescribeInProgress -CommandName In

    $old_pwd = $pwd
    & $SafeCommands['Push-Location'] $path
    $pwd = $path
    try {
        & $execute
    } finally {
        & $SafeCommands['Pop-Location']
        $pwd = $old_pwd
    }
}
tools\Functions\InModuleScope.ps1
function InModuleScope
{
<#
.SYNOPSIS
   Allows you to execute parts of a test script within the
   scope of a PowerShell script module.
.DESCRIPTION
   By injecting some test code into the scope of a PowerShell
   script module, you can use non-exported functions, aliases
   and variables inside that module, to perform unit tests on
   its internal implementation.

   InModuleScope may be used anywhere inside a Pester script,
   either inside or outside a Describe block.
.PARAMETER ModuleName
   The name of the module into which the test code should be
   injected. This module must already be loaded into the current
   PowerShell session.
.PARAMETER ScriptBlock
   The code to be executed within the script module.
.EXAMPLE
    # The script module:
    function PublicFunction
    {
        # Does something
    }

    function PrivateFunction
    {
        return $true
    }

    Export-ModuleMember -Function PublicFunction

    # The test script:

    Import-Module MyModule

    InModuleScope MyModule {
        Describe 'Testing MyModule' {
            It 'Tests the Private function' {
                PrivateFunction | Should -Be $true
            }
        }
    }

    Normally you would not be able to access "PrivateFunction" from
    the PowerShell session, because the module only exported
    "PublicFunction".  Using InModuleScope allowed this call to
    "PrivateFunction" to work successfully.
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $ModuleName,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock
    )

    if ($null -eq (& $SafeCommands['Get-Variable'] -Name Pester -ValueOnly -ErrorAction $script:IgnoreErrorPreference))
    {
        # User has executed a test script directly instead of calling Invoke-Pester
        $Pester = New-PesterState -Path (& $SafeCommands['Resolve-Path'] .) -TestNameFilter $null -TagFilter @() -ExcludeTagFilter @() -SessionState $PSCmdlet.SessionState
        $script:mockTable = @{}
    }

    $module = Get-ScriptModule -ModuleName $ModuleName -ErrorAction Stop

    $originalState = $Pester.SessionState
    $originalScriptBlockScope = Get-ScriptBlockScope -ScriptBlock $ScriptBlock

    try
    {
        $Pester.SessionState = $module.SessionState

        Set-ScriptBlockScope -ScriptBlock $ScriptBlock -SessionState $module.SessionState

        do
        {
            & $ScriptBlock
        } until ($true)
    }
    finally
    {
        $Pester.SessionState = $originalState
        Set-ScriptBlockScope -ScriptBlock $ScriptBlock -SessionStateInternal $originalScriptBlockScope
    }
}

function Get-ScriptModule
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $ModuleName
    )

    try
    {
        $modules = @(& $SafeCommands['Get-Module'] -Name $ModuleName -All -ErrorAction Stop)
    }
    catch
    {
        throw "No module named '$ModuleName' is currently loaded."
    }

    $scriptModules = @($modules | & $SafeCommands['Where-Object'] { $_.ModuleType -eq 'Script' })

    if ($scriptModules.Count -gt 1)
    {
        throw "Multiple Script modules named '$ModuleName' are currently loaded.  Make sure to remove any extra copies of the module from your session before testing."
    }

    if ($scriptModules.Count -eq 0)
    {
        $actualTypes = @(
            $modules |
            & $SafeCommands['Where-Object'] { $_.ModuleType -ne 'Script' } |
            & $SafeCommands['Select-Object'] -ExpandProperty ModuleType -Unique
        )

        $actualTypes = $actualTypes -join ', '

        throw "Module '$ModuleName' is not a Script module.  Detected modules of the following types: '$actualTypes'"
    }

    return $scriptModules[0]
}
tools\Functions\It.ps1
function It {
<#
.SYNOPSIS
Validates the results of a test inside of a Describe block.

.DESCRIPTION
The It command is intended to be used inside of a Describe or Context Block.
If you are familiar with the AAA pattern (Arrange-Act-Assert), the body of
the It block is the appropriate location for an assert. The convention is to
assert a single expectation for each It block. The code inside of the It block
should throw a terminating error if the expectation of the test is not met and
thus cause the test to fail. The name of the It block should expressively state
the expectation of the test.

In addition to using your own logic to test expectations and throw exceptions,
you may also use Pester's Should command to perform assertions in plain language.

You can intentionally mark It block result as inconclusive by using Set-TestInconclusive
command as the first tested statement in the It block.

.PARAMETER Name
An expressive phrase describing the expected test outcome.

.PARAMETER Test
The script block that should throw an exception if the
expectation of the test is not met.If you are following the
AAA pattern (Arrange-Act-Assert), this typically holds the
Assert.

.PARAMETER Pending
Use this parameter to explicitly mark the test as work-in-progress/not implemented/pending when you
need to distinguish a test that fails because it is not finished yet from a tests
that fail as a result of changes being made in the code base. An empty test, that is a
test that contains nothing except whitespace or comments is marked as Pending by default.

.PARAMETER Skip
Use this parameter to explicitly mark the test to be skipped. This is preferable to temporarily
commenting out a test, because the test remains listed in the output. Use the Strict parameter
of Invoke-Pester to force all skipped tests to fail.

.PARAMETER TestCases
Optional array of hashtable (or any IDictionary) objects.  If this parameter is used,
Pester will call the test script block once for each table in the TestCases array,
splatting the dictionary to the test script block as input.  If you want the name of
the test to appear differently for each test case, you can embed tokens into the Name
parameter with the syntax 'Adds numbers <A> and <B>' (assuming you have keys named A and B
in your TestCases hashtables.)

.EXAMPLE
function Add-Numbers($a, $b) {
    return $a + $b
}

Describe "Add-Numbers" {
    It "adds positive numbers" {
        $sum = Add-Numbers 2 3
        $sum | Should -Be 5
    }

    It "adds negative numbers" {
        $sum = Add-Numbers (-2) (-2)
        $sum | Should -Be (-4)
    }

    It "adds one negative number to positive number" {
        $sum = Add-Numbers (-2) 2
        $sum | Should -Be 0
    }

    It "concatenates strings if given strings" {
        $sum = Add-Numbers two three
        $sum | Should -Be "twothree"
    }
}

.EXAMPLE
function Add-Numbers($a, $b) {
    return $a + $b
}

Describe "Add-Numbers" {
    $testCases = @(
        @{ a = 2;     b = 3;       expectedResult = 5 }
        @{ a = -2;    b = -2;      expectedResult = -4 }
        @{ a = -2;    b = 2;       expectedResult = 0 }
        @{ a = 'two'; b = 'three'; expectedResult = 'twothree' }
    )

    It 'Correctly adds <a> and <b> to get <expectedResult>' -TestCases $testCases {
        param ($a, $b, $expectedResult)

        $sum = Add-Numbers $a $b
        $sum | Should -Be $expectedResult
    }
}

.LINK
Describe
Context
Set-TestInconclusive
about_should
#>
    [CmdletBinding(DefaultParameterSetName = 'Normal')]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$name,

        [Parameter(Position = 1)]
        [ScriptBlock] $test = {},

        [System.Collections.IDictionary[]] $TestCases,

        [Parameter(ParameterSetName = 'Pending')]
        [Switch] $Pending,

        [Parameter(ParameterSetName = 'Skip')]
        [Alias('Ignore')]
        [Switch] $Skip
    )

    ItImpl -Pester $pester -OutputScriptBlock ${function:Write-PesterResult} @PSBoundParameters
}

function ItImpl
{
    [CmdletBinding(DefaultParameterSetName = 'Normal')]
    param(
        [Parameter(Mandatory = $true, Position=0)]
        [string]$name,
        [Parameter(Position = 1)]
        [ScriptBlock] $test,
        [System.Collections.IDictionary[]] $TestCases,
        [Parameter(ParameterSetName = 'Pending')]
        [Switch] $Pending,

        [Parameter(ParameterSetName = 'Skip')]
        [Alias('Ignore')]
        [Switch] $Skip,

        $Pester,
        [scriptblock] $OutputScriptBlock
    )

    Assert-DescribeInProgress -CommandName It

    # Jumping through hoops to make strict mode happy.
    if ($PSCmdlet.ParameterSetName -ne 'Skip') { $Skip = $false }
    if ($PSCmdlet.ParameterSetName -ne 'Pending') { $Pending = $false }

    #unless Skip or Pending is specified you must specify a ScriptBlock to the Test parameter
    if (-not ($PSBoundParameters.ContainsKey('test') -or $Skip -or $Pending))
    {
        throw 'No test script block is provided. (Have you put the open curly brace on the next line?)'
    }

    #the function is called with Pending or Skipped set the script block if needed
    if ($null -eq $test) { $test = {} }

    #mark empty Its as Pending
    #[String]::IsNullOrWhitespace is not available in .NET version used with PowerShell 2
    if ($PSCmdlet.ParameterSetName -eq 'Normal' -and
       [String]::IsNullOrEmpty((Remove-Comments $test.ToString()) -replace "\s"))
    {
        $Pending = $true
    }

    $pendingSkip = @{}

    if ($PSCmdlet.ParameterSetName -eq 'Skip')
    {
        $pendingSkip['Skip'] = $Skip
    }
    else
    {
        $pendingSkip['Pending'] = $Pending
    }

    if ($null -ne $TestCases -and $TestCases.Count -gt 0)
    {
        foreach ($testCase in $TestCases)
        {
            $expandedName = [regex]::Replace($name, '<([^>]+)>', {
                $capture = $args[0].Groups[1].Value
                if ($testCase.Contains($capture))
                {
                    $testCase[$capture]
                }
                else
                {
                    "<$capture>"
                }
            })

            $splat = @{
                Name = $expandedName
                Scriptblock = $test
                Parameters = $testCase
                ParameterizedSuiteName = $name
                OutputScriptBlock = $OutputScriptBlock
            }

            Invoke-Test @splat @pendingSkip
        }
    }
    else
    {
        Invoke-Test -Name $name -ScriptBlock $test @pendingSkip -OutputScriptBlock $OutputScriptBlock
    }
}

function Invoke-Test
{
    [CmdletBinding(DefaultParameterSetName = 'Normal')]
    param (
        [Parameter(Mandatory = $true)]
        [string] $Name,

        [Parameter(Mandatory = $true)]
        [ScriptBlock] $ScriptBlock,

        [scriptblock] $OutputScriptBlock,

        [System.Collections.IDictionary] $Parameters,
        [string] $ParameterizedSuiteName,

        [Parameter(ParameterSetName = 'Pending')]
        [Switch] $Pending,

        [Parameter(ParameterSetName = 'Skip')]
        [Alias('Ignore')]
        [Switch] $Skip
    )

    if ($null -eq $Parameters) { $Parameters = @{} }

    try
    {
        if ($Skip)
        {
            $Pester.AddTestResult($Name, "Skipped", $null)
        }
        elseif ($Pending)
        {
            $Pester.AddTestResult($Name, "Pending", $null)
        }
        else
        {
            #todo: disabling the progress for now, it adds a lot of overhead and breaks output on linux, we don't have a good way to disable it by default, or to show it after delay see: https://github.com/pester/Pester/issues/846
            # & $SafeCommands['Write-Progress'] -Activity "Running test '$Name'" -Status Processing

            $errorRecord = $null
            try
            {
                $pester.EnterTest()
                Invoke-TestCaseSetupBlocks

                do
                {
                    $null = & $ScriptBlock @Parameters
                } until ($true)
            }
            catch
            {
                $errorRecord = $_
            }
            finally
            {
                #guarantee that the teardown action will run and prevent it from failing the whole suite
                try
                {
                    if (-not ($Skip -or $Pending))
                    {
                        Invoke-TestCaseTeardownBlocks
                    }
                }
                catch
                {
                    $errorRecord = $_
                }

                $pester.LeaveTest()
            }

            $result = ConvertTo-PesterResult -Name $Name -ErrorRecord $errorRecord
            $orderedParameters = Get-OrderedParameterDictionary -ScriptBlock $ScriptBlock -Dictionary $Parameters
            $Pester.AddTestResult( $result.name, $result.Result, $null, $result.FailureMessage, $result.StackTrace, $ParameterizedSuiteName, $orderedParameters, $result.ErrorRecord )
            #todo: disabling progress reporting see above & $SafeCommands['Write-Progress'] -Activity "Running test '$Name'" -Completed -Status Processing
        }
    }
    finally
    {
        Exit-MockScope -ExitTestCaseOnly
    }

    if ($null -ne $OutputScriptBlock)
    {
        $Pester.testresult[-1] | & $OutputScriptBlock
    }
}

function Get-OrderedParameterDictionary
{
    [OutputType([System.Collections.IDictionary])]
    param (
        [scriptblock] $ScriptBlock,
        [System.Collections.IDictionary] $Dictionary
    )

    $parameters = Get-ParameterDictionary -ScriptBlock $ScriptBlock

    $orderedDictionary = & $SafeCommands['New-Object'] System.Collections.Specialized.OrderedDictionary

    foreach ($parameterName in $parameters.Keys)
    {
        $value = $null
        if ($Dictionary.ContainsKey($parameterName))
        {
            $value = $Dictionary[$parameterName]
        }

        $orderedDictionary[$parameterName] = $value
    }

    return $orderedDictionary
}

function Get-ParameterDictionary
{
    param (
        [scriptblock] $ScriptBlock
    )

    $guid = [guid]::NewGuid().Guid

    try
    {
        & $SafeCommands['Set-Content'] function:\$guid $ScriptBlock
        $metadata = [System.Management.Automation.CommandMetadata](& $SafeCommands['Get-Command'] -Name $guid -CommandType Function)

        return $metadata.Parameters
    }
    finally
    {
        if (& $SafeCommands['Test-Path'] function:\$guid) { & $SafeCommands['Remove-Item'] function:\$guid }
    }
}
tools\Functions\Mock.ps1
function Mock {

<#
.SYNOPSIS
Mocks the behavior of an existing command with an alternate
implementation.

.DESCRIPTION
This creates new behavior for any existing command within the scope of a
Describe or Context block. The function allows you to specify a script block
that will become the command's new behavior.

Optionally, you may create a Parameter Filter which will examine the
parameters passed to the mocked command and will invoke the mocked
behavior only if the values of the parameter values pass the filter. If
they do not, the original command implementation will be invoked instead
of a mock.

You may create multiple mocks for the same command, each using a different
ParameterFilter. ParameterFilters will be evaluated in reverse order of
their creation. The last one created will be the first to be evaluated.
The mock of the first filter to pass will be used. The exception to this
rule are Mocks with no filters. They will always be evaluated last since
they will act as a "catch all" mock.

Mocks can be marked Verifiable. If so, the Assert-VerifiableMock command
can be used to check if all Verifiable mocks were actually called. If any
verifiable mock is not called, Assert-VerifiableMock will throw an
exception and indicate all mocks not called.

If you wish to mock commands that are called from inside a script module,
you can do so by using the -ModuleName parameter to the Mock command. This
injects the mock into the specified module. If you do not specify a
module name, the mock will be created in the same scope as the test script.
You may mock the same command multiple times, in different scopes, as needed.
Each module's mock maintains a separate call history and verified status.

.PARAMETER CommandName
The name of the command to be mocked.

.PARAMETER MockWith
A ScriptBlock specifying the behavior that will be used to mock CommandName.
The default is an empty ScriptBlock.
NOTE: Do not specify param or dynamicparam blocks in this script block.
These will be injected automatically based on the signature of the command
being mocked, and the MockWith script block can contain references to the
mocked commands parameter variables.

.PARAMETER Verifiable
When this is set, the mock will be checked when Assert-VerifiableMock is
called.

.PARAMETER ParameterFilter
An optional filter to limit mocking behavior only to usages of
CommandName where the values of the parameters passed to the command
pass the filter.

This ScriptBlock must return a boolean value. See examples for usage.

.PARAMETER ModuleName
Optional string specifying the name of the module where this command
is to be mocked.  This should be a module that _calls_ the mocked
command; it doesn't necessarily have to be the same module which
originally implemented the command.

.EXAMPLE
Mock Get-ChildItem { return @{FullName = "A_File.TXT"} }

Using this Mock, all calls to Get-ChildItem will return a hashtable with a
FullName property returning "A_File.TXT"

.EXAMPLE
Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp) }

This Mock will only be applied to Get-ChildItem calls within the user's temp directory.

.EXAMPLE
Mock Set-Content {} -Verifiable -ParameterFilter { $Path -eq "some_path" -and $Value -eq "Expected Value" }

When this mock is used, if the Mock is never invoked and Assert-VerifiableMock is called, an exception will be thrown. The command behavior will do nothing since the ScriptBlock is empty.

.EXAMPLE
Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp\1) }
Mock Get-ChildItem { return @{FullName = "B_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp\2) }
Mock Get-ChildItem { return @{FullName = "C_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp\3) }

Multiple mocks of the same command may be used. The parameter filter determines which is invoked. Here, if Get-ChildItem is called on the "2" directory of the temp folder, then B_File.txt will be returned.

.EXAMPLE
Mock Get-ChildItem { return @{FullName="B_File.TXT"} } -ParameterFilter { $Path -eq "$env:temp\me" }
Mock Get-ChildItem { return @{FullName="A_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp) }

Get-ChildItem $env:temp\me

Here, both mocks could apply since both filters will pass. A_File.TXT will be returned because it was the most recent Mock created.

.EXAMPLE
Mock Get-ChildItem { return @{FullName = "B_File.TXT"} } -ParameterFilter { $Path -eq "$env:temp\me" }
Mock Get-ChildItem { return @{FullName = "A_File.TXT"} }

Get-ChildItem c:\windows

Here, A_File.TXT will be returned. Since no filter was specified, it will apply to any call to Get-ChildItem that does not pass another filter.

.EXAMPLE
Mock Get-ChildItem { return @{FullName = "B_File.TXT"} } -ParameterFilter { $Path -eq "$env:temp\me" }
Mock Get-ChildItem { return @{FullName = "A_File.TXT"} }

Get-ChildItem $env:temp\me

Here, B_File.TXT will be returned. Even though the filterless mock was created more recently. This illustrates that filterless Mocks are always evaluated last regardless of their creation order.

.EXAMPLE
Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } -ModuleName MyTestModule

Using this Mock, all calls to Get-ChildItem from within the MyTestModule module
will return a hashtable with a FullName property returning "A_File.TXT"

.EXAMPLE
Get-Module -Name ModuleMockExample | Remove-Module
New-Module -Name ModuleMockExample  -ScriptBlock {
    function Hidden { "Internal Module Function" }
    function Exported { Hidden }

    Export-ModuleMember -Function Exported
} | Import-Module -Force

Describe "ModuleMockExample" {

    It "Hidden function is not directly accessible outside the module" {
        { Hidden } | Should -Throw
    }

    It "Original Hidden function is called" {
        Exported | Should -Be "Internal Module Function"
    }

    It "Hidden is replaced with our implementation" {
        Mock Hidden { "Mocked" } -ModuleName ModuleMockExample
        Exported | Should -Be "Mocked"
    }
}

This example shows how calls to commands made from inside a module can be
mocked by using the -ModuleName parameter.


.LINK
Assert-MockCalled
Assert-VerifiableMock
Describe
Context
It
about_Should
about_Mocking
#>
    [CmdletBinding()]
    param(
        [string]$CommandName,
        [ScriptBlock]$MockWith={},
        [switch]$Verifiable,
        [ScriptBlock]$ParameterFilter = {$True},
        [string]$ModuleName
    )

    Assert-DescribeInProgress -CommandName Mock

    $contextInfo = Validate-Command $CommandName $ModuleName
    $CommandName = $contextInfo.Command.Name

    if ($contextInfo.Session.Module -and $contextInfo.Session.Module.Name)
    {
        $ModuleName = $contextInfo.Session.Module.Name
    }
    else
    {
        $ModuleName = ''
    }

    if (Test-IsClosure -ScriptBlock $MockWith)
    {
        # If the user went out of their way to call GetNewClosure(), go ahead and leave the block bound to that
        # dynamic module's scope.
        $mockWithCopy = $MockWith
    }
    else
    {
        $mockWithCopy = [scriptblock]::Create($MockWith.ToString())
        Set-ScriptBlockScope -ScriptBlock $mockWithCopy -SessionState $contextInfo.Session
    }

    $block = @{
        Mock       = $mockWithCopy
        Filter     = $ParameterFilter
        Verifiable = $Verifiable
        Scope      = $pester.CurrentTestGroup
    }

    $mock = $mockTable["$ModuleName||$CommandName"]

    if (-not $mock)
    {
        $metadata                = $null
        $cmdletBinding           = ''
        $paramBlock              = ''
        $dynamicParamBlock       = ''
        $dynamicParamScriptBlock = $null

        if ($contextInfo.Command.psobject.Properties['ScriptBlock'] -or $contextInfo.Command.CommandType -eq 'Cmdlet')
        {
            $metadata = [System.Management.Automation.CommandMetaData]$contextInfo.Command
            $null = $metadata.Parameters.Remove('Verbose')
            $null = $metadata.Parameters.Remove('Debug')
            $null = $metadata.Parameters.Remove('ErrorAction')
            $null = $metadata.Parameters.Remove('WarningAction')
            $null = $metadata.Parameters.Remove('ErrorVariable')
            $null = $metadata.Parameters.Remove('WarningVariable')
            $null = $metadata.Parameters.Remove('OutVariable')
            $null = $metadata.Parameters.Remove('OutBuffer')

            # Some versions of PowerShell may include dynamic parameters here
            # We will filter them out and add them at the end to be
            # compatible with both earlier and later versions
            $dynamicParams = $metadata.Parameters.Values | & $SafeCommands['Where-Object'] {$_.IsDynamic}
            if($dynamicParams -ne $null) {
                $dynamicparams | & $SafeCommands['ForEach-Object'] { $null = $metadata.Parameters.Remove($_.name) }
            }

            $cmdletBinding = [Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($metadata)
            if ($global:PSVersionTable.PSVersion.Major -ge 3 -and $contextInfo.Command.CommandType -eq 'Cmdlet') {
                if ($cmdletBinding -ne '[CmdletBinding()]') {
                    $cmdletBinding = $cmdletBinding.Insert($cmdletBinding.Length-2, ',')
                }
                $cmdletBinding = $cmdletBinding.Insert($cmdletBinding.Length-2, 'PositionalBinding=$false')
            }

            $paramBlock = [Management.Automation.ProxyCommand]::GetParamBlock($metadata)

            if ($contextInfo.Command.CommandType -eq 'Cmdlet')
            {
                $dynamicParamBlock = "dynamicparam { Get-MockDynamicParameter -CmdletName '$($contextInfo.Command.Name)' -Parameters `$PSBoundParameters }"
            }
            else
            {
                $dynamicParamStatements = Get-DynamicParamBlock -ScriptBlock $contextInfo.Command.ScriptBlock

                if ($dynamicParamStatements -match '\S')
                {
                    $metadataSafeForDynamicParams = [System.Management.Automation.CommandMetaData]$contextInfo.Command
                    foreach ($param in $metadataSafeForDynamicParams.Parameters.Values)
                    {
                        $param.ParameterSets.Clear()
                    }

                    $paramBlockSafeForDynamicParams = [System.Management.Automation.ProxyCommand]::GetParamBlock($metadataSafeForDynamicParams)
                    $comma = if ($metadataSafeForDynamicParams.Parameters.Count -gt 0) { ',' } else { '' }
                    $dynamicParamBlock = "dynamicparam { Get-MockDynamicParameter -ModuleName '$ModuleName' -FunctionName '$CommandName' -Parameters `$PSBoundParameters -Cmdlet `$PSCmdlet }"

                    $code = @"
                        $cmdletBinding
                        param(
                            [object] `${P S Cmdlet}$comma
                            $paramBlockSafeForDynamicParams
                        )

                        `$PSCmdlet = `${P S Cmdlet}

                        $dynamicParamStatements
"@

                    $dynamicParamScriptBlock = [scriptblock]::Create($code)

                    $sessionStateInternal = Get-ScriptBlockScope -ScriptBlock $contextInfo.Command.ScriptBlock

                    if ($null -ne $sessionStateInternal)
                    {
                        Set-ScriptBlockScope -ScriptBlock $dynamicParamScriptBlock -SessionStateInternal $sessionStateInternal
                    }
                }
            }
        }

        $EscapeSingleQuotedStringContent =
        if ($global:PSVersionTable.PSVersion.Major -ge 5) {
            { [System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($args[0]) }
        } else {
            { $args[0] -replace "['‘’‚‛]", '$&$&' }
        }

        $newContent = & $SafeCommands['Get-Content'] function:\MockPrototype
        $newContent = $newContent -replace '#FUNCTIONNAME#', (& $EscapeSingleQuotedStringContent $CommandName)
        $newContent = $newContent -replace '#MODULENAME#', (& $EscapeSingleQuotedStringContent $ModuleName)

        $canCaptureArgs = 'true'
        if ($contextInfo.Command.CommandType -eq 'Cmdlet' -or
            ($contextInfo.Command.CommandType -eq 'Function' -and $contextInfo.Command.CmdletBinding)) {
            $canCaptureArgs = 'false'
        }
        $newContent = $newContent -replace '#CANCAPTUREARGS#', $canCaptureArgs

        $code = @"
            $cmdletBinding
            param ( $paramBlock )
            $dynamicParamBlock
            begin
            {
                `${mock call state} = @{}
                $($newContent -replace '#BLOCK#', 'Begin' -replace '#INPUT#')
            }

            process
            {
                $($newContent -replace '#BLOCK#', 'Process' -replace '#INPUT#', '-InputObject @($input)')
            }

            end
            {
                $($newContent -replace '#BLOCK#', 'End' -replace '#INPUT#')
            }
"@

        $mockScript = [scriptblock]::Create($code)

        $mock = @{
            OriginalCommand         = $contextInfo.Command
            Blocks                  = @()
            CommandName             = $CommandName
            SessionState            = $contextInfo.Session
            Scope                   = $pester.CurrentTestGroup
            PesterState             = $pester
            Metadata                = $metadata
            CallHistory             = @()
            DynamicParamScriptBlock = $dynamicParamScriptBlock
            Aliases                 = @()
            BootstrapFunctionName   = [Guid]::NewGuid().ToString()
        }

        $mockTable["$ModuleName||$CommandName"] = $mock

        $scriptBlock = { $ExecutionContext.InvokeProvider.Item.Set("Function:\script:$($args[0])", $args[1], $true, $true) }
        $null = Invoke-InMockScope -SessionState $mock.SessionState -ScriptBlock $scriptBlock -ArgumentList $Mock.BootstrapFunctionName, $mockScript

        $mock.Aliases += $CommandName

        $scriptBlock = {
            $setAlias = & (Pester\SafeGetCommand) -Name Set-Alias -CommandType Cmdlet -Module Microsoft.PowerShell.Utility
            & $setAlias -Name $args[0] -Value $args[1] -Scope Script
        }

        $null = Invoke-InMockScope -SessionState $mock.SessionState -ScriptBlock $scriptBlock -ArgumentList $CommandName, $mock.BootstrapFunctionName

        if ($mock.OriginalCommand.ModuleName)
        {
            $aliasName = "$($mock.OriginalCommand.ModuleName)\$($CommandName)"
            $mock.Aliases += $aliasName

            $scriptBlock = {
                $setAlias = & (Pester\SafeGetCommand) -Name Set-Alias -CommandType Cmdlet -Module Microsoft.PowerShell.Utility
                & $setAlias -Name $args[0] -Value $args[1] -Scope Script
            }

            $null = Invoke-InMockScope -SessionState $mock.SessionState -ScriptBlock $scriptBlock -ArgumentList $aliasName, $mock.BootstrapFunctionName
        }
    }

    $mock.Blocks = @(
        $mock.Blocks | & $SafeCommands['Where-Object'] { $_.Filter.ToString() -eq '$True' }
        if ($block.Filter.ToString() -eq '$True') { $block }

        $mock.Blocks | & $SafeCommands['Where-Object'] { $_.Filter.ToString() -ne '$True' }
        if ($block.Filter.ToString() -ne '$True') { $block }
    )
}


function Assert-VerifiableMock {
<#
.SYNOPSIS
Checks if any Verifiable Mock has not been invoked. If so, this will throw an exception.

.DESCRIPTION
This can be used in tandem with the -Verifiable switch of the Mock
function. Mock can be used to mock the behavior of an existing command
and optionally take a -Verifiable switch. When Assert-VerifiableMock
is called, it checks to see if any Mock marked Verifiable has not been
invoked. If any mocks have been found that specified -Verifiable and
have not been invoked, an exception will be thrown.

.EXAMPLE
Mock Set-Content {} -Verifiable -ParameterFilter {$Path -eq "some_path" -and $Value -eq "Expected Value"}

{ ...some code that never calls Set-Content some_path -Value "Expected Value"... }

Assert-VerifiableMock

This will throw an exception and cause the test to fail.

.EXAMPLE
Mock Set-Content {} -Verifiable -ParameterFilter {$Path -eq "some_path" -and $Value -eq "Expected Value"}

Set-Content some_path -Value "Expected Value"

Assert-VerifiableMock

This will not throw an exception because the mock was invoked.

#>
    [CmdletBinding()]param()
    Assert-DescribeInProgress -CommandName Assert-VerifiableMock

    $unVerified=@{}
    $mockTable.Keys | & $SafeCommands['ForEach-Object'] {
        $m=$_;

        $mockTable[$m].blocks |
        & $SafeCommands['Where-Object'] { $_.Verifiable } |
        & $SafeCommands['ForEach-Object'] { $unVerified[$m]=$_ }
    }
    if($unVerified.Count -gt 0) {
        foreach($mock in $unVerified.Keys){
            $array = $mock -split '\|\|'
            $function = $array[1]
            $module = $array[0]

            $message = "$([System.Environment]::NewLine) Expected $function "
            if ($module) { $message += "in module $module " }
            $message += "to be called with $($unVerified[$mock].Filter)"
        }
        throw $message
    }
}

function Assert-MockCalled {
<#
.SYNOPSIS
Checks if a Mocked command has been called a certain number of times
and throws an exception if it has not.

.DESCRIPTION
This command verifies that a mocked command has been called a certain number
of times.  If the call history of the mocked command does not match the parameters
passed to Assert-MockCalled, Assert-MockCalled will throw an exception.

.PARAMETER CommandName
The mocked command whose call history should be checked.

.PARAMETER ModuleName
The module where the mock being checked was injected.  This is optional,
and must match the ModuleName that was used when setting up the Mock.

.PARAMETER Times
The number of times that the mock must be called to avoid an exception
from throwing.

.PARAMETER Exactly
If this switch is present, the number specified in Times must match
exactly the number of times the mock has been called. Otherwise it
must match "at least" the number of times specified.  If the value
passed to the Times parameter is zero, the Exactly switch is implied.

.PARAMETER ParameterFilter
An optional filter to qualify wich calls should be counted. Only those
calls to the mock whose parameters cause this filter to return true
will be counted.

.PARAMETER ExclusiveFilter
Like ParameterFilter, except when you use ExclusiveFilter, and there
were any calls to the mocked command which do not match the filter,
an exception will be thrown.  This is a convenient way to avoid needing
to have two calls to Assert-MockCalled like this:

Assert-MockCalled SomeCommand -Times 1 -ParameterFilter { $something -eq $true }
Assert-MockCalled SomeCommand -Times 0 -ParameterFilter { $something -ne $true }

.PARAMETER Scope
An optional parameter specifying the Pester scope in which to check for
calls to the mocked command.  By default, Assert-MockCalled will find
all calls to the mocked command in the current Context block (if present),
or the current Describe block (if there is no active Context.)  Valid
values are Describe, Context and It. If you use a scope of Describe or
Context, the command will identify all calls to the mocked command in the
current Describe / Context block, as well as all child scopes of that block.

.EXAMPLE
C:\PS>Mock Set-Content {}

{... Some Code ...}

C:\PS>Assert-MockCalled Set-Content

This will throw an exception and cause the test to fail if Set-Content is not called in Some Code.

.EXAMPLE
C:\PS>Mock Set-Content -parameterFilter {$path.StartsWith("$env:temp\")}

{... Some Code ...}

C:\PS>Assert-MockCalled Set-Content 2 { $path -eq "$env:temp\test.txt" }

This will throw an exception if some code calls Set-Content on $path=$env:temp\test.txt less than 2 times

.EXAMPLE
C:\PS>Mock Set-Content {}

{... Some Code ...}

C:\PS>Assert-MockCalled Set-Content 0

This will throw an exception if some code calls Set-Content at all

.EXAMPLE
C:\PS>Mock Set-Content {}

{... Some Code ...}

C:\PS>Assert-MockCalled Set-Content -Exactly 2

This will throw an exception if some code does not call Set-Content Exactly two times.

.EXAMPLE
Describe 'Assert-MockCalled Scope behavior' {
    Mock Set-Content { }

    It 'Calls Set-Content at least once in the It block' {
        {... Some Code ...}

        Assert-MockCalled Set-Content -Exactly 0 -Scope It
    }
}

Checks for calls only within the current It block.

.EXAMPLE
Describe 'Describe' {
    Mock -ModuleName SomeModule Set-Content { }

    {... Some Code ...}

    It 'Calls Set-Content at least once in the Describe block' {
        Assert-MockCalled -ModuleName SomeModule Set-Content
    }
}

Checks for calls to the mock within the SomeModule module.  Note that both the Mock
and Assert-MockCalled commands use the same module name.

.EXAMPLE
Assert-MockCalled Get-ChildItem -ExclusiveFilter { $Path -eq 'C:\' }

Checks to make sure that Get-ChildItem was called at least one time with
the -Path parameter set to 'C:\', and that it was not called at all with
the -Path parameter set to any other value.

.NOTES
The parameter filter passed to Assert-MockCalled does not necessarily have to match the parameter filter
(if any) which was used to create the Mock.  Assert-MockCalled will find any entry in the command history
which matches its parameter filter, regardless of how the Mock was created.  However, if any calls to the
mocked command are made which did not match any mock's parameter filter (resulting in the original command
being executed instead of a mock), these calls to the original command are not tracked in the call history.
In other words, Assert-MockCalled can only be used to check for calls to the mocked implementation, not
to the original.

#>

[CmdletBinding(DefaultParameterSetName = 'ParameterFilter')]
param(
    [Parameter(Mandatory = $true, Position = 0)]
    [string]$CommandName,

    [Parameter(Position = 1)]
    [int]$Times=1,

    [Parameter(ParameterSetName = 'ParameterFilter', Position = 2)]
    [ScriptBlock]$ParameterFilter = {$True},

    [Parameter(ParameterSetName = 'ExclusiveFilter', Mandatory = $true)]
    [scriptblock] $ExclusiveFilter,

    [Parameter(Position = 3)]
    [string] $ModuleName,

    [Parameter(Position = 4)]
    [ValidateScript({
        if ([uint32]::TryParse($_, [ref] $null) -or
            $_ -eq 'Describe' -or
            $_ -eq 'Context' -or
            $_ -eq 'It')
        {
            return $true
        }

        throw "Scope argument must either be an unsigned integer, or one of the words 'Describe', 'Context', or 'It'."
    })]
    [string] $Scope,

    [switch]$Exactly
)

    if ($PSCmdlet.ParameterSetName -eq 'ParameterFilter')
    {
        $filter = $ParameterFilter
        $filterIsExclusive = $false
    }
    else
    {
        $filter = $ExclusiveFilter
        $filterIsExclusive = $true
    }

    Assert-DescribeInProgress -CommandName Assert-MockCalled

    if (-not $PSBoundParameters.ContainsKey('ModuleName') -and $null -ne $pester.SessionState.Module)
    {
        $ModuleName = $pester.SessionState.Module.Name
    }

    $contextInfo = Validate-Command $CommandName $ModuleName
    $CommandName = $contextInfo.Command.Name

    $mock = $script:mockTable["$ModuleName||$CommandName"]

    $moduleMessage = ''
    if ($ModuleName)
    {
        $moduleMessage = " in module $ModuleName"
    }

    if (-not $mock)
    {
        throw "You did not declare a mock of the $commandName Command${moduleMessage}."
    }

    if (-not $PSBoundParameters.ContainsKey('Scope'))
    {
        $scope = 1
    }

    $matchingCalls = & $SafeCommands['New-Object'] System.Collections.ArrayList
    $nonMatchingCalls = & $SafeCommands['New-Object'] System.Collections.ArrayList

    foreach ($historyEntry in $mock.CallHistory)
    {
        if (-not (Test-MockCallScope -CallScope $historyEntry.Scope -DesiredScope $Scope)) { continue }

        $params = @{
            ScriptBlock     = $filter
            BoundParameters = $historyEntry.BoundParams
            ArgumentList    = $historyEntry.Args
            Metadata        = $mock.Metadata
        }


        if (Test-ParameterFilter @params)
        {
            $null = $matchingCalls.Add($historyEntry)
        }
        else
        {
            $null = $nonMatchingCalls.Add($historyEntry)
        }
    }

    $lineText = $MyInvocation.Line.TrimEnd("$([System.Environment]::NewLine)")
    $line = $MyInvocation.ScriptLineNumber

    if($matchingCalls.Count -ne $times -and ($Exactly -or ($times -eq 0)))
    {
        $failureMessage = "Expected ${commandName}${moduleMessage} to be called $times times exactly but was called $($matchingCalls.Count) times"
        throw ( New-ShouldErrorRecord -Message $failureMessage -Line $line -LineText $lineText)
    }
    elseif($matchingCalls.Count -lt $times)
    {
        $failureMessage = "Expected ${commandName}${moduleMessage} to be called at least $times times but was called $($matchingCalls.Count) times"
        throw ( New-ShouldErrorRecord -Message $failureMessage -Line $line -LineText $lineText)
    }
    elseif ($filterIsExclusive -and $nonMatchingCalls.Count -gt 0)
    {
        $failureMessage = "Expected ${commandName}${moduleMessage} to only be called with with parameters matching the specified filter, but $($nonMatchingCalls.Count) non-matching calls were made"
        throw ( New-ShouldErrorRecord -Message $failureMessage -Line $line -LineText $lineText)
    }
}

function Test-MockCallScope
{
    [CmdletBinding()]
    param (
        [object] $CallScope,
        [string] $DesiredScope
    )

    if ($null -eq $CallScope)
    {
        # This indicates a call from the current test case ("It" block), which always passes Test-MockCallScope
        return $true
    }

    $testGroups = $pester.TestGroups
    [Array]::Reverse($testGroups)

    $target = 0
    $isNumberedScope = [int]::TryParse($DesiredScope, [ref] $target)

    # The Describe / Context stuff here is for backward compatibility.  May be deprecated / removed in the future.
    $actualScopeNumber = -1
    $describe = -1
    $context = -1

    for ($i = 0; $i -lt $testGroups.Count; $i++)
    {
        if ($CallScope -eq $testGroups[$i])
        {
            $actualScopeNumber = $i
            if ($isNumberedScope) { break }
        }

        if ($describe -lt 0 -and $testGroups[$i].Hint -eq 'Describe') { $describe = $i }
        if ($context -lt 0 -and $testGroups[$i].Hint -eq 'Context') { $context = $i }
    }

    if ($actualScopeNumber -lt 0)
    {
        # this should never happen; if we get here, it's a Pester bug.

        throw "Pester error: Corrupted mock call history table."
    }

    if ($isNumberedScope)
    {
        # For this, we consider scope 0 to be the current test case / It block, scope 1 to be the first Test Group up the stack, etc.
        # $actualScopeNumber currently off by one from that scale (zero-indexed for test groups only; we already checked for the 0 case
        # farther up, which only applies if $CallScope is $null).
        return $target -gt $actualScopeNumber
    }
    else
    {
        if ($DesiredScope -eq 'Describe') { return $describe -ge $actualScopeNumber }
        if ($DesiredScope -eq 'Context')  { return $context -ge $actualScopeNumber }
    }

    return $false
}

function Exit-MockScope {
    param (
        [switch] $ExitTestCaseOnly
    )

    if ($null -eq $mockTable) { return }

    $removeMockStub =
    {
        param (
            [string] $CommandName,
            [string[]] $Aliases
        )

        $ExecutionContext.InvokeProvider.Item.Remove("Function:\$CommandName", $false, $true, $true)

        foreach ($alias in $Aliases)
        {
            if ($ExecutionContext.InvokeProvider.Item.Exists("Alias:$alias", $true, $true))
            {
                $ExecutionContext.InvokeProvider.Item.Remove("Alias:$alias", $false, $true, $true)
            }
        }
    }

    $mockKeys = [string[]]$mockTable.Keys

    foreach ($mockKey in $mockKeys)
    {
        $mock = $mockTable[$mockKey]

        $shouldRemoveMock = (-not $ExitTestCaseOnly) -and (ShouldRemoveMock -Mock $mock -ActivePesterState $pester)
        if ($shouldRemoveMock)
        {
            $null = Invoke-InMockScope -SessionState $mock.SessionState -ScriptBlock $removeMockStub -ArgumentList $mock.BootstrapFunctionName, $mock.Aliases
            $mockTable.Remove($mockKey)
        }
        elseif ($mock.PesterState -eq $pester)
        {
            if (-not $ExitTestCaseOnly)
            {
                $mock.Blocks = @($mock.Blocks | & $SafeCommands['Where-Object'] { $_.Scope -ne $pester.CurrentTestGroup })
            }

            $testGroups = @($pester.TestGroups)

            $parentTestGroup = $null

            if ($testGroups.Count -gt 1)
            {
                $parentTestGroup = $testGroups[-2]
            }

            foreach ($historyEntry in $mock.CallHistory)
            {
                if ($ExitTestCaseOnly)
                {
                    if ($historyEntry.Scope -eq $null) { $historyEntry.Scope = $pester.CurrentTestGroup }
                }
                elseif ($parentTestGroup)
                {
                    if ($historyEntry.Scope -eq $pester.CurrentTestGroup) { $historyEntry.Scope = $parentTestGroup }
                }
            }
        }
    }
}

function ShouldRemoveMock($Mock, $ActivePesterState)
{
    if ($ActivePesterState -ne $mock.PesterState) { return $false }
    if ($mock.Scope -eq $ActivePesterState.CurrentTestGroup) { return $true }

    # These two should conditions should _probably_ never happen, because the above condition should
    # catch it, but just in case:
    if ($ActivePesterState.TestGroups.Count -eq 1) { return $true }
    if ($ActivePesterState.TestGroups[-2].Hint -eq 'Root') { return $true }

    return $false
}

function Validate-Command([string]$CommandName, [string]$ModuleName) {
    $module = $null
    $command = $null

    $scriptBlock = {
        $command = $ExecutionContext.InvokeCommand.GetCommand($args[0], 'All')
        while ($null -ne $command -and $command.CommandType -eq [System.Management.Automation.CommandTypes]::Alias)
        {
            $command = $command.ResolvedCommand
        }

        return $command
    }

    if ($ModuleName) {
        $module = Get-ScriptModule -ModuleName $ModuleName -ErrorAction Stop
        $command = & $module $scriptBlock $CommandName
    }

    $session = $pester.SessionState

    if (-not $command) {
        Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $session
        $command = & $scriptBlock $commandName
    }

    if (-not $command) {
        throw ([System.Management.Automation.CommandNotFoundException] "Could not find Command $commandName")
    }

    if ($module) {
        $session = & $module { $ExecutionContext.SessionState }
    }

    $hash = @{Command = $command; Session = $session}

    if ($command.CommandType -eq 'Function')
    {
        foreach ($mock in $mockTable.Values)
        {
            if ($command.Name -eq $mock.BootstrapFunctionName)
            {
                return @{
                    Command = $mock.OriginalCommand
                    Session = $mock.SessionState
                }
            }
        }
    }

    return $hash
}

function MockPrototype {
    if ($PSVersionTable.PSVersion.Major -ge 3)
    {
        [string] ${ignore preference} = 'Ignore'
    }
    else
    {
        [string] ${ignore preference} = 'SilentlyContinue'
    }

    ${get Variable Command} = & (Pester\SafeGetCommand) -Name Get-Variable -Module Microsoft.PowerShell.Utility -CommandType Cmdlet

    [object] ${a r g s} = $null
    if (${#CANCAPTUREARGS#}) {
        ${a r g s} = & ${get Variable Command} -Name args -ValueOnly -Scope Local -ErrorAction ${ignore preference}
    }
    if ($null -eq ${a r g s}) { ${a r g s} = @() }

    ${p s cmdlet} = & ${get Variable Command} -Name PSCmdlet -ValueOnly -Scope Local -ErrorAction ${ignore preference}

    ${session state} = if (${p s cmdlet}) { ${p s cmdlet}.SessionState }

    # @{mock call state} initialization is injected only into the begin block by the code that uses this prototype.
    Invoke-Mock -CommandName '#FUNCTIONNAME#' -ModuleName '#MODULENAME#' -BoundParameters $PSBoundParameters -ArgumentList ${a r g s} -CallerSessionState ${session state} -FromBlock '#BLOCK#' -MockCallState ${mock call state} #INPUT#
}

function Invoke-Mock {
    <#
        .SYNOPSIS
        This command is used by Pester's Mocking framework.  You do not need to call it directly.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $CommandName,

        [Parameter(Mandatory = $true)]
        [hashtable] $MockCallState,

        [string]
        $ModuleName,

        [hashtable]
        $BoundParameters = @{},

        [object[]]
        $ArgumentList = @(),

        [object] $CallerSessionState,

        [ValidateSet('Begin', 'Process', 'End')]
        [string] $FromBlock,

        [object] $InputObject
    )

    $detectedModule = $ModuleName
    $mock = FindMock -CommandName $CommandName -ModuleName ([ref]$detectedModule)

    if ($null -eq $mock)
    {
        # If this ever happens, it's a bug in Pester.  The scriptBlock that calls Invoke-Mock should be removed at the same time as the entry in the mock table.
        throw "Internal error detected:  Mock for '$CommandName' in module '$ModuleName' was called, but does not exist in the mock table."
    }

    switch ($FromBlock)
    {
        Begin
        {
            $MockCallState['InputObjects'] = & $SafeCommands['New-Object'] System.Collections.ArrayList
            $MockCallState['ShouldExecuteOriginalCommand'] = $false
            $MockCallState['BeginBoundParameters'] = $BoundParameters.Clone()
            $MockCallState['BeginArgumentList'] = $ArgumentList

            return
        }

        Process
        {
            $block = $null
            if ($detectedModule -eq $ModuleName)
            {
                $block = FindMatchingBlock -Mock $mock -BoundParameters $BoundParameters -ArgumentList $ArgumentList
            }

            if ($null -ne $block)
            {
                ExecuteBlock -Block $block `
                             -CommandName $CommandName `
                             -ModuleName $ModuleName `
                             -BoundParameters $BoundParameters `
                             -ArgumentList $ArgumentList `
                             -Mock $mock

                return
            }
            else
            {
                $MockCallState['ShouldExecuteOriginalCommand'] = $true
                if ($null -ne $InputObject)
                {
                    $null = $MockCallState['InputObjects'].AddRange(@($InputObject))
                }

                return
            }
        }

        End
        {
            if ($MockCallState['ShouldExecuteOriginalCommand'])
            {
                if ($MockCallState['InputObjects'].Count -gt 0)
                {
                    $scriptBlock = {
                        param ($Command, $ArgumentList, $BoundParameters, $InputObjects)
                        $InputObjects | & $Command @ArgumentList @BoundParameters
                    }
                }
                else
                {
                    $scriptBlock = {
                        param ($Command, $ArgumentList, $BoundParameters, $InputObjects)
                        & $Command @ArgumentList @BoundParameters
                    }
                }

                $state = if ($CallerSessionState) { $CallerSessionState } else { $mock.SessionState }

                Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $state

                & $scriptBlock -Command $mock.OriginalCommand `
                               -ArgumentList $MockCallState['BeginArgumentList'] `
                               -BoundParameters $MockCallState['BeginBoundParameters'] `
                               -InputObjects $MockCallState['InputObjects']
            }
        }
    }
}

function FindMock
{
    param (
        [string] $CommandName,
        [ref] $ModuleName
    )

    $mock = $mockTable["$($ModuleName.Value)||$CommandName"]

    if ($null -eq $mock)
    {
        $mock = $mockTable["||$CommandName"]
        if ($null -ne $mock)
        {
            $ModuleName.Value = ''
        }
    }

    return $mock
}

function FindMatchingBlock
{
    param (
        [object] $Mock,
        [hashtable] $BoundParameters = @{},
        [object[]] $ArgumentList = @()
    )

    for ($idx = $mock.Blocks.Length; $idx -gt 0; $idx--)
    {
        $block = $mock.Blocks[$idx - 1]

        $params = @{
            ScriptBlock     = $block.Filter
            BoundParameters = $BoundParameters
            ArgumentList    = $ArgumentList
            Metadata        = $mock.Metadata
        }

        if (Test-ParameterFilter @params)
        {
            return $block
        }
    }

    return $null
}

function ExecuteBlock
{
    param (
        [object] $Block,
        [object] $Mock,
        [string] $CommandName,
        [string] $ModuleName,
        [hashtable] $BoundParameters = @{},
        [object[]] $ArgumentList = @()
    )

    $Block.Verifiable = $false

    $scope = if ($pester.InTest) { $null } else { $pester.CurrentTestGroup }
    $Mock.CallHistory += @{CommandName = "$ModuleName||$CommandName"; BoundParams = $BoundParameters; Args = $ArgumentList; Scope = $scope }

    $scriptBlock = {
        param (
            [Parameter(Mandatory = $true)]
            [scriptblock]
            ${Script Block},

            [hashtable]
            $___BoundParameters___ = @{},

            [object[]]
            $___ArgumentList___ = @(),

            [System.Management.Automation.CommandMetadata]
            ${Meta data},

            [System.Management.Automation.SessionState]
            ${Session State}
        )

        # This script block exists to hold variables without polluting the test script's current scope.
        # Dynamic parameters in functions, for some reason, only exist in $PSBoundParameters instead
        # of being assigned a local variable the way static parameters do.  By calling Set-DynamicParameterVariable,
        # we create these variables for the caller's use in a Parameter Filter or within the mock itself, and
        # by doing it inside this temporary script block, those variables don't stick around longer than they
        # should.

        Set-DynamicParameterVariable -SessionState ${Session State} -Parameters $___BoundParameters___ -Metadata ${Meta data}
        & ${Script Block} @___BoundParameters___ @___ArgumentList___
    }

    Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $mock.SessionState
    $splat = @{
        'Script Block' = $block.Mock
        '___ArgumentList___' = $ArgumentList
        '___BoundParameters___' = $BoundParameters
        'Meta data' = $mock.Metadata
        'Session State' = $mock.SessionState
    }

    & $scriptBlock @splat
}

function Invoke-InMockScope
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [System.Management.Automation.SessionState]
        $SessionState,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter(ValueFromRemainingArguments = $true)]
        [object[]]
        $ArgumentList = @()
    )

    if ($SessionState.Module)
    {
        $SessionState.Module.Invoke($ScriptBlock, $ArgumentList)
    }
    else
    {
        Set-ScriptBlockScope -ScriptBlock $ScriptBlock -SessionState $SessionState
        & $ScriptBlock @ArgumentList
    }
}

function Test-ParameterFilter
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock,

        [System.Collections.IDictionary]
        $BoundParameters,

        [object[]]
        $ArgumentList,

        [System.Management.Automation.CommandMetadata]
        $Metadata
    )

    if ($null -eq $BoundParameters)   { $BoundParameters = @{} }
    if ($null -eq $ArgumentList)      { $ArgumentList = @() }

    $paramBlock = Get-ParamBlockFromBoundParameters -BoundParameters $BoundParameters -Metadata $Metadata

    $scriptBlockString = "
        $paramBlock

        Set-StrictMode -Off
        $ScriptBlock
    "

    $cmd = [scriptblock]::Create($scriptBlockString)
    Set-ScriptBlockScope -ScriptBlock $cmd -SessionState $pester.SessionState

    & $cmd @BoundParameters @ArgumentList
}

function Get-ParamBlockFromBoundParameters
{
    param (
        [System.Collections.IDictionary] $BoundParameters,
        [System.Management.Automation.CommandMetadata] $Metadata
    )

    $params = foreach ($paramName in $BoundParameters.get_Keys())
    {
        if (IsCommonParameter -Name $paramName -Metadata $Metadata)
        {
            continue
        }

        "`${$paramName}"
    }

    $params = $params -join ','

    if ($null -ne $Metadata)
    {
        $cmdletBinding = [System.Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($Metadata)
    }
    else
    {
        $cmdletBinding = ''
    }

    return "$cmdletBinding param ($params)"
}

function IsCommonParameter
{
    param (
        [string] $Name,
        [System.Management.Automation.CommandMetadata] $Metadata
    )

    if ($null -ne $Metadata)
    {
        if ([System.Management.Automation.Internal.CommonParameters].GetProperty($Name)) { return $true }
        if ($Metadata.SupportsShouldProcess -and [System.Management.Automation.Internal.ShouldProcessParameters].GetProperty($Name)) { return $true }
        if ($PSVersionTable.PSVersion.Major -ge 3 -and $Metadata.SupportsPaging -and [System.Management.Automation.PagingParameters].GetProperty($Name)) { return $true }
        if ($Metadata.SupportsTransactions -and [System.Management.Automation.Internal.TransactionParameters].GetProperty($Name)) { return $true }
    }

    return $false
}

function Set-DynamicParameterVariable
{
    <#
        .SYNOPSIS
        This command is used by Pester's Mocking framework.  You do not need to call it directly.
    #>

    param (
        [Parameter(Mandatory = $true)]
        [System.Management.Automation.SessionState]
        $SessionState,

        [hashtable]
        $Parameters,

        [System.Management.Automation.CommandMetadata]
        $Metadata
    )

    if ($null -eq $Parameters) { $Parameters = @{} }

    foreach ($keyValuePair in $Parameters.GetEnumerator())
    {
        $variableName = $keyValuePair.Key

        if (-not (IsCommonParameter -Name $variableName -Metadata $Metadata))
        {
            if ($ExecutionContext.SessionState -eq $SessionState)
            {
                & $SafeCommands['Set-Variable'] -Scope 1 -Name $variableName -Value $keyValuePair.Value -Force -Confirm:$false -WhatIf:$false
            }
            else
            {
                $SessionState.PSVariable.Set($variableName, $keyValuePair.Value)
            }
        }
    }
}

function Get-DynamicParamBlock
{
    param (
        [scriptblock] $ScriptBlock
    )

    if ($PSVersionTable.PSVersion.Major -le 2)
    {
        $flags = [System.Reflection.BindingFlags]'Instance, NonPublic'
        $dynamicParams = [scriptblock].GetField('_dynamicParams', $flags).GetValue($ScriptBlock)

        if ($null -ne $dynamicParams)
        {
            return $dynamicParams.ToString()

        }
    }
    else
    {
        If ( $ScriptBlock.AST.psobject.Properties.Name -match "Body")
        {
            if ($null -ne $ScriptBlock.Ast.Body.DynamicParamBlock)
            {
                $statements = $ScriptBlock.Ast.Body.DynamicParamBlock.Statements |
                            & $SafeCommands['Select-Object'] -ExpandProperty Extent |
                            & $SafeCommands['Select-Object'] -ExpandProperty Text

                return $statements -join "$([System.Environment]::NewLine)"
            }
        }
    }
}

function Get-MockDynamicParameter
{
    <#
        .SYNOPSIS
        This command is used by Pester's Mocking framework.  You do not need to call it directly.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'Cmdlet')]
        [string] $CmdletName,

        [Parameter(Mandatory = $true, ParameterSetName = 'Function')]
        [string] $FunctionName,

        [Parameter(ParameterSetName = 'Function')]
        [string] $ModuleName,

        [System.Collections.IDictionary] $Parameters,

        [object] $Cmdlet
    )

    switch ($PSCmdlet.ParameterSetName)
    {
        'Cmdlet'
        {
            Get-DynamicParametersForCmdlet -CmdletName $CmdletName -Parameters $Parameters
        }

        'Function'
        {
            Get-DynamicParametersForMockedFunction -FunctionName $FunctionName -ModuleName $ModuleName -Parameters $Parameters -Cmdlet $Cmdlet
        }
    }
}

function Get-DynamicParametersForCmdlet
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $CmdletName,

        [ValidateScript({
            if ($PSVersionTable.PSVersion.Major -ge 3 -and
                $null -ne $_ -and
                $_.GetType().FullName -ne 'System.Management.Automation.PSBoundParametersDictionary')
            {
                throw 'The -Parameters argument must be a PSBoundParametersDictionary object ($PSBoundParameters).'
            }

            return $true
        })]
        [System.Collections.IDictionary] $Parameters
    )

    try
    {
        $command = & $SafeCommands['Get-Command'] -Name $CmdletName -CommandType Cmdlet -ErrorAction Stop

        if (@($command).Count -gt 1)
        {
            throw "Name '$CmdletName' resolved to multiple Cmdlets"
        }
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError($_)
    }

    if ($null -eq $command.ImplementingType.GetInterface('IDynamicParameters', $true))
    {
        return
    }

    if ('5.0.10586.122' -lt $PSVersionTable.PSVersion)
    {
        # Older version of PS required Reflection to do this.  It has run into problems on occasion with certain cmdlets,
        # such as ActiveDirectory and AzureRM, so we'll take advantage of the newer PSv5 engine features if at all possible.

        if ($null -eq $Parameters) { $paramsArg = @() } else { $paramsArg = @($Parameters) }

        $command = $ExecutionContext.InvokeCommand.GetCommand($CmdletName, [System.Management.Automation.CommandTypes]::Cmdlet, $paramsArg)
        $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()

        foreach ($param in $command.Parameters.Values)
        {
            if (-not $param.IsDynamic) { continue }
            if ($Parameters.ContainsKey($param.Name)) { continue }

            $dynParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param.Name, $param.ParameterType, $param.Attributes)
            $paramDictionary.Add($param.Name, $dynParam)
        }

        return $paramDictionary
    }
    else
    {
        if ($null -eq $Parameters) { $Parameters = @{} }

        $cmdlet = & $SafeCommands['New-Object'] $command.ImplementingType.FullName

        $flags = [System.Reflection.BindingFlags]'Instance, Nonpublic'
        $context = $ExecutionContext.GetType().GetField('_context', $flags).GetValue($ExecutionContext)
        [System.Management.Automation.Cmdlet].GetProperty('Context', $flags).SetValue($cmdlet, $context, $null)

        foreach ($keyValuePair in $Parameters.GetEnumerator())
        {
            $property = $cmdlet.GetType().GetProperty($keyValuePair.Key)
            if ($null -eq $property -or -not $property.CanWrite) { continue }

            $isParameter = [bool]($property.GetCustomAttributes([System.Management.Automation.ParameterAttribute], $true))
            if (-not $isParameter) { continue }

            $property.SetValue($cmdlet, $keyValuePair.Value, $null)
        }

        try
        {
            # This unary comma is important in some cases.  On Windows 7 systems, the ActiveDirectory module cmdlets
            # return objects from this method which implement IEnumerable for some reason, and even cause PowerShell
            # to throw an exception when it tries to cast the object to that interface.

            # We avoid that problem by wrapping the result of GetDynamicParameters() in a one-element array with the
            # unary comma.  PowerShell enumerates that array instead of trying to enumerate the goofy object, and
            # everyone's happy.

            # Love the comma.  Don't delete it.  We don't have a test for this yet, unless we can get the AD module
            # on a Server 2008 R2 build server, or until we write some C# code to reproduce its goofy behavior.

            ,$cmdlet.GetDynamicParameters()
        }
        catch [System.NotImplementedException]
        {
            # Some cmdlets implement IDynamicParameters but then throw a NotImplementedException.  I have no idea why.  Ignore them.
        }
    }
}

function Get-DynamicParametersForMockedFunction
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $FunctionName,

        [string]
        $ModuleName,

        [System.Collections.IDictionary]
        $Parameters,

        [object]
        $Cmdlet
    )

    $mock = $mockTable["$ModuleName||$FunctionName"]

    if (-not $mock)
    {
        throw "Internal error detected:  Mock for '$FunctionName' in module '$ModuleName' was called, but does not exist in the mock table."
    }

    if ($mock.DynamicParamScriptBlock)
    {
        $splat = @{ 'P S Cmdlet' = $Cmdlet }
        return & $mock.DynamicParamScriptBlock @Parameters @splat
    }
}

function Test-IsClosure
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock
    )

    $sessionStateInternal = Get-ScriptBlockScope -ScriptBlock $ScriptBlock
    if ($null -eq $sessionStateInternal) { return $false }

    $flags = [System.Reflection.BindingFlags]'Instance,NonPublic'
    $module = $sessionStateInternal.GetType().GetProperty('Module', $flags).GetValue($sessionStateInternal, $null)

    return (
        $null -ne $module -and
        $module.Name -match '^__DynamicModule_([a-f\d-]+)$' -and
        $null -ne ($matches[1] -as [guid])
    )
}
tools\Functions\New-Fixture.ps1
function New-Fixture {
    <#
    .SYNOPSIS
    This function generates two scripts, one that defines a function
    and another one that contains its tests.

    .DESCRIPTION
    This function generates two scripts, one that defines a function
    and another one that contains its tests. The files are by default
    placed in the current directory and are called and populated as such:


    The script defining the function: .\Clean.ps1:

    function Clean {

    }

    The script containing the example test .\Clean.Tests.ps1:

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

    Describe "Clean" {

        It "does something useful" {
            $true | Should -Be $false
        }
    }


    .PARAMETER Name
    Defines the name of the function and the name of the test to be created.

    .PARAMETER Path
    Defines path where the test and the function should be created, you can use full or relative path.
    If the parameter is not specified the scripts are created in the current directory.

    .EXAMPLE
    New-Fixture -Name Clean

    Creates the scripts in the current directory.

    .EXAMPLE
    New-Fixture C:\Projects\Cleaner Clean

    Creates the scripts in the C:\Projects\Cleaner directory.

    .EXAMPLE
    New-Fixture Cleaner Clean

    Creates a new folder named Cleaner in the current directory and creates the scripts in it.

    .LINK
    Describe
    Context
    It
    about_Pester
    about_Should
    #>

    param (
        [String]$Path = $PWD,
        [Parameter(Mandatory=$true)]
        [String]$Name
    )

    $Name = $Name -replace '.ps1',''

    #region File contents
    #keep this formatted as is. the format is output to the file as is, including indentation
    $scriptCode = "function $Name {$([System.Environment]::NewLine)$([System.Environment]::NewLine)}"

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

Describe "#name#" {
    It "does something useful" {
        $true | Should -Be $false
    }
}' -replace "#name#",$Name

    #endregion

    $Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)

    Create-File -Path $Path -Name "$Name.ps1" -Content $scriptCode
    Create-File -Path $Path -Name "$Name.Tests.ps1" -Content $testCode
}

function Create-File ($Path,$Name,$Content) {
    if (-not (& $SafeCommands['Test-Path'] -Path $Path)) {
        & $SafeCommands['New-Item'] -ItemType Directory -Path $Path | & $SafeCommands['Out-Null']
    }

    $FullPath = & $SafeCommands['Join-Path'] -Path $Path -ChildPath $Name
    if (-not (& $SafeCommands['Test-Path'] -Path $FullPath)) {
        & $SafeCommands['Set-Content'] -Path  $FullPath -Value $Content -Encoding UTF8
        & $SafeCommands['Get-Item'] -Path $FullPath
    }
    else
    {
        # This is deliberately not sent through $SafeCommands, because our own tests rely on
        # mocking Write-Warning, and it's not really the end of the world if this call happens to
        # be screwed up in an edge case.
        Write-Warning "Skipping the file '$FullPath', because it already exists."
    }
}
tools\Functions\New-MockObject.ps1
function New-MockObject {
<#
.SYNOPSIS
This function instantiates a .NET object from a type.

.DESCRIPTION
Using the New-MockObject you can mock an object based on .NET type.

An .NET assembly for the particular type must be available in the system and loaded.

.PARAMETER Type
The .NET type to create an object based on.

.EXAMPLE
PS> $obj = New-MockObject -Type 'System.Diagnostics.Process'
PS> $obj.GetType().FullName
    System.Diagnostics.Process
#>

    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [type]$Type
    )

    [System.Runtime.Serialization.Formatterservices]::GetUninitializedObject($Type)

}
tools\Functions\Output.ps1
$Script:ReportStrings = DATA {
    @{
        StartMessage   = "Executing all tests in '{0}'"
        FilterMessage  = ' matching test name {0}'
        TagMessage     = ' with Tags {0}'
        MessageOfs     = "', '"

        CoverageTitle   = 'Code coverage report:'
        CoverageMessage = 'Covered {2:P2} of {3:N0} analyzed {0} in {4:N0} {1}.'
        MissedSingular  = 'Missed command:'
        MissedPlural    = 'Missed commands:'
        CommandSingular = 'Command'
        CommandPlural   = 'Commands'
        FileSingular    = 'File'
        FilePlural      = 'Files'

        Describe = 'Describing {0}'
        Script   = 'Executing script {0}'
        Context  = 'Context {0}'
        Margin   = '  '
        Timing   = 'Tests completed in {0}'

        # If this is set to an empty string, the count won't be printed
        ContextsPassed = ''
        ContextsFailed = ''

        TestsPassed       = 'Tests Passed: {0}, '
        TestsFailed       = 'Failed: {0}, '
        TestsSkipped      = 'Skipped: {0}, '
        TestsPending      = 'Pending: {0}, '
        TestsInconclusive = 'Inconclusive: {0} '
    }
}

$Script:ReportTheme = DATA {
    @{
        Describe       = 'Green'
        DescribeDetail = 'DarkYellow'
        Context        = 'Cyan'
        ContextDetail  = 'DarkCyan'
        Pass           = 'DarkGreen'
        PassTime       = 'DarkGray'
        Fail           = 'Red'
        FailTime       = 'DarkGray'
        Skipped        = 'Yellow'
        Pending        = 'Gray'
        Inconclusive   = 'Gray'
        Incomplete     = 'Yellow'
        IncompleteTime = 'DarkGray'
        Foreground     = 'White'
        Information    = 'DarkGray'
        Coverage       = 'White'
        CoverageWarn   = 'DarkRed'
    }
}

function Format-PesterPath ($Path, [String]$Delimiter) {
    # -is check is not enough for the arrays, the incoming value will likely be object[]
    # so we have to check if we can upcast to our required type

    if ($null -eq $Path)
    {
        $null
    }
    elseif ($Path -is [String])
    {
        $Path
    }
    elseif ($Path -is [hashtable])
    {
        # a well formed pester hashtable contains Path
        $Path.Path
    }
    elseif ($null -ne ($path -as [hashtable[]]))
    {
        ($path | foreach { $_.Path }) -join $Delimiter
    }
    # needs to stay at the bottom because almost everything can be upcast to array of string
    elseif ($Path -as [String[]])
    {
        $Path -join $Delimiter
    }
}
function Write-PesterStart {
    param(
        [Parameter(mandatory=$true, valueFromPipeline=$true)]
        $PesterState,
        $Path = $Path
    )
    process {
        if(-not ( $pester.Show | Has-Flag Header)) { return }

        $OFS = $ReportStrings.MessageOfs

        $message = $ReportStrings.StartMessage -f (Format-PesterPath $Path -Delimiter $OFS)
        if ($PesterState.TestNameFilter) {
           $message += $ReportStrings.FilterMessage -f "$($PesterState.TestNameFilter)"
        }
        if ($PesterState.TagFilter) {
           $message += $ReportStrings.TagMessage -f "$($PesterState.TagFilter)"
        }

        & $SafeCommands['Write-Host'] $message -Foreground $ReportTheme.Foreground
    }
}

function Write-Describe {
    param (
        [Parameter(mandatory=$true, valueFromPipeline=$true)]
        $Describe,

        [string] $CommandUsed = 'Describe'
    )
    process {
        if(-not ( $pester.Show | Has-Flag Describe)) { return }

        $margin = $ReportStrings.Margin * $pester.IndentLevel

        $Text = if($Describe.PSObject.Properties['Name'] -and $Describe.Name) {
            $ReportStrings.$CommandUsed -f $Describe.Name
        } else {
            $ReportStrings.$CommandUsed -f $Describe
        }

        & $SafeCommands['Write-Host']
        & $SafeCommands['Write-Host'] "${margin}${Text}" -ForegroundColor $ReportTheme.Describe
        # If the feature has a longer description, write that too
        if($Describe.PSObject.Properties['Description'] -and $Describe.Description) {
            $Describe.Description -split "$([System.Environment]::NewLine)" | ForEach {
                & $SafeCommands['Write-Host'] ($ReportStrings.Margin * ($pester.IndentLevel + 1)) $_ -ForegroundColor $ReportTheme.DescribeDetail
            }
        }
    }
}

function Write-Context {
    param (
        [Parameter(mandatory=$true, valueFromPipeline=$true)]
        $Context
    )
    process {
        if(-not ( $pester.Show | Has-Flag Context)) { return }
        $Text = if($Context.PSObject.Properties['Name'] -and $Context.Name) {
                $ReportStrings.Context -f $Context.Name
            } else {
                $ReportStrings.Context -f $Context
            }

        & $SafeCommands['Write-Host']
        & $SafeCommands['Write-Host'] ($ReportStrings.Margin + $Text) -ForegroundColor $ReportTheme.Context
        # If the scenario has a longer description, write that too
        if($Context.PSObject.Properties['Description'] -and $Context.Description) {
            $Context.Description -split "$([System.Environment]::NewLine)" | ForEach {
                & $SafeCommands['Write-Host'] (" " * $ReportStrings.Context.Length) $_ -ForegroundColor $ReportTheme.ContextDetail
            }
        }
    }
}

function ConvertTo-PesterResult {
    param(
        [String] $Name,
        [Nullable[TimeSpan]] $Time,
        [System.Management.Automation.ErrorRecord] $ErrorRecord
    )

    $testResult = @{
        name = $Name
        time = $time
        failureMessage = ""
        stackTrace = ""
        ErrorRecord = $null
        success = $false
        result = "Failed"
    }

    if(-not $ErrorRecord)
    {
        $testResult.Result = "Passed"
        $testResult.success = $true
        return $testResult
    }

    if ($ErrorRecord.FullyQualifiedErrorID -eq 'PesterAssertionFailed')
    {
        # we use TargetObject to pass structured information about the error.
        $details = $ErrorRecord.TargetObject

        $failureMessage = $details.Message
        $file = $details.File
        $line = $details.Line
        $Text = $details.LineText
    }
    elseif ($ErrorRecord.FullyQualifiedErrorId -eq 'PesterTestInconclusive')
    {
        # we use TargetObject to pass structured information about the error.
        $details = $ErrorRecord.TargetObject

        $failureMessage = $details.Message
        $file = $details.File
        $line = $details.Line
        $text = $details.LineText

        $testResult.Result = 'Inconclusive'
    }
    else
    {
        $failureMessage = $ErrorRecord.ToString()
        $file = $ErrorRecord.InvocationInfo.ScriptName
        $line = $ErrorRecord.InvocationInfo.ScriptLineNumber
        $Text = $ErrorRecord.InvocationInfo.Line
    }

    $testResult.failureMessage = $failureMessage
    $testResult.stackTrace = "at <ScriptBlock>, ${file}: line ${line}$([System.Environment]::NewLine)${line}: ${Text}"
    $testResult.ErrorRecord = $ErrorRecord

    return $testResult
}

function Remove-Comments ($Text)
{
    $text -replace "(?s)(<#.*#>)" -replace "\#.*"
}

function Write-PesterResult {
    param (
        [Parameter(mandatory=$true, valueFromPipeline=$true)]
        $TestResult
    )

    process {
        $quiet = $pester.Show -eq [Pester.OutputTypes]::None
        $OutputType = [Pester.OutputTypes] $TestResult.Result
        $writeToScreen = $pester.Show | Has-Flag $OutputType
        $skipOutput = $quiet -or (-not $writeToScreen)

        if ($skipOutput)
        {
            return
        }

        $margin = $ReportStrings.Margin * ($pester.IndentLevel + 1)
        $error_margin = $margin + $ReportStrings.Margin
        $output = $TestResult.name
        $humanTime = Get-HumanTime $TestResult.Time.TotalSeconds

        if (-not ($OutputType | Has-Flag 'Default, Summary'))
        {
            switch ($TestResult.Result)
            {
                Passed {
                    & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Pass "$margin[+] $output " -NoNewLine
                    & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.PassTime $humanTime
                    break
                }

                Failed {
                    & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Fail "$margin[-] $output " -NoNewLine
                    & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.FailTime $humanTime

                    if($pester.IncludeVSCodeMarker) {
                        & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Fail $($TestResult.stackTrace -replace '(?m)^',$error_margin)
                        & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Fail $($TestResult.failureMessage -replace '(?m)^',$error_margin)
                    }
                    else {
                        $TestResult.ErrorRecord |
                        ConvertTo-FailureLines |
                        foreach {$_.Message + $_.Trace} |
                        foreach { & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Fail $($_ -replace '(?m)^',$error_margin) }
                    }
                    break
                }

                Skipped {
                    & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Skipped "$margin[!] $output $humanTime"
                    break
                }

                Pending {
                    & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Pending "$margin[?] $output $humanTime"
                    break
                }

                Inconclusive {
                    & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Inconclusive "$margin[?] $output $humanTime"

                    if ($testresult.FailureMessage) {
                        & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Inconclusive $($TestResult.failureMessage -replace '(?m)^',$error_margin)
                    }

                    & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Inconclusive $($TestResult.stackTrace -replace '(?m)^',$error_margin)
                    break
                }

                default {
                    # TODO:  Add actual Incomplete status as default rather than checking for null time.
                    if($null -eq $TestResult.Time) {
                        & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Incomplete "$margin[?] $output " -NoNewLine
                        & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.IncompleteTime $humanTime
                    }
                }
            }
        }
    }
}

function Write-PesterReport {
    param (
        [Parameter(mandatory=$true, valueFromPipeline=$true)]
        $PesterState
    )
    if(-not ($PesterState.Show | Has-Flag Summary)) { return }

    & $SafeCommands['Write-Host'] ($ReportStrings.Timing -f (Get-HumanTime $PesterState.Time.TotalSeconds)) -Foreground $ReportTheme.Foreground

    $Success, $Failure = if($PesterState.FailedCount -gt 0) {
                            $ReportTheme.Foreground, $ReportTheme.Fail
                         } else {
                            $ReportTheme.Pass, $ReportTheme.Information
                         }
    $Skipped = if($PesterState.SkippedCount -gt 0) { $ReportTheme.Skipped } else { $ReportTheme.Information }
    $Pending = if($PesterState.PendingCount -gt 0) { $ReportTheme.Pending } else { $ReportTheme.Information }
    $Inconclusive = if($PesterState.InconclusiveCount -gt 0) { $ReportTheme.Inconclusive } else { $ReportTheme.Information }

    Try {
        $PesterStatePassedScenariosCount = $PesterState.PassedScenarios.Count
    }
    Catch {
        $PesterStatePassedScenariosCount = 0
    }

    Try {
        $PesterStateFailedScenariosCount = $PesterState.FailedScenarios.Count
    }
    Catch {
        $PesterStateFailedScenariosCount = 0
    }

    if($ReportStrings.ContextsPassed) {
        & $SafeCommands['Write-Host'] ($ReportStrings.ContextsPassed -f $PesterStatePassedScenariosCount) -Foreground $Success -NoNewLine
        & $SafeCommands['Write-Host'] ($ReportStrings.ContextsFailed -f $PesterStateFailedScenariosCount) -Foreground $Failure
    }
    if($ReportStrings.TestsPassed) {
        & $SafeCommands['Write-Host'] ($ReportStrings.TestsPassed -f $PesterState.PassedCount) -Foreground $Success -NoNewLine
        & $SafeCommands['Write-Host'] ($ReportStrings.TestsFailed -f $PesterState.FailedCount) -Foreground $Failure -NoNewLine
        & $SafeCommands['Write-Host'] ($ReportStrings.TestsSkipped -f $PesterState.SkippedCount) -Foreground $Skipped -NoNewLine
        & $SafeCommands['Write-Host'] ($ReportStrings.TestsPending -f $PesterState.PendingCount) -Foreground $Pending -NoNewLine
        & $SafeCommands['Write-Host'] ($ReportStrings.TestsInconclusive -f $PesterState.InconclusiveCount) -Foreground $Inconclusive
    }
}

function Write-CoverageReport {
    param ([object] $CoverageReport)

    if ($null -eq $CoverageReport -or ($pester.Show -eq [Pester.OutputTypes]::None) -or $CoverageReport.NumberOfCommandsAnalyzed -eq 0)
    {
        return
    }

    $totalCommandCount = $CoverageReport.NumberOfCommandsAnalyzed
    $fileCount = $CoverageReport.NumberOfFilesAnalyzed
    $executedPercent = ($CoverageReport.NumberOfCommandsExecuted / $CoverageReport.NumberOfCommandsAnalyzed).ToString("P2")

    $command = if ($totalCommandCount -gt 1) { $ReportStrings.CommandPlural } else { $ReportStrings.CommandSingular }
    $file = if ($fileCount -gt 1) { $ReportStrings.FilePlural } else { $ReportStrings.FileSingular }

    $commonParent = Get-CommonParentPath -Path $CoverageReport.AnalyzedFiles
    $report = $CoverageReport.MissedCommands | & $SafeCommands['Select-Object'] -Property @(
        @{ Name = 'File'; Expression = { Get-RelativePath -Path $_.File -RelativeTo $commonParent } }
        'Function'
        'Line'
        'Command'
    )

    & $SafeCommands['Write-Host']
    & $SafeCommands['Write-Host'] $ReportStrings.CoverageTitle -Foreground $ReportTheme.Coverage

    if ($CoverageReport.MissedCommands.Count -gt 0)
    {
        & $SafeCommands['Write-Host'] ($ReportStrings.CoverageMessage -f $command, $file, $executedPercent, $totalCommandCount, $fileCount) -Foreground $ReportTheme.CoverageWarn
        if ($CoverageReport.MissedCommands.Count -eq 1)
        {
            & $SafeCommands['Write-Host'] $ReportStrings.MissedSingular -Foreground $ReportTheme.CoverageWarn
        } else {
            & $SafeCommands['Write-Host'] $ReportStrings.MissedPlural -Foreground $ReportTheme.CoverageWarn
        }
        $report | & $SafeCommands['Format-Table'] -AutoSize | & $SafeCommands['Out-Host']
    } else {
        & $SafeCommands['Write-Host'] ($ReportStrings.CoverageMessage -f $command, $file, $executedPercent, $totalCommandCount, $fileCount) -Foreground $ReportTheme.Coverage
    }
}

function ConvertTo-FailureLines
{
    param (
        [Parameter(mandatory=$true, valueFromPipeline=$true)]
        $ErrorRecord
    )
    process {
        $lines = & $script:SafeCommands['New-Object'] psobject -Property @{
            Message = @()
            Trace = @()
        }

        ## convert the exception messages
        $exception = $ErrorRecord.Exception
        $exceptionLines = @()

        while ($exception)
        {
            $exceptionName = $exception.GetType().Name
            $thisLines = $exception.Message.Split([string[]]($([System.Environment]::NewLine),"\n","`n"), [System.StringSplitOptions]::RemoveEmptyEntries)
            if ($ErrorRecord.FullyQualifiedErrorId -ne 'PesterAssertionFailed')
            {
                $thisLines[0] = "$exceptionName`: $($thisLines[0])"
            }
            [array]::Reverse($thisLines)
            $exceptionLines += $thisLines
            $exception = $exception.InnerException
        }
        [array]::Reverse($exceptionLines)
        $lines.Message += $exceptionLines
        if ($ErrorRecord.FullyQualifiedErrorId -eq 'PesterAssertionFailed')
        {
            $lines.Message += "$($ErrorRecord.TargetObject.Line)`: $($ErrorRecord.TargetObject.LineText)".Split([string[]]($([System.Environment]::NewLine),"\n","`n"),  [System.StringSplitOptions]::RemoveEmptyEntries)
        }

        if ( -not ($ErrorRecord | & $SafeCommands['Get-Member'] -Name ScriptStackTrace) )
        {
            if ($ErrorRecord.FullyQualifiedErrorID -eq 'PesterAssertionFailed')
            {
                $lines.Trace += "at line: $($ErrorRecord.TargetObject.Line) in $($ErrorRecord.TargetObject.File)"
            }
            else
            {
                $lines.Trace += "at line: $($ErrorRecord.InvocationInfo.ScriptLineNumber) in $($ErrorRecord.InvocationInfo.ScriptName)"
            }
            return $lines
        }

        ## convert the stack trace if present (there might be none if we are raising the error ourselves)
        # todo: this is a workaround see https://github.com/pester/Pester/pull/886
        if ($null -ne $ErrorRecord.ScriptStackTrace) {
            $traceLines = $ErrorRecord.ScriptStackTrace.Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries)
        }

        $count = 0

        # omit the lines internal to Pester

        If ((GetPesterOS) -ne 'Windows') {

            [String]$pattern1 = '^at (Invoke-Test|Context|Describe|InModuleScope|Invoke-Pester), .*/Functions/.*.ps1: line [0-9]*$'
            [String]$pattern2 = '^at Should<End>, .*/Functions/Assertions/Should.ps1: line [0-9]*$'
            [String]$pattern3 = '^at Assert-MockCalled, .*/Functions/Mock.ps1: line [0-9]*$'
        }
        Else {

            [String]$pattern1 = '^at (Invoke-Test|Context|Describe|InModuleScope|Invoke-Pester), .*\\Functions\\.*.ps1: line [0-9]*$'
            [String]$pattern2 = '^at Should<End>, .*\\Functions\\Assertions\\Should.ps1: line [0-9]*$'
            [String]$pattern3 = '^at Assert-MockCalled, .*\\Functions\\Mock.ps1: line [0-9]*$'

        }

        foreach ( $line in $traceLines )
        {
            if ( $line -match $pattern1 )
            {
                break
            }
            $count ++
        }
        $lines.Trace += $traceLines |
            & $SafeCommands['Select-Object'] -First $count |
            & $SafeCommands['Where-Object'] {
                $_ -notmatch $pattern2 -and
                $_ -notmatch $pattern3
            }

        return $lines
    }
}
tools\Functions\PesterState.ps1
function New-PesterState
{
    param (
        [String[]]$TagFilter,
        [String[]]$ExcludeTagFilter,
        [String[]]$TestNameFilter,
        [System.Management.Automation.SessionState]$SessionState,
        [Switch]$Strict,
        [Pester.OutputTypes]$Show = 'All',
        [object]$PesterOption
    )

    if ($null -eq $SessionState) { $SessionState = $ExecutionContext.SessionState }

    if ($null -eq $PesterOption)
    {
        $PesterOption = New-PesterOption
    }
    elseif ($PesterOption -is [System.Collections.IDictionary])
    {
        try
        {
            $PesterOption = New-PesterOption @PesterOption
        }
        catch
        {
            throw
        }
    }

    & $SafeCommands['New-Module'] -Name Pester -AsCustomObject -ArgumentList $TagFilter, $ExcludeTagFilter, $TestNameFilter, $SessionState, $Strict, $Show, $PesterOption -ScriptBlock {
        param (
            [String[]]$_tagFilter,
            [String[]]$_excludeTagFilter,
            [String[]]$_testNameFilter,
            [System.Management.Automation.SessionState]$_sessionState,
            [Switch]$Strict,
            [Pester.OutputTypes]$Show,
            [object]$PesterOption
        )

        #public read-only
        $TagFilter = $_tagFilter
        $ExcludeTagFilter = $_excludeTagFilter
        $TestNameFilter = $_testNameFilter

        $script:SessionState = $_sessionState
        $script:Stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
        $script:MostRecentTimestamp = 0
        $script:CommandCoverage = @()
        $script:Strict = $Strict
        $script:Show = $Show
        $script:InTest = $false

        $script:TestResult = @()

        $script:TotalCount = 0
        $script:Time = [timespan]0
        $script:PassedCount = 0
        $script:FailedCount = 0
        $script:SkippedCount = 0
        $script:PendingCount = 0
        $script:InconclusiveCount = 0

        $script:IncludeVSCodeMarker = $PesterOption.IncludeVSCodeMarker
        $script:TestSuiteName       = $PesterOption.TestSuiteName

        $script:SafeCommands = @{}

        $script:SafeCommands['New-Object']          = & (Pester\SafeGetCommand) -Name New-Object          -Module Microsoft.PowerShell.Utility -CommandType Cmdlet
        $script:SafeCommands['Select-Object']       = & (Pester\SafeGetCommand) -Name Select-Object       -Module Microsoft.PowerShell.Utility -CommandType Cmdlet
        $script:SafeCommands['Export-ModuleMember'] = & (Pester\SafeGetCommand) -Name Export-ModuleMember -Module Microsoft.PowerShell.Core    -CommandType Cmdlet
        $script:SafeCommands['Add-Member']          = & (Pester\SafeGetCommand) -Name Add-Member          -Module Microsoft.PowerShell.Utility -CommandType Cmdlet

        function New-TestGroup([string] $Name, [string] $Hint)
        {
            & $SafeCommands['New-Object'] psobject -Property @{
                Name              = $Name
                Type              = 'TestGroup'
                Hint              = $Hint
                Actions           = [System.Collections.ArrayList]@()
                BeforeEach        = & $SafeCommands['New-Object'] System.Collections.Generic.List[scriptblock]
                AfterEach         = & $SafeCommands['New-Object'] System.Collections.Generic.List[scriptblock]
                BeforeAll         = & $SafeCommands['New-Object'] System.Collections.Generic.List[scriptblock]
                AfterAll          = & $SafeCommands['New-Object'] System.Collections.Generic.List[scriptblock]
                TotalCount        = 0
                Time              = [timespan]0
                PassedCount       = 0
                FailedCount       = 0
                SkippedCount      = 0
                PendingCount      = 0
                InconclusiveCount = 0
            }
        }

        $script:TestActions = New-TestGroup -Name Pester -Hint Root
        $script:TestGroupStack = & $SafeCommands['New-Object'] System.Collections.Stack
        $script:TestGroupStack.Push($script:TestActions)

        function EnterTestGroup([string] $Name, [string] $Hint)
        {
            $newGroup = New-TestGroup @PSBoundParameters
            $null = $script:TestGroupStack.Peek().Actions.Add($newGroup)
            $script:TestGroupStack.Push($newGroup)
        }

        function LeaveTestGroup([string] $Name, [string] $Hint)
        {
            $currentGroup = $script:TestGroupStack.Pop()

            if ($currentGroup.Name -ne $Name -or $currentGroup.Hint -ne $Hint)
            {
                throw "TestGroups stack corrupted:  Expected name/hint of '$Name','$Hint'.  Found '$($currentGroup.Name)', '$($currentGroup.Hint)'."
            }
        }

        function AddTestResult
        {
            param (
                [string]$Name,
                [ValidateSet("Failed","Passed","Skipped","Pending","Inconclusive")]
                [string]$Result,
                [Nullable[TimeSpan]]$Time,
                [string]$FailureMessage,
                [string]$StackTrace,
                [string] $ParameterizedSuiteName,
                [System.Collections.IDictionary] $Parameters,
                [System.Management.Automation.ErrorRecord] $ErrorRecord
            )

            # defining this function in here, because otherwise it is not available
            function New-ErrorRecord ([string] $Message, [string] $ErrorId, [string] $File, [string] $Line, [string] $LineText) {
                $exception = & $SafeCommands['New-Object'] Exception $Message
                $errorCategory = [Management.Automation.ErrorCategory]::InvalidResult
                # we use ErrorRecord.TargetObject to pass structured information about the error to a reporting system.
                $targetObject = @{Message = $Message; File = $File; Line = $Line; LineText = $LineText}
                $errorRecord = & $SafeCommands['New-Object'] Management.Automation.ErrorRecord $exception, $ErrorID, $errorCategory, $targetObject
                return $errorRecord
            }

            $previousTime = $script:MostRecentTimestamp
            $script:MostRecentTimestamp = $script:Stopwatch.Elapsed

            if ($null -eq $Time)
            {
                $Time = $script:MostRecentTimestamp - $previousTime
            }

            if (-not $script:Strict)
            {
                $Passed = "Passed","Skipped","Pending" -contains $Result
            }
            else
            {
                $Passed = $Result -eq "Passed"
                if (($Result -eq "Skipped") -or ($Result -eq "Pending"))
                {
                    $FailureMessage = "The test failed because the test was executed in Strict mode and the result '$result' was translated to Failed."
                    $ErrorRecord = New-ErrorRecord -ErrorId 'PesterTestInconclusive' -Message $FailureMessage
                    $Result = "Failed"
                }

            }

            $script:TotalCount++
            $script:Time += $Time

            switch ($Result)
            {
                Passed  { $script:PassedCount++; break; }
                Failed  { $script:FailedCount++; break; }
                Skipped { $script:SkippedCount++; break; }
                Pending { $script:PendingCount++; break; }
                Inconclusive { $script:InconclusiveCount++; break; }
            }

            $resultRecord = & $SafeCommands['New-Object'] -TypeName PsObject -Property @{
                Name                   = $Name
                Type                   = 'TestCase'
                Passed                 = $Passed
                Result                 = $Result
                Time                   = $Time
                FailureMessage         = $FailureMessage
                StackTrace             = $StackTrace
                ErrorRecord            = $ErrorRecord
                ParameterizedSuiteName = $ParameterizedSuiteName
                Parameters             = $Parameters
                Show                   = $script:Show
            }

            $null = $script:TestGroupStack.Peek().Actions.Add($resultRecord)

            # Attempting some degree of backward compatibility for the TestResult collection for now; deprecated and will be removed in the future
            $describe = ''
            $contexts = [System.Collections.ArrayList]@()

            # make a copy of the stack and reverse it
            $reversedStack = $script:TestGroupStack.ToArray()
            [array]::Reverse($reversedStack)

            foreach ($group in $reversedStack)
            {
                if ($group.Hint -eq 'Root' -or $group.Hint -eq 'Script') { continue }
                if ($describe -eq '')
                {
                    $describe = $group.Name
                }
                else
                {
                    $null = $contexts.Add($group.Name)
                }
            }

            $context = $contexts -join '\'

            $script:TestResult += & $SafeCommands['New-Object'] psobject -Property @{
                Describe               = $describe
                Context                = $context
                Name                   = $Name
                Passed                 = $Passed
                Result                 = $Result
                Time                   = $Time
                FailureMessage         = $FailureMessage
                StackTrace             = $StackTrace
                ErrorRecord            = $ErrorRecord
                ParameterizedSuiteName = $ParameterizedSuiteName
                Parameters             = $Parameters
                Show                  = $script:Show
            }
        }

        function AddSetupOrTeardownBlock([scriptblock] $ScriptBlock, [string] $CommandName)
        {
            $currentGroup = $script:TestGroupStack.Peek()

            $isSetupCommand = IsSetupCommand -CommandName $CommandName
            $isGroupCommand = IsTestGroupCommand -CommandName $CommandName

            if ($isSetupCommand)
            {
                if ($isGroupCommand)
                {
                    $currentGroup.BeforeAll.Add($ScriptBlock)
                }
                else
                {
                    $currentGroup.BeforeEach.Add($ScriptBlock)
                }
            }
            else
            {
                if ($isGroupCommand)
                {
                    $currentGroup.AfterAll.Add($ScriptBlock)
                }
                else
                {
                    $currentGroup.AfterEach.Add($ScriptBlock)
                }
            }
        }

        function IsSetupCommand
        {
            param ([string] $CommandName)
            return $CommandName -eq 'BeforeEach' -or $CommandName -eq 'BeforeAll'
        }

        function IsTestGroupCommand
        {
            param ([string] $CommandName)
            return $CommandName -eq 'BeforeAll' -or $CommandName -eq 'AfterAll'
        }

        function GetTestCaseSetupBlocks
        {
            $blocks = @(
                foreach ($group in $this.TestGroups)
                {
                    $group.BeforeEach
                }
            )

            return $blocks
        }

        function GetTestCaseTeardownBlocks
        {
            $groups = @($this.TestGroups)
            [Array]::Reverse($groups)

            $blocks = @(
                foreach ($group in $groups)
                {
                    $group.AfterEach
                }
            )

            return $blocks
        }

        function GetCurrentTestGroupSetupBlocks
        {
            return $script:TestGroupStack.Peek().BeforeAll
        }

        function GetCurrentTestGroupTeardownBlocks
        {
            return $script:TestGroupStack.Peek().AfterAll
        }

        function EnterTest
        {
            if ($script:InTest)
            {
                throw 'You are already in a test case.'
            }

            $script:InTest = $true
        }

        function LeaveTest
        {
            $script:InTest = $false
        }

        $ExportedVariables = "TagFilter",
        "ExcludeTagFilter",
        "TestNameFilter",
        "TestResult",
        "SessionState",
        "CommandCoverage",
        "Strict",
        "Show",
        "Time",
        "TotalCount",
        "PassedCount",
        "FailedCount",
        "SkippedCount",
        "PendingCount",
        "InconclusiveCount",
        "IncludeVSCodeMarker",
        "TestActions",
        "TestGroupStack",
        "TestSuiteName",
        "InTest"

        $ExportedFunctions = "EnterTestGroup",
                             "LeaveTestGroup",
                             "AddTestResult",
                             "AddSetupOrTeardownBlock",
                             "GetTestCaseSetupBlocks",
                             "GetTestCaseTeardownBlocks",
                             "GetCurrentTestGroupSetupBlocks",
                             "GetCurrentTestGroupTeardownBlocks",
                             "EnterTest",
                             "LeaveTest"

        & $SafeCommands['Export-ModuleMember'] -Variable $ExportedVariables -function $ExportedFunctions
    }  |
    & $SafeCommands['Add-Member'] -PassThru -MemberType ScriptProperty -Name CurrentTestGroup -Value {
        $this.TestGroupStack.Peek()
    } |
    & $SafeCommands['Add-Member'] -PassThru -MemberType ScriptProperty -Name TestGroups -Value {
        $array = $this.TestGroupStack.ToArray()
        [Array]::Reverse($array)
        return $array
    } |
    & $SafeCommands['Add-Member'] -PassThru -MemberType ScriptProperty -Name IndentLevel -Value {
        # We ignore the root node of the stack here, and don't start indenting until after the Script nodes inside the root
        return [Math]::Max(0, $this.TestGroupStack.Count - 2)
    }
}
tools\Functions\SetupTeardown.ps1
function BeforeEach
{
<#
.SYNOPSIS
    Defines a series of steps to perform at the beginning of every It block within
    the current Context or Describe block.

.DESCRIPTION
    BeforeEach, AfterEach, BeforeAll, and AfterAll are unique in that they apply
    to the entire Context or Describe block, regardless of the order of the
    statements in the Context or Describe.  For a full description of this
    behavior, as well as how multiple BeforeEach or AfterEach blocks interact
    with each other, please refer to the about_BeforeEach_AfterEach help file.

.LINK
    about_BeforeEach_AfterEach
#>
    [CmdletBinding()]
    param
    (
        # the scriptblock to execute
        [Parameter(Mandatory = $true,
                   Position = 1)]
        [Scriptblock]
        $Scriptblock
    )
    Assert-DescribeInProgress -CommandName BeforeEach
}

function AfterEach
{
<#
.SYNOPSIS
    Defines a series of steps to perform at the end of every It block within
    the current Context or Describe block.

.DESCRIPTION
    BeforeEach, AfterEach, BeforeAll, and AfterAll are unique in that they apply
    to the entire Context or Describe block, regardless of the order of the
    statements in the Context or Describe.  For a full description of this
    behavior, as well as how multiple BeforeEach or AfterEach blocks interact
    with each other, please refer to the about_BeforeEach_AfterEach help file.

.LINK
    about_BeforeEach_AfterEach
#>
    [CmdletBinding()]
    param
    (
        # the scriptblock to execute
        [Parameter(Mandatory = $true,
                   Position = 1)]
        [Scriptblock]
        $Scriptblock
    )
    Assert-DescribeInProgress -CommandName AfterEach
}

function BeforeAll
{
<#
.SYNOPSIS
    Defines a series of steps to perform at the beginning of the current Context
    or Describe block.

.DESCRIPTION
    BeforeEach, AfterEach, BeforeAll, and AfterAll are unique in that they apply
    to the entire Context or Describe block, regardless of the order of the
    statements in the Context or Describe.

.LINK
    about_BeforeEach_AfterEach
#>
    [CmdletBinding()]
    param
    (
        # the scriptblock to execute
        [Parameter(Mandatory = $true,
                   Position = 1)]
        [Scriptblock]
        $Scriptblock
    )
    Assert-DescribeInProgress -CommandName BeforeAll
}

function AfterAll
{
<#
.SYNOPSIS
    Defines a series of steps to perform at the end of every It block within
    the current Context or Describe block.

.DESCRIPTION
    BeforeEach, AfterEach, BeforeAll, and AfterAll are unique in that they apply
    to the entire Context or Describe block, regardless of the order of the
    statements in the Context or Describe.

.LINK
    about_BeforeEach_AfterEach
#>
    [CmdletBinding()]
    param
    (
        # the scriptblock to execute
        [Parameter(Mandatory = $true,
                   Position = 1)]
        [Scriptblock]
        $Scriptblock
    )
    Assert-DescribeInProgress -CommandName AfterAll
}

function Invoke-TestCaseSetupBlocks
{
    Invoke-Blocks -ScriptBlock $pester.GetTestCaseSetupBlocks()
}

function Invoke-TestCaseTeardownBlocks
{
    Invoke-Blocks -ScriptBlock $pester.GetTestCaseTeardownBlocks()
}

function Invoke-TestGroupSetupBlocks
{
    Invoke-Blocks -ScriptBlock $pester.GetCurrentTestGroupSetupBlocks()
}

function Invoke-TestGroupTeardownBlocks
{
    Invoke-Blocks -ScriptBlock $pester.GetCurrentTestGroupTeardownBlocks()
}

function Invoke-Blocks
{
    param ([scriptblock[]] $ScriptBlock)

    foreach ($block in $ScriptBlock)
    {
        if ($null -eq $block) { continue }
        $null = . $block
    }
}

function Add-SetupAndTeardown
{
    param (
        [scriptblock] $ScriptBlock
    )

    if ($PSVersionTable.PSVersion.Major -le 2)
    {
        Add-SetupAndTeardownV2 -ScriptBlock $ScriptBlock
    }
    else
    {
        Add-SetupAndTeardownV3 -ScriptBlock $ScriptBlock
    }
}

function Add-SetupAndTeardownV3
{
    param (
        [scriptblock] $ScriptBlock
    )

    $pattern = '^(?:Before|After)(?:Each|All)$'
    $predicate = {
        param ([System.Management.Automation.Language.Ast] $Ast)

        $Ast -is [System.Management.Automation.Language.CommandAst] -and
        $Ast.CommandElements.Count -eq 2 -and
        $Ast.CommandElements[0].ToString() -match $pattern -and
        $Ast.CommandElements[1] -is [System.Management.Automation.Language.ScriptBlockExpressionAst]
    }

    $searchNestedBlocks = $false

    $calls = $ScriptBlock.Ast.FindAll($predicate, $searchNestedBlocks)

    foreach ($call in $calls)
    {
        # For some reason, calling ScriptBlockAst.GetScriptBlock() sometimes blows up due to failing semantics
        # checks, even though the code is perfectly valid.  So we'll poke around with reflection again to skip
        # that part and just call the internal ScriptBlock constructor that we need

        $iPmdProviderType = [scriptblock].Assembly.GetType('System.Management.Automation.Language.IParameterMetadataProvider')

        $flags = [System.Reflection.BindingFlags]'Instance, NonPublic'
        $constructor = [scriptblock].GetConstructor($flags, $null, [Type[]]@($iPmdProviderType, [bool]), $null)

        $block = $constructor.Invoke(@($call.CommandElements[1].ScriptBlock, $false))

        Set-ScriptBlockScope -ScriptBlock $block -SessionState $pester.SessionState
        $commandName = $call.CommandElements[0].ToString()
        Add-SetupOrTeardownScriptBlock -CommandName $commandName -ScriptBlock $block
    }
}

function Add-SetupAndTeardownV2
{
    param (
        [scriptblock] $ScriptBlock
    )

    $codeText = $ScriptBlock.ToString()
    $tokens = @(ParseCodeIntoTokens -CodeText $codeText)

    for ($i = 0; $i -lt $tokens.Count; $i++)
    {
        $token = $tokens[$i]
        $type = $token.Type
        if ($type -eq [System.Management.Automation.PSTokenType]::Command -and
            (IsSetupOrTeardownCommand -CommandName $token.Content))
        {
            $openBraceIndex, $closeBraceIndex = Get-BraceIndicesForCommand -Tokens $tokens -CommandIndex $i

            $block = Get-ScriptBlockFromTokens -Tokens $Tokens -OpenBraceIndex $openBraceIndex -CloseBraceIndex $closeBraceIndex -CodeText $codeText
            Add-SetupOrTeardownScriptBlock -CommandName $token.Content -ScriptBlock $block

            $i = $closeBraceIndex
        }
        elseif ($type -eq [System.Management.Automation.PSTokenType]::GroupStart)
        {
            # We don't want to parse Setup or Teardown commands in child scopes here, so anything
            # bounded by a GroupStart / GroupEnd token pair which is not immediately preceded by
            # a setup / teardown command name is ignored.
            $i = Get-GroupCloseTokenIndex -Tokens $tokens -GroupStartTokenIndex $i
        }
    }
}

function ParseCodeIntoTokens
{
    param ([string] $CodeText)

    $parseErrors = $null
    $tokens = [System.Management.Automation.PSParser]::Tokenize($CodeText, [ref] $parseErrors)

    if ($parseErrors.Count -gt 0)
    {
        $currentScope = $pester.CurrentTestGroup.Hint
        if (-not $currentScope) { $currentScope = 'test group' }
        throw "The current $currentScope block contains syntax errors."
    }

    return $tokens
}

function IsSetupOrTeardownCommand
{
    param ([string] $CommandName)
    return (IsSetupCommand -CommandName $CommandName) -or (IsTeardownCommand -CommandName $CommandName)
}

function IsSetupCommand
{
    param ([string] $CommandName)
    return $CommandName -eq 'BeforeEach' -or $CommandName -eq 'BeforeAll'
}

function IsTeardownCommand
{
    param ([string] $CommandName)
    return $CommandName -eq 'AfterEach' -or $CommandName -eq 'AfterAll'
}

function IsTestGroupCommand
{
    param ([string] $CommandName)
    return $CommandName -eq 'BeforeAll' -or $CommandName -eq 'AfterAll'
}

function Get-BraceIndicesForCommand
{
    param (
        [System.Management.Automation.PSToken[]] $Tokens,
        [int] $CommandIndex
    )

    $openingGroupTokenIndex = Get-GroupStartTokenForCommand -Tokens $Tokens -CommandIndex $CommandIndex
    $closingGroupTokenIndex = Get-GroupCloseTokenIndex -Tokens $Tokens -GroupStartTokenIndex $openingGroupTokenIndex

    return $openingGroupTokenIndex, $closingGroupTokenIndex
}

function Get-GroupStartTokenForCommand
{
    param (
        [System.Management.Automation.PSToken[]] $Tokens,
        [int] $CommandIndex
    )

    # We may want to allow newlines, other parameters, etc at some point.  For now it's good enough to
    # just verify that the next token after our BeforeEach or AfterEach command is an opening curly brace.

    $commandName = $Tokens[$CommandIndex].Content

    if ($CommandIndex + 1 -ge $tokens.Count -or
        $tokens[$CommandIndex + 1].Type -ne [System.Management.Automation.PSTokenType]::GroupStart -or
        $tokens[$CommandIndex + 1].Content -ne '{')
    {
        throw "The $commandName command must be immediately followed by the opening brace of a script block."
    }

    return $CommandIndex + 1
}

& $SafeCommands['Add-Type'] -TypeDefinition @'
    namespace Pester
    {
        using System;
        using System.Management.Automation;

        public static class ClosingBraceFinder
        {
            public static int GetClosingBraceIndex(PSToken[] tokens, int startIndex)
            {
                int groupLevel = 1;
                int len = tokens.Length;

                for (int i = startIndex + 1; i < len; i++)
                {
                    PSTokenType type = tokens[i].Type;
                    if (type == PSTokenType.GroupStart)
                    {
                        groupLevel++;
                    }
                    else if (type == PSTokenType.GroupEnd)
                    {
                        groupLevel--;

                        if (groupLevel <= 0) { return i; }
                    }
                }

                return -1;
            }
        }
    }
'@

function Get-GroupCloseTokenIndex
{
    param (
        [System.Management.Automation.PSToken[]] $Tokens,
        [int] $GroupStartTokenIndex
    )

    $closeIndex = [Pester.ClosingBraceFinder]::GetClosingBraceIndex($Tokens, $GroupStartTokenIndex)

    if ($closeIndex -lt 0)
    {
        throw 'No corresponding GroupEnd token was found.'
    }

    return $closeIndex
}

function Get-ScriptBlockFromTokens
{
    param (
        [System.Management.Automation.PSToken[]] $Tokens,
        [int] $OpenBraceIndex,
        [int] $CloseBraceIndex,
        [string] $CodeText
    )

    $blockStart = $Tokens[$OpenBraceIndex + 1].Start
    $blockLength = $Tokens[$CloseBraceIndex].Start - $blockStart
    $setupOrTeardownCodeText = $codeText.Substring($blockStart, $blockLength)

    $scriptBlock = [scriptblock]::Create($setupOrTeardownCodeText)
    Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $pester.SessionState

    return $scriptBlock
}

function Add-SetupOrTeardownScriptBlock
{
    param (
        [string] $CommandName,
        [scriptblock] $ScriptBlock
    )

    $Pester.AddSetupOrTeardownBlock($ScriptBlock, $CommandName)
}
tools\Functions\TestDrive.ps1
#
function New-TestDrive ([Switch]$PassThru, [string] $Path) {
    if ($Path -notmatch '\S')
    {
        $directory = New-RandomTempDirectory
    }
    else
    {
        if (-not (& $SafeCommands['Test-Path'] -Path $Path))
        {
            & $SafeCommands['New-Item'] -ItemType Container -Path $Path | & $SafeCommands['Out-Null']
        }

        $directory = & $SafeCommands['Get-Item'] $Path
    }

    $DriveName = "TestDrive"

    #setup the test drive
    if ( -not (& $SafeCommands['Test-Path'] "${DriveName}:\") )
    {
        & $SafeCommands['New-PSDrive'] -Name $DriveName -PSProvider FileSystem -Root $directory -Scope Global -Description "Pester test drive" | & $SafeCommands['Out-Null']
    }

    #publish the global TestDrive variable used in few places within the module
    if (-not (& $SafeCommands['Test-Path'] "Variable:Global:$DriveName"))
    {
        & $SafeCommands['New-Variable'] -Name $DriveName -Scope Global -Value $directory
    }

    if ( $PassThru ) { & $SafeCommands['Get-PSDrive'] -Name $DriveName }
}


function Clear-TestDrive ([String[]]$Exclude) {
    $Path = (& $SafeCommands['Get-PSDrive'] -Name TestDrive).Root
    if (& $SafeCommands['Test-Path'] -Path $Path )
    {
        #Get-ChildItem -Exclude did not seem to work with full paths
        & $SafeCommands['Get-ChildItem'] -Recurse -Path $Path |
        & $SafeCommands['Sort-Object'] -Descending  -Property "FullName" |
        & $SafeCommands['Where-Object'] { $Exclude -NotContains $_.FullName } |
        & $SafeCommands['Remove-Item'] -Force -Recurse
    }
}

function New-RandomTempDirectory {
    do
    {
        $tempPath = Get-TempDirectory
        $Path = & $SafeCommands['Join-Path'] -Path $tempPath -ChildPath ([Guid]::NewGuid())
    } until (-not (& $SafeCommands['Test-Path'] -Path $Path ))

    & $SafeCommands['New-Item'] -ItemType Container -Path $Path
}

function Get-TestDriveItem {
<#
    .SYNOPSIS
    The Get-TestDriveItem cmdlet gets the item in Pester test drive.

    .DESCRIPTION
    The Get-TestDriveItem cmdlet gets the item in Pester test drive. It does not
    get the contents of the item at the location unless you use a wildcard
    character (*) to request all the contents of the item.

    The function Get-TestDriveItem is deprecated since Pester v. 4.0
    and will be deleted in the next major version of Pester.

    .PARAMETER Path
    Specifies the path to an item. The path need to be relative to TestDrive:.
    This cmdlet gets the item at the specified location. Wildcards are permitted.
    This parameter is required, but the parameter name ("Path") is optional.

    .EXAMPLE

    Get-TestDriveItem MyTestFolder\MyTestFile.txt

    This command returns the file MyTestFile.txt located in the folder MyTestFolder
    what is located under TestDrive.

    .LINK
    https://github.com/pester/Pester/wiki/TestDrive
    about_TestDrive
#>

    #moved here from Pester.psm1
    param ([string]$Path)

    & $SafeCommands['Write-Warning'] -Message "The function Get-TestDriveItem is deprecated since Pester 4.0.0 and will be removed from Pester 5.0.0."

    Assert-DescribeInProgress -CommandName Get-TestDriveItem
    & $SafeCommands['Get-Item'] $(& $SafeCommands['Join-Path'] $TestDrive $Path )
}

function Get-TestDriveChildItem {
    $Path = (& $SafeCommands['Get-PSDrive'] -Name TestDrive).Root
    if (& $SafeCommands['Test-Path'] -Path $Path )
    {
        & $SafeCommands['Get-ChildItem'] -Recurse -Path $Path
    }
}

function Remove-TestDrive {

    $DriveName = "TestDrive"
    $Drive = & $SafeCommands['Get-PSDrive'] -Name $DriveName -ErrorAction $script:IgnoreErrorPreference
    $Path = ($Drive).Root


    if ($pwd -like "$DriveName*" ) {
        #will staying in the test drive cause issues?
        #TODO review this
        & $SafeCommands['Write-Warning'] -Message "Your current path is set to ${pwd}:. You should leave ${DriveName}:\ before leaving Describe."
    }

    if ( $Drive )
    {
        $Drive | & $SafeCommands['Remove-PSDrive'] -Force -ErrorAction $script:IgnoreErrorPreference
    }

    if (& $SafeCommands['Test-Path'] -Path $Path)
    {
        & $SafeCommands['Remove-Item'] -Path $Path -Force -Recurse
    }

    if (& $SafeCommands['Get-Variable'] -Name $DriveName -Scope Global -ErrorAction $script:IgnoreErrorPreference) {
        & $SafeCommands['Remove-Variable'] -Scope Global -Name $DriveName -Force
    }
}

function Setup {
    <#
        .SYNOPSIS
        This command is included in the Pester Mocking framework for backwards compatibility.  You do not need to call it directly.
    #>
    param(
    [switch]$Dir,
    [switch]$File,
    $Path,
    $Content = "",
    [switch]$PassThru
    )

    Assert-DescribeInProgress -CommandName Setup

    $TestDriveName = & $SafeCommands['Get-PSDrive'] TestDrive |
                     & $SafeCommands['Select-Object'] -ExpandProperty Root

    if ($Dir) {
        $item = & $SafeCommands['New-Item'] -Name $Path -Path "${TestDriveName}\" -Type Container -Force
    }
    if ($File) {
        $item = $Content | & $SafeCommands['New-Item'] -Name $Path -Path "${TestDriveName}\" -Type File -Force
    }

    if($PassThru) {
        return $item
    }
}
tools\Functions\TestResults.ps1
function Get-HumanTime($Seconds) {
    if($Seconds -gt 0.99) {
        $time = [math]::Round($Seconds, 2)
        $unit = 's'
    }
    else {
        $time = [math]::Floor($Seconds * 1000)
        $unit = 'ms'
    }
    return "$time$unit"
}

function GetFullPath ([string]$Path) {
    if (-not [System.IO.Path]::IsPathRooted($Path))
    {
        $Path = & $SafeCommands['Join-Path'] $ExecutionContext.SessionState.Path.CurrentFileSystemLocation $Path
    }

    return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
}

function Export-PesterResults
{
    param (
        $PesterState,
        [string] $Path,
        [string] $Format
    )

    switch ($Format)
    {
        'NUnitXml'       { Export-NUnitReport -PesterState $PesterState -Path $Path }

        default
        {
            throw "'$Format' is not a valid Pester export format."
        }
    }
}
function Export-NUnitReport {
    param (
        [parameter(Mandatory=$true,ValueFromPipeline=$true)]
        $PesterState,

        [parameter(Mandatory=$true)]
        [String]$Path
    )

    #the xmlwriter create method can resolve relatives paths by itself. but its current directory might
    #be different from what PowerShell sees as the current directory so I have to resolve the path beforehand
    #working around the limitations of Resolve-Path

    $Path = GetFullPath -Path $Path

    $settings = & $SafeCommands['New-Object'] -TypeName Xml.XmlWriterSettings -Property @{
        Indent = $true
        NewLineOnAttributes = $false
    }

    $xmlFile = $null
    $xmlWriter = $null
    try {
        $xmlFile = [IO.File]::Create($Path)
        $xmlWriter = [Xml.XmlWriter]::Create($xmlFile, $settings)

        Write-NUnitReport -XmlWriter $xmlWriter -PesterState $PesterState

        $xmlWriter.Flush()
        $xmlFile.Flush()
    }
    finally
    {
        if ($null -ne $xmlWriter) {
            try { $xmlWriter.Close() } catch {}
        }
        if ($null -ne $xmlFile) {
            try { $xmlFile.Close() } catch {}
        }
    }
}

function Write-NUnitReport($PesterState, [System.Xml.XmlWriter] $XmlWriter)
{
    # Write the XML Declaration
    $XmlWriter.WriteStartDocument($false)

    # Write Root Element
    $xmlWriter.WriteStartElement('test-results')

    Write-NUnitTestResultAttributes @PSBoundParameters
    Write-NUnitTestResultChildNodes @PSBoundParameters

    $XmlWriter.WriteEndElement()
}

function Write-NUnitTestResultAttributes($PesterState, [System.Xml.XmlWriter] $XmlWriter)
{
    $XmlWriter.WriteAttributeString('xmlns','xsi', $null, 'http://www.w3.org/2001/XMLSchema-instance')
    $XmlWriter.WriteAttributeString('xsi','noNamespaceSchemaLocation', [Xml.Schema.XmlSchema]::InstanceNamespace , 'nunit_schema_2.5.xsd')
    $XmlWriter.WriteAttributeString('name','Pester')
    $XmlWriter.WriteAttributeString('total', ($PesterState.TotalCount - $PesterState.SkippedCount))
    $XmlWriter.WriteAttributeString('errors', '0')
    $XmlWriter.WriteAttributeString('failures', $PesterState.FailedCount)
    $XmlWriter.WriteAttributeString('not-run', '0')
    $XmlWriter.WriteAttributeString('inconclusive', $PesterState.PendingCount + $PesterState.InconclusiveCount)
    $XmlWriter.WriteAttributeString('ignored', $PesterState.SkippedCount)
    $XmlWriter.WriteAttributeString('skipped', '0')
    $XmlWriter.WriteAttributeString('invalid', '0')
    $date = & $SafeCommands['Get-Date']
    $XmlWriter.WriteAttributeString('date', (& $SafeCommands['Get-Date'] -Date $date -Format 'yyyy-MM-dd'))
    $XmlWriter.WriteAttributeString('time', (& $SafeCommands['Get-Date'] -Date $date -Format 'HH:mm:ss'))
}

function Write-NUnitTestResultChildNodes($PesterState, [System.Xml.XmlWriter] $XmlWriter)
{
    Write-NUnitEnvironmentInformation @PSBoundParameters
    Write-NUnitCultureInformation @PSBoundParameters

    $suiteInfo = Get-TestSuiteInfo -TestSuite $PesterState -TestSuiteName $PesterState.TestSuiteName

    $XmlWriter.WriteStartElement('test-suite')

    Write-NUnitTestSuiteAttributes -TestSuiteInfo $suiteInfo -XmlWriter $XmlWriter

    $XmlWriter.WriteStartElement('results')

    foreach ($action in $PesterState.TestActions.Actions)
    {
        Write-NUnitTestSuiteElements -XmlWriter $XmlWriter -Node $action
    }

    $XmlWriter.WriteEndElement()
    $XmlWriter.WriteEndElement()
}

function Write-NUnitEnvironmentInformation($PesterState, [System.Xml.XmlWriter] $XmlWriter)
{
    $XmlWriter.WriteStartElement('environment')

    $environment = Get-RunTimeEnvironment
    foreach ($keyValuePair in $environment.GetEnumerator()) {
        $XmlWriter.WriteAttributeString($keyValuePair.Name, $keyValuePair.Value)
    }

    $XmlWriter.WriteEndElement()
}

function Write-NUnitCultureInformation($PesterState, [System.Xml.XmlWriter] $XmlWriter)
{
    $XmlWriter.WriteStartElement('culture-info')

    $XmlWriter.WriteAttributeString('current-culture', ([System.Threading.Thread]::CurrentThread.CurrentCulture).Name)
    $XmlWriter.WriteAttributeString('current-uiculture', ([System.Threading.Thread]::CurrentThread.CurrentUiCulture).Name)

    $XmlWriter.WriteEndElement()
}

function Write-NUnitTestSuiteElements($Node, [System.Xml.XmlWriter] $XmlWriter, [string] $Path)
{
    $suiteInfo = Get-TestSuiteInfo $Node

    $XmlWriter.WriteStartElement('test-suite')

    Write-NUnitTestSuiteAttributes -TestSuiteInfo $suiteInfo -XmlWriter $XmlWriter

    $XmlWriter.WriteStartElement('results')

    $separator = if ($Path) { '.' } else { '' }
    $newName = if ($Node.Hint -ne 'Script') { $suiteInfo.Name } else { '' }
    $newPath = "${Path}${separator}${newName}"

    foreach ($action in $Node.Actions)
    {
        if ($action.Type -eq 'TestGroup')
        {
            Write-NUnitTestSuiteElements -Node $action -XmlWriter $XmlWriter -Path $newPath
        }
    }

    $suites = @(
        $Node.Actions |
        & $SafeCommands['Where-Object'] { $_.Type -eq 'TestCase' } |
        & $SafeCommands['Group-Object'] -Property ParameterizedSuiteName
    )

    foreach ($suite in $suites)
    {
        if ($suite.Name)
        {
            $parameterizedSuiteInfo = Get-ParameterizedTestSuiteInfo -TestSuiteGroup $suite

            $XmlWriter.WriteStartElement('test-suite')

            Write-NUnitTestSuiteAttributes -TestSuiteInfo $parameterizedSuiteInfo -TestSuiteType 'ParameterizedTest' -XmlWriter $XmlWriter -Path $newPath

            $XmlWriter.WriteStartElement('results')
        }

        foreach ($testCase in $suite.Group)
        {
            Write-NUnitTestCaseElement -TestResult $testCase -XmlWriter $XmlWriter -Path $newPath -ParameterizedSuiteName $suite.Name
        }

        if ($suite.Name)
        {
            $XmlWriter.WriteEndElement()
            $XmlWriter.WriteEndElement()
        }
    }

    $XmlWriter.WriteEndElement()
    $XmlWriter.WriteEndElement()
}

function Get-ParameterizedTestSuiteInfo ([Microsoft.PowerShell.Commands.GroupInfo] $TestSuiteGroup)
{
    $node = & $SafeCommands['New-Object'] psobject -Property @{
        Name              = $TestSuiteGroup.Name
        TotalCount        = 0
        Time              = [timespan]0
        PassedCount       = 0
        FailedCount       = 0
        SkippedCount      = 0
        PendingCount      = 0
        InconclusiveCount = 0
    }

    foreach ($testCase in $TestSuiteGroup.Group)
    {
        $node.TotalCount++

        switch ($testCase.Result)
        {
            Passed       { $Node.PassedCount++;       break; }
            Failed       { $Node.FailedCount++;       break; }
            Skipped      { $Node.SkippedCount++;      break; }
            Pending      { $Node.PendingCount++;      break; }
            Inconclusive { $Node.InconclusiveCount++; break; }
        }

        $Node.Time += $testCase.Time
    }

    return Get-TestSuiteInfo -TestSuite $node
}

function Get-TestSuiteInfo ($TestSuite, $TestSuiteName)
{
    if (-not $PSBoundParameters.ContainsKey('TestSuiteName')) { $TestSuiteName = $TestSuite.Name }

    $suite = @{
        resultMessage = 'Failure'
        success       = if ($TestSuite.FailedCount -eq 0) { 'True' } else { 'False' }
        totalTime     = Convert-TimeSpan $TestSuite.Time
        name          = $TestSuiteName
        description   = $TestSuiteName
    }

    $suite.resultMessage = Get-GroupResult $TestSuite
    $suite
}

function Get-TestTime($tests) {
    [TimeSpan]$totalTime = 0;
    if ($tests)
    {
        foreach ($test in $tests)
        {
            $totalTime += $test.time
        }
    }

    Convert-TimeSpan -TimeSpan $totalTime
}
function Convert-TimeSpan {
    param (
        [Parameter(ValueFromPipeline=$true)]
        $TimeSpan
    )
    process {
        if ($TimeSpan) {
            [string][math]::round(([TimeSpan]$TimeSpan).totalseconds,4)
        }
        else
        {
            '0'
        }
    }
}
function Get-TestSuccess($tests) {
    $result = $true
    if ($tests)
    {
        foreach ($test in $tests) {
            if (-not $test.Passed) {
                $result = $false
                break
            }
        }
    }
    [String]$result
}
function Write-NUnitTestSuiteAttributes($TestSuiteInfo, [string] $TestSuiteType = 'TestFixture', [System.Xml.XmlWriter] $XmlWriter, [string] $Path)
{
    $name = $TestSuiteInfo.Name

    if ($TestSuiteType -eq 'ParameterizedTest' -and $Path)
    {
        $name = "$Path.$name"
    }

    $XmlWriter.WriteAttributeString('type', $TestSuiteType)
    $XmlWriter.WriteAttributeString('name', $name)
    $XmlWriter.WriteAttributeString('executed', 'True')
    $XmlWriter.WriteAttributeString('result', $TestSuiteInfo.resultMessage)
    $XmlWriter.WriteAttributeString('success', $TestSuiteInfo.success)
    $XmlWriter.WriteAttributeString('time',$TestSuiteInfo.totalTime)
    $XmlWriter.WriteAttributeString('asserts','0')
    $XmlWriter.WriteAttributeString('description', $TestSuiteInfo.Description)
}

function Write-NUnitTestCaseElement($TestResult, [System.Xml.XmlWriter] $XmlWriter, [string] $ParameterizedSuiteName, [string] $Path)
{
    $XmlWriter.WriteStartElement('test-case')

    Write-NUnitTestCaseAttributes -TestResult $TestResult -XmlWriter $XmlWriter -ParameterizedSuiteName $ParameterizedSuiteName -Path $Path

    $XmlWriter.WriteEndElement()
}

function Write-NUnitTestCaseAttributes($TestResult, [System.Xml.XmlWriter] $XmlWriter, [string] $ParameterizedSuiteName, [string] $Path)
{
    $testName = $TestResult.Name

    if ($testName -eq $ParameterizedSuiteName)
    {
        $paramString = ''
        if ($null -ne $TestResult.Parameters)
        {
            $params = @(
                foreach ($value in $TestResult.Parameters.Values)
                {
                    if ($null -eq $value)
                    {
                        'null'
                    }
                    elseif ($value -is [string])
                    {
                        '"{0}"' -f $value
                    }
                    else
                    {
                        #do not use .ToString() it uses the current culture settings
                        #and we need to use en-US culture, which [string] or .ToString([Globalization.CultureInfo]'en-us') uses
                        [string]$value
                    }
                }
            )

            $paramString = $params -join ','
        }

        $testName = "$testName($paramString)"
    }

    $separator = if ($Path) { '.' } else { '' }
    $testName = "${Path}${separator}${testName}"

    $XmlWriter.WriteAttributeString('description', $TestResult.Name)

    $XmlWriter.WriteAttributeString('name', $testName)
    $XmlWriter.WriteAttributeString('time', (Convert-TimeSpan $TestResult.Time))
    $XmlWriter.WriteAttributeString('asserts', '0')
    $XmlWriter.WriteAttributeString('success', $TestResult.Passed)

    switch ($TestResult.Result)
    {
        Passed
        {
            $XmlWriter.WriteAttributeString('result', 'Success')
            $XmlWriter.WriteAttributeString('executed', 'True')
            break
        }
        Skipped
        {
            $XmlWriter.WriteAttributeString('result', 'Ignored')
            $XmlWriter.WriteAttributeString('executed', 'False')
            break
        }

        Pending
        {
            $XmlWriter.WriteAttributeString('result', 'Inconclusive')
            $XmlWriter.WriteAttributeString('executed', 'True')
            break
        }
        Inconclusive
        {
            $XmlWriter.WriteAttributeString('result', 'Inconclusive')
            $XmlWriter.WriteAttributeString('executed', 'True')

            if ($TestResult.FailureMessage)
            {
                $XmlWriter.WriteStartElement('reason')
                $xmlWriter.WriteElementString('message', $TestResult.FailureMessage)
                $XmlWriter.WriteEndElement() # Close reason tag
            }

            break
        }
        Failed
        {
            $XmlWriter.WriteAttributeString('result', 'Failure')
            $XmlWriter.WriteAttributeString('executed', 'True')
            $XmlWriter.WriteStartElement('failure')
            $xmlWriter.WriteElementString('message', $TestResult.FailureMessage)
            $XmlWriter.WriteElementString('stack-trace', $TestResult.StackTrace)
            $XmlWriter.WriteEndElement() # Close failure tag
            break
        }
    }
}
function Get-RunTimeEnvironment() {
    # based on what we found during startup, use the appropriate cmdlet
    if ( $SafeCommands['Get-CimInstance'] -ne $null )
    {
        $osSystemInformation = (& $SafeCommands['Get-CimInstance'] Win32_OperatingSystem)
    }
    elseif ( $SafeCommands['Get-WmiObject'] -ne $null )
    {
        $osSystemInformation = (& $SafeCommands['Get-WmiObject'] Win32_OperatingSystem)
    }
    else
    {
        $osSystemInformation = @{
            Name = "Unknown"
            Version = "0.0.0.0"
            }
    }

    If ( ($PSVersionTable.ContainsKey('PSEdition')) -and ($PSVersionTable.PSEdition -EQ 'Core')) {

        $CLrVersion = "Unknown"

    }
    Else {

        $CLrVersion = [string]$PSVersionTable.ClrVersion

    }

    @{
        'nunit-version' = '2.5.8.0'
        'os-version' = $osSystemInformation.Version
        platform = $osSystemInformation.Name
        cwd = (& $SafeCommands['Get-Location']).Path #run path
        'machine-name' = $env:ComputerName
        user = $env:Username
        'user-domain' = $env:userDomain
        'clr-version' = $CLrVersion
    }
}

function Exit-WithCode ($FailedCount) {
    $host.SetShouldExit($FailedCount)
}

function Get-GroupResult ($InputObject)
{
    #I am not sure about the result precedence, and can't find any good source
    #TODO: Confirm this is the correct order of precedence
    if ($inputObject.FailedCount  -gt 0) { return 'Failure' }
    if ($InputObject.SkippedCount -gt 0) { return 'Ignored' }
    if ($InputObject.PendingCount -gt 0) { return 'Inconclusive' }
    return 'Success'
}
tools\lib\core\Gherkin.dll
md5: D7F8D7CCD83AE1E70E02ED12D3A1ED2E | sha1: 8BF711F2F904CAD27A9604F38F59680C97E4AB44 | sha256: 352A6A598BA8D1E350F497791A9FB9AC64BA11E977FF6CCB7432D8516DDF7685 | sha512: 740BBDD6F10839ED0DF0F373253B3422469C6718EAF40EEE014FCD9659894EE6482FEB0E643D06F7662A065806795D19A5F03019AE038A217729AEAAE0751F2D
tools\lib\legacy\Gherkin.dll
md5: 57D3FEAD935A8F3CCCACB159DAE1AAE6 | sha1: D5747A5E77B2D76BAA87887295CC3A5D62BF7ED3 | sha256: 2BF80E108FC1A112D9AC9E631D29903028EEBABD2A7831ACFCE88CCBEFDF773E | sha512: 6BCD2A6ED4FE3F3E24C88A767D08231A4F83CDF43E01DDF083E23EB00D38D0897680F76A14B9A9E7E83907792B1C8507AF7AE4D43FB8E142BB0B26201BFC0FA3
tools\lib\legacy\Newtonsoft.Json.dll
md5: E346FCECD037F0BE2777231949977587 | sha1: 50E571B3AEA31DB3DF2610A1CA4DFC94612A2CC4 | sha256: EFD8CF9A3BC2AB4E15FA33D42771E18D78539759CBF30652DF4C43E6825CE5F0 | sha512: FFC183626899D1AD1806786BC95C4809AAB3947C78FBFDB38A01D312F2F679DC7DC82F8389074CBCC470D055982CFC370D482FF4D0B3B91532CA409B1FCA32A9
tools\LICENSE
 
tools\nunit_schema_2.5.xsd
 
tools\Pester.psd1
@{

# Script module or binary module file associated with this manifest.
ModuleToProcess = 'Pester.psm1'

# Version number of this module.
ModuleVersion = '4.2.0'

# ID used to uniquely identify this module
GUID = 'a699dea5-2c73-4616-a270-1f7abb777e71'

# Author of this module
Author = 'Pester Team'

# Company or vendor of this module
CompanyName = 'Pester'

# Copyright statement for this module
Copyright = 'Copyright (c) 2017 by Pester Team, licensed under Apache 2.0 License.'

# Description of the functionality provided by this module
Description = 'Pester provides a framework for running BDD style Tests to execute and validate PowerShell commands inside of PowerShell and offers a powerful set of Mocking Functions that allow tests to mimic and mock the functionality of any command inside of a piece of PowerShell code being tested. Pester tests can execute any command or script that is accessible to a pester test file. This can include functions, Cmdlets, Modules and scripts. Pester can be run in ad hoc style in a console or it can be integrated into the Build scripts of a Continuous Integration system.'

# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '2.0'

# Functions to export from this module
FunctionsToExport = @(
    'Describe'
    'Context'
    'It'
    'Should'
    'Mock'
    'Assert-MockCalled'
    'Assert-VerifiableMock'
    'Assert-VerifiableMocks'
    'New-Fixture'
    'Get-TestDriveItem'
    'Invoke-Pester'
    'Setup'
    'In'
    'InModuleScope'
    'Invoke-Mock'
    'BeforeEach'
    'AfterEach'
    'BeforeAll'
    'AfterAll'
    'Get-MockDynamicParameter'
    'Set-DynamicParameterVariable'
    'Set-TestInconclusive'
    'SafeGetCommand'
    'New-PesterOption'
    'New-MockObject'
    'Add-AssertionOperator'

    # Gherkin Support:
    'Invoke-Gherkin'
    'Invoke-GherkinStep'
    'Find-GherkinStep'
    'GherkinStep'
    'BeforeEachFeature'
    'AfterEachFeature'
    'BeforeEachScenario'
    'AfterEachScenario'
)

# # Cmdlets to export from this module
# CmdletsToExport = '*'

# Variables to export from this module
VariablesToExport = @(
    'Path'
    'TagFilter'
    'ExcludeTagFilter'
    'TestNameFilter'
    'TestResult'
    'CurrentContext'
    'CurrentDescribe'
    'CurrentTest'
    'SessionState'
    'CommandCoverage'
    'BeforeEach'
    'AfterEach'
    'Strict'
)

# # Aliases to export from this module
AliasesToExport = @(
    'Given'
    'When'
    'Then'
    'And'
    'But'
)


# List of all modules packaged with this module
# ModuleList = @()

# List of all files packaged with this module
# FileList = @()

PrivateData = @{
    # PSData is module packaging and gallery metadata embedded in PrivateData
    # It's for rebuilding PowerShellGet (and PoshCode) NuGet-style packages
    # We had to do this because it's the only place we're allowed to extend the manifest
    # https://connect.microsoft.com/PowerShell/feedback/details/421837
    PSData = @{
        # The primary categorization of this module (from the TechNet Gallery tech tree).
        Category = "Scripting Techniques"

        # Keyword tags to help users find this module via navigations and search.
        Tags = @('powershell','unit_testing','bdd','tdd','mocking')

        # The web address of an icon which can be used in galleries to represent this module
        IconUri = 'https://raw.githubusercontent.com/pester/Pester/master/doc/pester.PNG'

        # The web address of this module's project or support homepage.
        ProjectUri = "https://github.com/Pester/Pester"

        # The web address of this module's license. Points to a page that's embeddable and linkable.
        LicenseUri = "https://www.apache.org/licenses/LICENSE-2.0.html"

        # Release notes for this particular version of the module
        ReleaseNotes = 'https://github.com/pester/Pester/releases/tag/4.2.0-alpha2'

        # Prerelease string of this module
        Prerelease = 'alpha2'
    }
}

# HelpInfo URI of this module
# HelpInfoURI = ''

# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''

}
tools\Pester.psm1
if ($PSVersionTable.PSVersion.Major -ge 3)
{
    $script:IgnoreErrorPreference = 'Ignore'
    $outNullModule = 'Microsoft.PowerShell.Core'
    $outHostModule = 'Microsoft.PowerShell.Core'
}
else
{
    $script:IgnoreErrorPreference = 'SilentlyContinue'
    $outNullModule = 'Microsoft.PowerShell.Utility'
    $outHostModule = $null
}

# Tried using $ExecutionState.InvokeCommand.GetCmdlet() here, but it does not trigger module auto-loading the way
# Get-Command does.  Since this is at import time, before any mocks have been defined, that's probably acceptable.
# If someone monkeys with Get-Command before they import Pester, they may break something.

# The -All parameter is required when calling Get-Command to ensure that PowerShell can find the command it is
# looking for. Otherwise, if you have modules loaded that define proxy cmdlets or that have cmdlets with the same
# name as the safe cmdlets, Get-Command will return null.
$safeCommandLookupParameters = @{
    CommandType = [System.Management.Automation.CommandTypes]::Cmdlet
    ErrorAction = [System.Management.Automation.ActionPreference]::Stop
}

if ($PSVersionTable.PSVersion.Major -gt 2)
{
    $safeCommandLookupParameters['All'] = $true
}

$script:SafeCommands = @{
    'Add-Member'          = Get-Command -Name Add-Member           -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Add-Type'            = Get-Command -Name Add-Type             -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Compare-Object'      = Get-Command -Name Compare-Object       -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Export-ModuleMember' = Get-Command -Name Export-ModuleMember  -Module Microsoft.PowerShell.Core       @safeCommandLookupParameters
    'ForEach-Object'      = Get-Command -Name ForEach-Object       -Module Microsoft.PowerShell.Core       @safeCommandLookupParameters
    'Format-Table'        = Get-Command -Name Format-Table         -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Get-ChildItem'       = Get-Command -Name Get-ChildItem        -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'Get-Command'         = Get-Command -Name Get-Command          -Module Microsoft.PowerShell.Core       @safeCommandLookupParameters
    'Get-Content'         = Get-Command -Name Get-Content          -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'Get-Date'            = Get-Command -Name Get-Date             -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Get-Item'            = Get-Command -Name Get-Item             -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'Get-ItemProperty'     = Get-Command -Name Get-ItemProperty        -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'Get-Location'        = Get-Command -Name Get-Location         -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'Get-Member'          = Get-Command -Name Get-Member           -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Get-Module'          = Get-Command -Name Get-Module           -Module Microsoft.PowerShell.Core       @safeCommandLookupParameters
    'Get-PSDrive'         = Get-Command -Name Get-PSDrive          -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'Get-PSCallStack'      = Get-Command -Name Get-PSCallStack         -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Get-Unique'           = Get-Command -Name Get-Unique              -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Get-Variable'        = Get-Command -Name Get-Variable         -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Group-Object'        = Get-Command -Name Group-Object         -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Import-LocalizedData' = Get-Command -Name Import-LocalizedData    -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Import-Module'        = Get-Command -Name Import-Module           -Module Microsoft.PowerShell.Core       @safeCommandLookupParameters
    'Join-Path'           = Get-Command -Name Join-Path            -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'Measure-Object'      = Get-Command -Name Measure-Object       -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'New-Item'            = Get-Command -Name New-Item             -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'New-Module'          = Get-Command -Name New-Module           -Module Microsoft.PowerShell.Core       @safeCommandLookupParameters
    'New-Object'          = Get-Command -Name New-Object           -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'New-PSDrive'         = Get-Command -Name New-PSDrive          -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'New-Variable'        = Get-Command -Name New-Variable         -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Out-Host'            = Get-Command -Name Out-Host             -Module $outHostModule                  @safeCommandLookupParameters
    'Out-File'            = Get-Command -Name Out-File             -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Out-Null'            = Get-Command -Name Out-Null             -Module $outNullModule                  @safeCommandLookupParameters
    'Out-String'          = Get-Command -Name Out-String           -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Pop-Location'        = Get-Command -Name Pop-Location         -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'Push-Location'       = Get-Command -Name Push-Location        -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'Remove-Item'         = Get-Command -Name Remove-Item          -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'Remove-PSBreakpoint' = Get-Command -Name Remove-PSBreakpoint  -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Remove-PSDrive'      = Get-Command -Name Remove-PSDrive       -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'Remove-Variable'     = Get-Command -Name Remove-Variable      -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Resolve-Path'        = Get-Command -Name Resolve-Path         -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'Select-Object'       = Get-Command -Name Select-Object        -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Set-Content'         = Get-Command -Name Set-Content          -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'Set-Location'         = Get-Command -Name Set-Location            -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'Set-PSBreakpoint'    = Get-Command -Name Set-PSBreakpoint     -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Set-StrictMode'      = Get-Command -Name Set-StrictMode       -Module Microsoft.PowerShell.Core       @safeCommandLookupParameters
    'Set-Variable'        = Get-Command -Name Set-Variable         -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Sort-Object'         = Get-Command -Name Sort-Object          -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Split-Path'          = Get-Command -Name Split-Path           -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'Start-Sleep'         = Get-Command -Name Start-Sleep          -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Test-Path'           = Get-Command -Name Test-Path            -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    'Where-Object'        = Get-Command -Name Where-Object         -Module Microsoft.PowerShell.Core       @safeCommandLookupParameters
    'Write-Error'         = Get-Command -Name Write-Error          -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Write-Host'          = Get-Command -Name Write-Host           -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Write-Progress'      = Get-Command -Name Write-Progress       -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Write-Verbose'       = Get-Command -Name Write-Verbose        -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    'Write-Warning'       = Get-Command -Name Write-Warning        -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
}

# Not all platforms have Get-WmiObject (Nano or PSCore 6.0.0-beta.x on Linux)
# Get-CimInstance is preferred, but we can use Get-WmiObject if it exists
# Moreover, it shouldn't really be fatal if neither of those cmdlets
# exist
if ( Get-Command -ea SilentlyContinue Get-CimInstance )
{
    $script:SafeCommands['Get-CimInstance'] = Get-Command -Name Get-CimInstance -Module CimCmdlets @safeCommandLookupParameters
}
elseif ( Get-command -ea SilentlyContinue Get-WmiObject )
{
    $script:SafeCommands['Get-WmiObject']   = Get-Command -Name Get-WmiObject   -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
}
else
{
    Write-Warning "OS Information retrieval is not possible, reports will contain only partial system data"
}

# little sanity check to make sure we don't blow up a system with a typo up there
# (not that I've EVER done that by, for example, mapping New-Item to Remove-Item...)

foreach ($keyValuePair in $script:SafeCommands.GetEnumerator())
{
    if ($keyValuePair.Key -ne $keyValuePair.Value.Name)
    {
        throw "SafeCommands entry for $($keyValuePair.Key) does not hold a reference to the proper command."
    }
}

$script:AssertionOperators = & $SafeCommands['New-Object'] 'Collections.Generic.Dictionary[string,object]'([StringComparer]::InvariantCultureIgnoreCase)
$script:AssertionAliases = & $SafeCommands['New-Object'] 'Collections.Generic.Dictionary[string,object]'([StringComparer]::InvariantCultureIgnoreCase)
$script:AssertionDynamicParams = & $SafeCommands['New-Object'] System.Management.Automation.RuntimeDefinedParameterDictionary

function Test-NullOrWhiteSpace {
    param ([string]$String)

    $String -match "^\s*$"
}

function Assert-ValidAssertionName
{
    param([string]$Name)
    if ($Name -notmatch '^\S+$')
    {
        throw "Assertion name '$name' is invalid, assertion name must be a single word."
    }
}

function Assert-ValidAssertionAlias
{
    param([string]$Alias)
    if ($Alias -notmatch '^\S+$')
    {
        throw "Assertion alias '$string' is invalid, assertion alias must be a single word."
    }
}

function Add-AssertionOperator
{
<#
.SYNOPSIS
    Register an Assertion Operator with Pester
.DESCRIPTION
    This function allows you to create custom Should assertions.
.EXAMPLE
    function BeAwesome($ActualValue, [switch] $Negate)
    {

        [bool] $succeeded = $ActualValue -eq 'Awesome'
        if ($Negate) { $succeeded = -not $succeeded }

        if (-not $succeeded)
        {
            if ($Negate)
            {
                $failureMessage = "{$ActualValue} is not Awesome"
            }
            else
            {
                $failureMessage = "{$ActualValue} is not Awesome"
            }
        }

        return New-Object psobject -Property @{
            Succeeded      = $succeeded
            FailureMessage = $failureMessage
        }
    }

    Add-AssertionOperator -Name  BeAwesome `
                        -Test  $function:BeAwesome `
                        -Alias 'BA'

    PS C:\> "bad" | should -BeAwesome
    {bad} is not Awesome
.PARAMETER Name
    The name of the assertion. This will become a Named Parameter of Should.
.PARAMETER Test
    The test function. The function must return a PSObject with a [Bool]succeeded and a [string]failureMessage property.
.PARAMETER Alias
    A list of aliases for the Named Parameter.
.PARAMETER SupportsArrayInput
    Does the test function support the passing an array of values to test.
#>
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $Name,

        [Parameter(Mandatory = $true)]
        [scriptblock] $Test,

        [ValidateNotNullOrEmpty()]
        [AllowEmptyCollection()]
        [string[]] $Alias = @(),

        [switch] $SupportsArrayInput
    )

    $entry = New-Object psobject -Property @{
        Test                       = $Test
        SupportsArrayInput         = [bool]$SupportsArrayInput
        Name                       = $Name
        Alias                      = $Alias
    }
    if (Test-AssertionOperatorIsDuplicate -Operator $entry)
    {
        # This is an exact duplicate of an existing assertion operator.
        return
    }

    $namesToCheck = @(
        $Name
        $Alias
    )

    Assert-AssertionOperatorNameIsUnique -Name $namesToCheck

    $script:AssertionOperators[$Name] = $entry

    foreach ($string in $Alias | Where { -not (Test-NullOrWhiteSpace $_)})
    {
        Assert-ValidAssertionAlias -Alias $string
        $script:AssertionAliases[$string] = $Name
    }

    Add-AssertionDynamicParameterSet -AssertionEntry $entry
}
function Test-AssertionOperatorIsDuplicate
{
    param (
        [psobject] $Operator
    )

    $existing = $script:AssertionOperators[$Operator.Name]
    if (-not $existing) { return $false }

    return $Operator.SupportsArrayInput -eq $existing.SupportsArrayInput -and
           $Operator.Test.ToString() -eq $existing.Test.ToString() -and
           -not (Compare-Object $Operator.Alias $existing.Alias)
}
function Assert-AssertionOperatorNameIsUnique
{
    param (
        [string[]] $Name
    )

    foreach ($string in $name | Where { -not (Test-NullOrWhiteSpace $_)})
    {
        Assert-ValidAssertionName -Name $string

        if ($script:AssertionOperators.ContainsKey($string))
        {
            throw "Assertion operator name '$string' has been added multiple times."
        }

        if ($script:AssertionAliases.ContainsKey($string))
        {
            throw "Assertion operator name '$string' already exists as an alias for operator '$($script:AssertionAliases[$key])'"
        }
    }
}

function Add-AssertionDynamicParameterSet
{
    param (
        [object] $AssertionEntry
    )

    ${function:__AssertionTest__} = $AssertionEntry.Test
    $commandInfo = Get-Command __AssertionTest__ -CommandType Function
    $metadata = [System.Management.Automation.CommandMetadata]$commandInfo

    $attribute = New-Object Management.Automation.ParameterAttribute
    $attribute.ParameterSetName = $AssertionEntry.Name
    $attribute.Mandatory = $true

    $attributeCollection = New-Object Collections.ObjectModel.Collection[Attribute]
    $null = $attributeCollection.Add($attribute)
    if (-not (Test-NullOrWhiteSpace $AssertionEntry.Alias))
    {
        Assert-ValidAssertionAlias -Alias $AssertionEntry.Alias
        $attribute = New-Object System.Management.Automation.AliasAttribute($AssertionEntry.Alias)
        $attributeCollection.Add($attribute)
    }

    $dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter($AssertionEntry.Name, [switch], $attributeCollection)
    $null = $script:AssertionDynamicParams.Add($AssertionEntry.Name, $dynamic)

    if ($script:AssertionDynamicParams.ContainsKey('Not'))
    {
        $dynamic = $script:AssertionDynamicParams['Not']
    }
    else
    {
        $dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Not', [switch], (New-Object System.Collections.ObjectModel.Collection[Attribute]))
        $null = $script:AssertionDynamicParams.Add('Not', $dynamic)
    }

    $attribute = New-Object System.Management.Automation.ParameterAttribute
    $attribute.ParameterSetName = $AssertionEntry.Name
    $attribute.Mandatory = $false
    $null = $dynamic.Attributes.Add($attribute)

    $i = 1
    foreach ($parameter in $metadata.Parameters.Values)
    {
        if ($parameter.Name -eq 'ActualValue' -or $parameter.Name -eq 'Not' -or $parameter.Name -eq 'Negate') { continue }

        if ($script:AssertionOperators.ContainsKey($parameter.Name) -or $script:AssertionAliases.ContainsKey($parameter.Name))
        {
            throw "Test block for assertion operator $($AssertionEntry.Name) contains a parameter named $($parameter.Name), which conflicts with another assertion operator's name or alias."
        }

        foreach ($alias in $parameter.Aliases)
        {
            if ($script:AssertionOperators.ContainsKey($alias) -or $script:AssertionAliases.ContainsKey($alias))
            {
                throw "Test block for assertion operator $($AssertionEntry.Name) contains a parameter named $($parameter.Name) with alias $alias, which conflicts with another assertion operator's name or alias."
            }
        }

        if ($script:AssertionDynamicParams.ContainsKey($parameter.Name))
        {
            $dynamic = $script:AssertionDynamicParams[$parameter.Name]
        }
        else
           {
            # We deliberately use a type of [object] here to avoid conflicts between different assertion operators that may use the same parameter name.
            # We also don't bother to try to copy transformation / validation attributes here for the same reason.
            # Because we'll be passing these parameters on to the actual test function later, any errors will come out at that time.

            $dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter($parameter.Name, [object], (New-Object System.Collections.ObjectModel.Collection[Attribute]))
            $null = $script:AssertionDynamicParams.Add($parameter.Name, $dynamic)
        }

        $attribute = New-Object Management.Automation.ParameterAttribute
        $attribute.ParameterSetName = $AssertionEntry.Name
        $attribute.Mandatory = $false
        $attribute.Position = ($i++)

        $null = $dynamic.Attributes.Add($attribute)
    }
}

function Get-AssertionOperatorEntry([string] $Name)
{
    return $script:AssertionOperators[$Name]
}

function Get-AssertionDynamicParams
{
    return $script:AssertionDynamicParams
}

$Script:PesterRoot = & $SafeCommands['Split-Path'] -Path $MyInvocation.MyCommand.Path
"$PesterRoot\Functions\*.ps1", "$PesterRoot\Functions\Assertions\*.ps1" |
& $script:SafeCommands['Resolve-Path'] |
& $script:SafeCommands['Where-Object'] { -not ($_.ProviderPath.ToLower().Contains(".tests.")) } |
& $script:SafeCommands['ForEach-Object'] { . $_.ProviderPath }

if (& $script:SafeCommands['Test-Path'] "$PesterRoot\Dependencies") {
    # sub-modules
    & $script:SafeCommands['Get-ChildItem'] "$PesterRoot\Dependencies\*\*.psm1" |
    & $script:SafeCommands['ForEach-Object'] { & $script:SafeCommands['Import-Module'] $_.FullName -Force -DisableNameChecking }
}

Add-Type -TypeDefinition @"
using System;

namespace Pester
{
    [Flags]
    public enum OutputTypes
    {
        None = 0,
        Default = 1,
        Passed = 2,
        Failed = 4,
        Pending = 8,
        Skipped = 16,
        Inconclusive = 32,
        Describe = 64,
        Context = 128,
        Summary = 256,
        Header = 512,
        All = Default | Passed | Failed | Pending | Skipped | Inconclusive | Describe | Context | Summary | Header,
        Fails = Default | Failed | Pending | Skipped | Inconclusive | Describe | Context | Summary | Header
    }
}
"@

function Has-Flag {
     param
     (
         [Parameter(Mandatory = $true)]
         [Pester.OutputTypes]
         $Setting,
         [Parameter(Mandatory = $true, ValueFromPipeline=$true)]
         [Pester.OutputTypes]
         $Value
     )

  0 -ne ($Setting -band $Value)
}

function Invoke-Pester {
<#
.SYNOPSIS
Runs Pester tests

.DESCRIPTION
The Invoke-Pester function runs Pester tests, including *.Tests.ps1 files and
Pester tests in PowerShell scripts.

You can run scripts that include Pester tests just as you would any other
Windows PowerShell script, including typing the full path at the command line
and running in a script editing program. Typically, you use Invoke-Pester to run
all Pester tests in a directory, or to use its many helpful parameters,
including parameters that generate custom objects or XML files.

By default, Invoke-Pester runs all *.Tests.ps1 files in the current directory
and all subdirectories recursively. You can use its parameters to select tests
by file name, test name, or tag.

To run Pester tests in scripts that take parameter values, use the Script
parameter with a hash table value.

Also, by default, Pester tests write test results to the console host, much like
Write-Host does, but you can use the Quiet parameter to suppress the host
messages, use the PassThru parameter to generate a custom object
(PSCustomObject) that contains the test results, use the OutputXml and
OutputFormat parameters to write the test results to an XML file, and use the
EnableExit parameter to return an exit code that contains the number of failed
tests.

You can also use the Strict parameter to fail all pending and skipped tests.
This feature is ideal for build systems and other processes that require success
on every test.

To help with test design, Invoke-Pester includes a CodeCoverage parameter that
lists commands, functions, and lines of code that did not run during test
execution and returns the code that ran as a percentage of all tested code.

Invoke-Pester, and the Pester module that exports it, are products of an
open-source project hosted on GitHub. To view, comment, or contribute to the
repository, see https://github.com/Pester.

.PARAMETER Script
Specifies the test files that Pester runs. You can also use the Script parameter
to pass parameter names and values to a script that contains Pester tests. The
value of the Script parameter can be a string, a hash table, or a collection
of hash tables and strings. Wildcard characters are supported.

The Script parameter is optional. If you omit it, Invoke-Pester runs all
*.Tests.ps1 files in the local directory and its subdirectories recursively.

To run tests in other files, such as .ps1 files, enter the path and file name of
the file. (The file name is required. Name patterns that end in "*.ps1" run only
*.Tests.ps1 files.)

To run a Pester test with parameter names and/or values, use a hash table as the
value of the script parameter. The keys in the hash table are:

-- Path [string] (required): Specifies a test to run. The value is a path\file
   name or name pattern. Wildcards are permitted. All hash tables in a Script
   parameter value must have a Path key.

-- Parameters [hashtable]: Runs the script with the specified parameters. The
   value is a nested hash table with parameter name and value pairs, such as
   @{UserName = 'User01'; Id = '28'}.

-- Arguments [array]: An array or comma-separated list of parameter values
   without names, such as 'User01', 28. Use this key to pass values to positional
   parameters.


.PARAMETER TestName
Runs only tests in Describe blocks that have the specified name or name pattern.
Wildcard characters are supported.

If you specify multiple TestName values, Invoke-Pester runs tests that have any
of the values in the Describe name (it ORs the TestName values).

.PARAMETER EnableExit
Will cause Invoke-Pester to exit with a exit code equal to the number of failed
tests once all tests have been run. Use this to "fail" a build when any tests fail.

.PARAMETER OutputFile
The path where Invoke-Pester will save formatted test results log file.

The path must include the location and name of the folder and file name with
the xml extension.

If this path is not provided, no log will be generated.

.PARAMETER OutputFormat
The format of output. Two formats of output are supported: NUnitXML and
LegacyNUnitXML.

.PARAMETER Tag
Runs only tests in Describe blocks with the specified Tag parameter values.
Wildcard characters and Tag values that include spaces or whitespace characters
are not supported.

When you specify multiple Tag values, Invoke-Pester runs tests that have any
of the listed tags (it ORs the tags). However, when you specify TestName
and Tag values, Invoke-Pester runs only describe blocks that have one of the
specified TestName values and one of the specified Tag values.

If you use both Tag and ExcludeTag, ExcludeTag takes precedence.

.PARAMETER ExcludeTag
Omits tests in Describe blocks with the specified Tag parameter values. Wildcard
characters and Tag values that include spaces or whitespace characters are not
supported.

When you specify multiple ExcludeTag values, Invoke-Pester omits tests that have
any of the listed tags (it ORs the tags). However, when you specify TestName
and ExcludeTag values, Invoke-Pester omits only describe blocks that have one
of the specified TestName values and one of the specified Tag values.

If you use both Tag and ExcludeTag, ExcludeTag takes precedence

.PARAMETER PassThru
Returns a custom object (PSCustomObject) that contains the test results.

By default, Invoke-Pester writes to the host program, not to the output stream (stdout).
If you try to save the result in a variable, the variable is empty unless you
use the PassThru parameter.

To suppress the host output, use the Quiet parameter.

.PARAMETER CodeCoverage
Adds a code coverage report to the Pester tests. Takes strings or hash table values.

A code coverage report lists the lines of code that did and did not run during
a Pester test. This report does not tell whether code was tested; only whether
the code ran during the test.

By default, the code coverage report is written to the host program
(like Write-Host). When you use the PassThru parameter, the custom object
that Invoke-Pester returns has an additional CodeCoverage property that contains
a custom object with detailed results of the code coverage test, including lines
hit, lines missed, and helpful statistics.

However, NUnitXML and LegacyNUnitXML output (OutputXML, OutputFormat) do not include
any code coverage information, because it's not supported by the schema.

Enter the path to the files of code under test (not the test file).
Wildcard characters are supported. If you omit the path, the default is local
directory, not the directory specified by the Script parameter.

To run a code coverage test only on selected functions or lines in a script,
enter a hash table value with the following keys:

-- Path (P)(mandatory) <string>. Enter one path to the files. Wildcard characters
   are supported, but only one string is permitted.

One of the following: Function or StartLine/EndLine

-- Function (F) <string>: Enter the function name. Wildcard characters are
   supported, but only one string is permitted.

-or-

-- StartLine (S): Performs code coverage analysis beginning with the specified
   line. Default is line 1.
-- EndLine (E): Performs code coverage analysis ending with the specified line.
   Default is the last line of the script.

.PARAMETER CodeCoverageOutputFile
The path where Invoke-Pester will save formatted code coverage results file.

The path must include the location and name of the folder and file name with
a required extension (usually the xml).

If this path is not provided, no file will be generated.

.PARAMETER CodeCoverageOutputFileFormat
The name of a code coverage report file format.

Default value is: JaCoCo.

Currently supported formats are:
- JaCoCo - this XML file format is compatible with the VSTS/TFS

.PARAMETER Strict
Makes Pending and Skipped tests to Failed tests. Useful for continuous
integration where you need to make sure all tests passed.

.PARAMETER Quiet
The parameter Quiet is deprecated since Pester v. 4.0 and will be deleted
in the next major version of Pester. Please use the parameter Show
with value 'None' instead.

The parameter Quiet suppresses the output that Pester writes to the host program,
including the result summary and CodeCoverage output.

This parameter does not affect the PassThru custom object or the XML output that
is written when you use the Output parameters.

.PARAMETER Show
Customizes the output Pester writes to the screen. Available options are None, Default,
Passed, Failed, Pending, Skipped, Inconclusive, Describe, Context, Summary, Header, All, Fails.

The options can be combined to define presets.
Common use cases are:

None - to write no output to the screen.
All - to write all available information (this is default option).
Fails - to write everything except Passed (but including Describes etc.).

A common setting is also Failed, Summary, to write only failed tests and test summary.

This parameter does not affect the PassThru custom object or the XML output that
is written when you use the Output parameters.

.PARAMETER PesterOption
Sets advanced options for the test execution. Enter a PesterOption object,
such as one that you create by using the New-PesterOption cmdlet, or a hash table
in which the keys are option names and the values are option values.
For more information on the options available, see the help for New-PesterOption.

.Example
Invoke-Pester

This command runs all *.Tests.ps1 files in the current directory and its subdirectories.

.Example
Invoke-Pester -Script .\Util*

This commands runs all *.Tests.ps1 files in subdirectories with names that begin
with 'Util' and their subdirectories.

.Example
Invoke-Pester -Script D:\MyModule, @{ Path = '.\Tests\Utility\ModuleUnit.Tests.ps1'; Parameters = @{ Name = 'User01' }; Arguments = srvNano16  }

This command runs all *.Tests.ps1 files in D:\MyModule and its subdirectories.
It also runs the tests in the ModuleUnit.Tests.ps1 file using the following
parameters: .\Tests\Utility\ModuleUnit.Tests.ps1 srvNano16 -Name User01

.Example
Invoke-Pester -TestName "Add Numbers"

This command runs only the tests in the Describe block named "Add Numbers".

.EXAMPLE
$results = Invoke-Pester -Script D:\MyModule -PassThru -Show None
$failed = $results.TestResult | where Result -eq 'Failed'

$failed.Name
cannot find help for parameter: Force : in Compress-Archive
help for Force parameter in Compress-Archive has wrong Mandatory value
help for Compress-Archive has wrong parameter type for Force
help for Update parameter in Compress-Archive has wrong Mandatory value
help for DestinationPath parameter in Expand-Archive has wrong Mandatory value

$failed[0]
Describe               : Test help for Compress-Archive in Microsoft.PowerShell.Archive (1.0.0.0)
Context                : Test parameter help for Compress-Archive
Name                   : cannot find help for parameter: Force : in Compress-Archive
Result                 : Failed
Passed                 : False
Time                   : 00:00:00.0193083
FailureMessage         : Expected: value to not be empty
StackTrace             : at line: 279 in C:\GitHub\PesterTdd\Module.Help.Tests.ps1
                         279:                     $parameterHelp.Description.Text | Should Not BeNullOrEmpty
ErrorRecord            : Expected: value to not be empty
ParameterizedSuiteName :
Parameters             : {}

This examples uses the PassThru parameter to return a custom object with the
Pester test results. By default, Invoke-Pester writes to the host program, but not
to the output stream. It also uses the Quiet parameter to suppress the host output.

The first command runs Invoke-Pester with the PassThru and Quiet parameters and
saves the PassThru output in the $results variable.

The second command gets only failing results and saves them in the $failed variable.

The third command gets the names of the failing results. The result name is the
name of the It block that contains the test.

The fourth command uses an array index to get the first failing result. The
property values describe the test, the expected result, the actual result, and
useful values, including a stack trace.

.Example
Invoke-Pester -EnableExit -OutputFile ".\artifacts\TestResults.xml" -OutputFormat NUnitXml

This command runs all tests in the current directory and its subdirectories. It
writes the results to the TestResults.xml file using the NUnitXml schema. The
test returns an exit code equal to the number of test failures.

 .EXAMPLE
Invoke-Pester -CodeCoverage 'ScriptUnderTest.ps1'

Runs all *.Tests.ps1 scripts in the current directory, and generates a coverage
report for all commands in the "ScriptUnderTest.ps1" file.

.EXAMPLE
Invoke-Pester -CodeCoverage @{ Path = 'ScriptUnderTest.ps1'; Function = 'FunctionUnderTest' }

Runs all *.Tests.ps1 scripts in the current directory, and generates a coverage
report for all commands in the "FunctionUnderTest" function in the "ScriptUnderTest.ps1" file.

 .EXAMPLE
Invoke-Pester -CodeCoverage 'ScriptUnderTest.ps1' -CodeCoverageOutputFile '.\artifacts\TestOutput.xml'

Runs all *.Tests.ps1 scripts in the current directory, and generates a coverage
report for all commands in the "ScriptUnderTest.ps1" file, and writes the coverage report to TestOutput.xml
file using the JaCoCo XML Report DTD.

.EXAMPLE
Invoke-Pester -CodeCoverage @{ Path = 'ScriptUnderTest.ps1'; StartLine = 10; EndLine = 20 }

Runs all *.Tests.ps1 scripts in the current directory, and generates a coverage
report for all commands on lines 10 through 20 in the "ScriptUnderTest.ps1" file.

.EXAMPLE
Invoke-Pester -Script C:\Tests -Tag UnitTest, Newest -ExcludeTag Bug

This command runs *.Tests.ps1 files in C:\Tests and its subdirectories. In those
files, it runs only tests that have UnitTest or Newest tags, unless the test
also has a Bug tag.

.LINK
https://github.com/pester/Pester/wiki/Invoke-Pester
Describe
about_Pester
New-PesterOption

#>
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param(
        [Parameter(Position=0,Mandatory=0)]
        [Alias('Path', 'relative_path')]
        [object[]]$Script = '.',

        [Parameter(Position=1,Mandatory=0)]
        [Alias("Name")]
        [string[]]$TestName,

        [Parameter(Position=2,Mandatory=0)]
        [switch]$EnableExit,

        [Parameter(Position=4,Mandatory=0)]
        [Alias('Tags')]
        [string[]]$Tag,

        [string[]]$ExcludeTag,

        [switch]$PassThru,

        [object[]] $CodeCoverage = @(),

        [string] $CodeCoverageOutputFile,

        [ValidateSet('JaCoCo')]
        [String]$CodeCoverageOutputFileFormat = "JaCoCo",

        [Switch]$Strict,

        [Parameter(Mandatory = $true, ParameterSetName = 'NewOutputSet')]
        [string] $OutputFile,

        [Parameter(ParameterSetName = 'NewOutputSet')]
        [ValidateSet('NUnitXml')]
        [string] $OutputFormat = 'NUnitXml',

        [Switch]$Quiet,

        [object]$PesterOption,

        [Pester.OutputTypes]$Show = 'All'
    )
    begin {
        # Ensure when running Pester that we're using RSpec strings
        & $script:SafeCommands['Import-LocalizedData'] -BindingVariable Script:ReportStrings -BaseDirectory $PesterRoot -FileName RSpec.psd1 -ErrorAction SilentlyContinue

        # Fallback to en-US culture strings
        If ([String]::IsNullOrEmpty($ReportStrings)) {

            & $script:SafeCommands['Import-LocalizedData'] -BaseDirectory $PesterRoot -BindingVariable Script:ReportStrings -UICulture 'en-US' -FileName RSpec.psd1 -ErrorAction Stop

        }
    }

    end {
        if ($PSBoundParameters.ContainsKey('Quiet'))
        {
            & $script:SafeCommands['Write-Warning'] 'The -Quiet parameter has been deprecated; please use the new -Show parameter instead. To get no output use -Show None.'
            & $script:SafeCommands['Start-Sleep'] -Seconds 2

            if (!$PSBoundParameters.ContainsKey('Show'))
            {
                $Show = [Pester.OutputTypes]::None
            }
        }

        $script:mockTable = @{}
        $pester = New-PesterState -TestNameFilter $TestName -TagFilter ($Tag -split "\s") -ExcludeTagFilter ($ExcludeTag -split "\s") -SessionState $PSCmdlet.SessionState -Strict:$Strict -Show:$Show -PesterOption $PesterOption

        try
        {
            Enter-CoverageAnalysis -CodeCoverage $CodeCoverage -PesterState $pester
            Write-PesterStart $pester $Script

            $invokeTestScript = {
                param (
                    [Parameter(Position = 0)]
                    [string] $Path,

                    [object[]] $Arguments = @(),
                    [System.Collections.IDictionary] $Parameters = @{}
                )

                & $Path @Parameters @Arguments
            }

            Set-ScriptBlockScope -ScriptBlock $invokeTestScript -SessionState $PSCmdlet.SessionState

            $testScripts = @(ResolveTestScripts $Script)

            foreach ($testScript in $testScripts)
            {
                try
                {
                    $pester.EnterTestGroup($testScript.Path, 'Script')
                    Write-Describe $testScript.Path -CommandUsed Script
                    do
                    {
                        & $invokeTestScript -Path $testScript.Path -Arguments $testScript.Arguments -Parameters $testScript.Parameters
                    } until ($true)
                }
                catch
                {
                    $firstStackTraceLine = $_.ScriptStackTrace -split '\r?\n' | & $script:SafeCommands['Select-Object'] -First 1
                    $pester.AddTestResult("Error occurred in test script '$($testScript.Path)'", "Failed", $null, $_.Exception.Message, $firstStackTraceLine, $null, $null, $_)

                    # This is a hack to ensure that XML output is valid for now.  The test-suite names come from the Describe attribute of the TestResult
                    # objects, and a blank name is invalid NUnit XML.  This will go away when we promote test scripts to have their own test-suite nodes,
                    # planned for v4.0
                    $pester.TestResult[-1].Describe = "Error in $($testScript.Path)"

                    $pester.TestResult[-1] | Write-PesterResult
                }
                finally
                {
                    Exit-MockScope
                    $pester.LeaveTestGroup($testScript.Path, 'Script')
                }
            }

            $pester | Write-PesterReport
            $coverageReport = Get-CoverageReport -PesterState $pester
            Write-CoverageReport -CoverageReport $coverageReport
            if ((& $script:SafeCommands['Get-Variable'] -Name CodeCoverageOutputFile -ValueOnly -ErrorAction $script:IgnoreErrorPreference) `
                -and (& $script:SafeCommands['Get-Variable'] -Name CodeCoverageOutputFileFormat -ValueOnly -ErrorAction $script:IgnoreErrorPreference) -eq 'JaCoCo') {
                $jaCoCoReport = Get-JaCoCoReportXml -PesterState $pester -CoverageReport $coverageReport
                $jaCoCoReport | & $SafeCommands['Out-File'] $CodeCoverageOutputFile -Encoding utf8
            }
            Exit-CoverageAnalysis -PesterState $pester
        }
        finally
        {
            Exit-MockScope
        }

        Set-PesterStatistics

        if (& $script:SafeCommands['Get-Variable'] -Name OutputFile -ValueOnly -ErrorAction $script:IgnoreErrorPreference) {
            Export-PesterResults -PesterState $pester -Path $OutputFile -Format $OutputFormat
        }

    if ($PassThru) {
        # Remove all runtime properties like current* and Scope
        $properties = @(
            "TagFilter","ExcludeTagFilter","TestNameFilter","TotalCount","PassedCount","FailedCount","SkippedCount","PendingCount",'InconclusiveCount',"Time","TestResult"

                if ($CodeCoverage)
                {
                    @{ Name = 'CodeCoverage'; Expression = { $coverageReport } }
                }
            )

            $pester | & $script:SafeCommands['Select-Object'] -Property $properties
        }

        if ($EnableExit) { Exit-WithCode -FailedCount $pester.FailedCount }
    }
}

function New-PesterOption
{
<#
.SYNOPSIS
Creates an object that contains advanced options for Invoke-Pester
.DESCRIPTION
By using New-PesterOption you can set options what allow easier integration with external applications or
modifies output generated by Invoke-Pester.
The result of New-PesterOption need to be assigned to the parameter 'PesterOption' of the Invoke-Pester function.
.PARAMETER IncludeVSCodeMarker
When this switch is set, an extra line of output will be written to the console for test failures, making it easier
for VSCode's parser to provide highlighting / tooltips on the line where the error occurred.
.PARAMETER TestSuiteName
When generating NUnit XML output, this controls the name assigned to the root "test-suite" element.  Defaults to "Pester".
.INPUTS
None
You cannot pipe input to this command.
.OUTPUTS
System.Management.Automation.PSObject
.EXAMPLE
    PS > $Options = New-PesterOption -TestSuiteName "Tests - Set A"

    PS > Invoke-Pester -PesterOption $Options -Outputfile ".\Results-Set-A.xml" -OutputFormat NUnitXML

    The result of commands will be execution of tests and saving results of them in a NUnitMXL file where the root "test-suite"
    will be named "Tests - Set A".
.LINK
Invoke-Pester
#>

    [CmdletBinding()]
    param (
        [switch] $IncludeVSCodeMarker,

        [ValidateNotNullOrEmpty()]
        [string] $TestSuiteName = 'Pester'
    )

    return & $script:SafeCommands['New-Object'] psobject -Property @{
        IncludeVSCodeMarker = [bool]$IncludeVSCodeMarker
        TestSuiteName       = $TestSuiteName
    }
}

function ResolveTestScripts
{
    param ([object[]] $Path)

    $resolvedScriptInfo = @(
        foreach ($object in $Path)
        {
            if ($object -is [System.Collections.IDictionary])
            {
                $unresolvedPath = Get-DictionaryValueFromFirstKeyFound -Dictionary $object -Key 'Path', 'p'
                $arguments      = @(Get-DictionaryValueFromFirstKeyFound -Dictionary $object -Key 'Arguments', 'args', 'a')
                $parameters     = Get-DictionaryValueFromFirstKeyFound -Dictionary $object -Key 'Parameters', 'params'

                if ($null -eq $Parameters) { $Parameters = @{} }

                if ($unresolvedPath -isnot [string] -or $unresolvedPath -notmatch '\S')
                {
                    throw 'When passing hashtables to the -Path parameter, the Path key is mandatory, and must contain a single string.'
                }

                if ($null -ne $parameters -and $parameters -isnot [System.Collections.IDictionary])
                {
                    throw 'When passing hashtables to the -Path parameter, the Parameters key (if present) must be assigned an IDictionary object.'
                }
            }
            else
            {
                $unresolvedPath = [string] $object
                $arguments      = @()
                $parameters     = @{}
            }

            if ($unresolvedPath -notmatch '[\*\?\[\]]' -and
                (& $script:SafeCommands['Test-Path'] -LiteralPath $unresolvedPath -PathType Leaf) -and
                (& $script:SafeCommands['Get-Item'] -LiteralPath $unresolvedPath) -is [System.IO.FileInfo])
            {
                $extension = [System.IO.Path]::GetExtension($unresolvedPath)
                if ($extension -ne '.ps1')
                {
                    & $script:SafeCommands['Write-Error'] "Script path '$unresolvedPath' is not a ps1 file."
                }
                else
                {
                    & $script:SafeCommands['New-Object'] psobject -Property @{
                        Path       = $unresolvedPath
                        Arguments  = $arguments
                        Parameters = $parameters
                    }
                }
            }
            else
            {
                # World's longest pipeline?

                & $script:SafeCommands['Resolve-Path'] -Path $unresolvedPath |
                & $script:SafeCommands['Where-Object'] { $_.Provider.Name -eq 'FileSystem' } |
                & $script:SafeCommands['Select-Object'] -ExpandProperty ProviderPath |
                & $script:SafeCommands['Get-ChildItem'] -Include *.Tests.ps1 -Recurse |
                & $script:SafeCommands['Where-Object'] { -not $_.PSIsContainer } |
                & $script:SafeCommands['Select-Object'] -ExpandProperty FullName -Unique |
                & $script:SafeCommands['ForEach-Object'] {
                    & $script:SafeCommands['New-Object'] psobject -Property @{
                        Path       = $_
                        Arguments  = $arguments
                        Parameters = $parameters
                    }
                }
            }
        }
    )

    # Here, we have the option of trying to weed out duplicate file paths that also contain identical
    # Parameters / Arguments.  However, we already make sure that each object in $Path didn't produce
    # any duplicate file paths, and if the caller happens to pass in a set of parameters that produce
    # dupes, maybe that's not our problem.  For now, just return what we found.

    $resolvedScriptInfo
}

function Get-DictionaryValueFromFirstKeyFound
{
    param ([System.Collections.IDictionary] $Dictionary, [object[]] $Key)

    foreach ($keyToTry in $Key)
    {
        if ($Dictionary.Contains($keyToTry)) { return $Dictionary[$keyToTry] }
    }
}

function Set-ScriptBlockScope
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter(Mandatory = $true, ParameterSetName = 'FromSessionState')]
        [System.Management.Automation.SessionState]
        $SessionState,

        [Parameter(Mandatory = $true, ParameterSetName = 'FromSessionStateInternal')]
        $SessionStateInternal
    )

    $flags = [System.Reflection.BindingFlags]'Instance,NonPublic'

    if ($PSCmdlet.ParameterSetName -eq 'FromSessionState')
    {
        $SessionStateInternal = $SessionState.GetType().GetProperty('Internal', $flags).GetValue($SessionState, $null)
    }

    [scriptblock].GetProperty('SessionStateInternal', $flags).SetValue($ScriptBlock, $SessionStateInternal, $null)
}

function Get-ScriptBlockScope
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock
    )

    $flags = [System.Reflection.BindingFlags]'Instance,NonPublic'
    [scriptblock].GetProperty('SessionStateInternal', $flags).GetValue($ScriptBlock, $null)
}


function SafeGetCommand
{
    <#
        .SYNOPSIS
        This command is used by Pester's Mocking framework.  You do not need to call it directly.
    #>

    return $script:SafeCommands['Get-Command']
}

function Set-PesterStatistics($Node)
{
    if ($null -eq $Node) { $Node = $pester.TestActions }

    foreach ($action in $Node.Actions)
    {
        if ($action.Type -eq 'TestGroup')
        {
            Set-PesterStatistics -Node $action

            $Node.TotalCount        += $action.TotalCount
            $Node.Time              += $action.Time
            $Node.PassedCount       += $action.PassedCount
            $Node.FailedCount       += $action.FailedCount
            $Node.SkippedCount      += $action.SkippedCount
            $Node.PendingCount      += $action.PendingCount
            $Node.InconclusiveCount += $action.InconclusiveCount
        }
        elseif ($action.Type -eq 'TestCase')
        {
            $node.TotalCount++

            switch ($action.Result)
            {
                Passed       { $Node.PassedCount++;       break; }
                Failed       { $Node.FailedCount++;       break; }
                Skipped      { $Node.SkippedCount++;      break; }
                Pending      { $Node.PendingCount++;      break; }
                Inconclusive { $Node.InconclusiveCount++; break; }
            }

            $Node.Time += $action.Time
        }
    }
}

$snippetsDirectoryPath = "$PSScriptRoot\Snippets"
if ((& $script:SafeCommands['Test-Path'] -Path Variable:\psise) -and
    ($null -ne $psISE) -and
    ($PSVersionTable.PSVersion.Major -ge 3) -and
    (& $script:SafeCommands['Test-Path'] $snippetsDirectoryPath))
{
    Import-IseSnippet -Path $snippetsDirectoryPath
}

function Assert-VerifiableMocks {

<#
.SYNOPSIS
The function is for backward compatibility only. Please update your code and use 'Assert-VerifiableMock' instead.

.DESCRIPTION
The function was reintroduced in the version 4.0.8 of Pester to avoid loading older version of Pester when Assert-VerifiableMocks is called.

The function will be removed finally in the next major version of Pester.

.LINK
https://github.com/pester/Pester/wiki/Migrating-from-Pester-3-to-Pester-4
https://github.com/pester/Pester/issues/880

#>

[CmdletBinding()]
param()

Throw "This command has been renamed to 'Assert-VerifiableMock' (without the 's' at the end), please update your code. For more information see: https://github.com/pester/Pester/wiki/Migrating-from-Pester-3-to-Pester-4"

}

& $script:SafeCommands['Export-ModuleMember'] Describe, Context, It, In, Mock, Assert-VerifiableMock, Assert-VerifiableMocks, Assert-MockCalled, Set-TestInconclusive
& $script:SafeCommands['Export-ModuleMember'] New-Fixture, Get-TestDriveItem, Should, Invoke-Pester, Setup, InModuleScope, Invoke-Mock
& $script:SafeCommands['Export-ModuleMember'] BeforeEach, AfterEach, BeforeAll, AfterAll
& $script:SafeCommands['Export-ModuleMember'] Get-MockDynamicParameter, Set-DynamicParameterVariable
& $script:SafeCommands['Export-ModuleMember'] SafeGetCommand, New-PesterOption
& $script:SafeCommands['Export-ModuleMember'] Invoke-Gherkin, Find-GherkinStep, BeforeEachFeature, BeforeEachScenario, AfterEachFeature, AfterEachScenario, GherkinStep -Alias Given, When, Then, And, But
& $script:SafeCommands['Export-ModuleMember'] New-MockObject, Add-AssertionOperator
tools\Snippets\Context.snippets.ps1xml
 
tools\Snippets\Describe.snippets.ps1xml
 
tools\Snippets\It.snippets.ps1xml
 
tools\Snippets\ShouldBe.snippets.ps1xml
 
tools\Snippets\ShouldBeGreaterThan.snippets.ps1xml
 
tools\Snippets\ShouldBeLessThan.snippets.ps1xml
 
tools\Snippets\ShouldBeNullOrEmpty.snippets.ps1xml
 
tools\Snippets\ShouldExist.snippets.ps1xml
 
tools\Snippets\ShouldFileContentMatch.snippets.ps1xml
 
tools\Snippets\ShouldMatch.snippets.ps1xml
 
tools\Snippets\ShouldNotBe.snippets.ps1xml
 
tools\Snippets\ShouldNotBeNullOrEmpty.snippets.ps1xml
 
tools\Snippets\ShouldNotExist.snippets.ps1xml
 
tools\Snippets\ShouldNotFileContentMatch.snippets.ps1xml
 
tools\Snippets\ShouldNotMatch.snippets.ps1xml
 
tools\Snippets\ShouldNotThrow.snippets.ps1xml
 
tools\Snippets\ShouldThrow.snippets.ps1xml
 

Log in or click on link to see number of positives.

In cases where actual malware is found, the packages are subject to removal. Software sometimes has false positives. Moderators do not necessarily validate the safety of the underlying software, only that a package retrieves software from the official distribution point and/or validate embedded software against official distribution point (where distribution rights allow redistribution).

Chocolatey Pro provides runtime protection from possible malware.

Add to Builder Version Downloads Last Updated Status
Pester 5.6.0 4870 Wednesday, June 5, 2024 Approved
Pester 5.5.0 19268 Tuesday, June 27, 2023 Approved
Pester 5.4.1 5106 Wednesday, April 5, 2023 Approved
Pester 5.4.0 5257 Tuesday, January 10, 2023 Approved
Pester 5.3.3 22033 Friday, April 29, 2022 Approved
Pester 5.3.2 907 Friday, April 22, 2022 Approved
Pester 5.3.1 13015 Tuesday, September 21, 2021 Approved
Pester 5.3.0 3819 Tuesday, August 17, 2021 Approved
Pester 5.2.2 9272 Thursday, May 27, 2021 Approved
Pester 5.2.1 1369 Thursday, May 13, 2021 Approved
Pester 5.2.0 976 Thursday, May 6, 2021 Approved
Pester 5.1.1 6031 Wednesday, February 10, 2021 Approved
Pester 5.1.0 150 Wednesday, February 10, 2021 Approved
Pester 5.0.4 2786 Wednesday, February 10, 2021 Approved
Pester 5.0.4-beta1 317 Thursday, August 13, 2020 Exempted
Pester 4.10.2-beta1 99 Saturday, May 29, 2021 Approved
Pester 4.10.1 21542 Friday, February 7, 2020 Approved
Pester 4.9.0 10974 Sunday, September 8, 2019 Approved
Pester 4.8.1 8468 Saturday, May 11, 2019 Approved
Pester 4.8.0 1134 Wednesday, May 1, 2019 Approved
Pester 4.7.3 3513 Saturday, March 23, 2019 Approved
Pester 4.7.2 1386 Friday, March 8, 2019 Approved
Pester 4.7.1 645 Tuesday, March 5, 2019 Approved
Pester 4.7.0 633 Sunday, March 3, 2019 Approved
Pester 4.4.1 9044 Thursday, September 20, 2018 Approved
Pester 4.4.0 5061 Friday, July 20, 2018 Approved
Pester 4.4.0-beta2 336 Sunday, July 8, 2018 Approved
Pester 4.4.0-beta 445 Sunday, May 6, 2018 Approved
Pester 4.3.1 7612 Tuesday, February 20, 2018 Approved
Pester 4.3.0 435 Tuesday, February 20, 2018 Approved
Pester 4.2.0 543 Sunday, February 18, 2018 Approved
Pester 4.2.0-alpha3 420 Sunday, December 17, 2017 Exempted
Pester 4.2.0-alpha2 435 Tuesday, December 12, 2017 Exempted
Pester 4.1.0 6086 Sunday, November 26, 2017 Approved
Pester 4.0.6-rc 601 Thursday, August 17, 2017 Approved
Pester 4.0.5-rc 491 Tuesday, July 25, 2017 Approved
Pester 4.0.3-rc 521 Wednesday, March 22, 2017 Approved
Pester 4.0.2-rc 551 Wednesday, January 18, 2017 Approved
Pester 4.0.1-rc 425 Wednesday, January 18, 2017 Approved
Pester 4.0.0-rc1 490 Wednesday, January 18, 2017 Approved
Pester 3.4.6 28688 Friday, January 13, 2017 Approved
Pester 3.4.3 15507 Friday, August 26, 2016 Approved
Pester 3.4.2 1351 Tuesday, August 2, 2016 Approved
Pester 3.4.1 8938 Friday, July 22, 2016 Approved
Pester 3.4.0 5951 Tuesday, March 1, 2016 Approved
Pester 3.3.14 5532 Wednesday, December 16, 2015 Approved
Pester 3.3.13 9983 Thursday, December 10, 2015 Approved
Pester 3.3.12 634 Tuesday, December 8, 2015 Approved
Pester 3.3.11 7067 Tuesday, September 8, 2015 Approved
Pester 3.3.10 2364 Friday, August 14, 2015 Approved
Pester 3.3.9 6696 Sunday, May 24, 2015 Approved
Pester 3.3.8 5036 Wednesday, April 15, 2015 Approved
Pester 3.3.7 502 Wednesday, April 15, 2015 Approved
Pester 3.3.6 1896 Thursday, March 19, 2015 Approved
Pester 3.3.5 2816 Friday, January 23, 2015 Approved
Pester 3.3.4 515 Thursday, January 22, 2015 Approved
Pester 3.3.3 462 Thursday, January 22, 2015 Approved
Pester 3.3.2 494 Monday, January 19, 2015 Approved
Pester 3.3.1 528 Monday, January 12, 2015 Approved
Pester 3.3.0 440 Saturday, January 10, 2015 Approved
Pester 3.2.0 667 Saturday, December 13, 2014 Approved
Pester 3.1.1 656 Wednesday, October 29, 2014 Approved
Pester 3.0.3 478 Monday, October 13, 2014 Approved
Pester 3.0.2 669 Monday, September 8, 2014 Approved
Pester 3.0.1.1 538 Thursday, August 28, 2014 Approved
Pester 3.0.0 470 Thursday, August 21, 2014 Approved
Pester 3.0.0-beta2 548 Friday, July 4, 2014 Approved
Pester 3.0.0-beta 556 Wednesday, June 25, 2014 Approved
Pester 2.1.0 598 Sunday, June 15, 2014 Approved
Pester 2.0.4 664 Sunday, March 9, 2014 Approved
Pester 2.0.3 979 Tuesday, April 16, 2013 Approved
Pester 2.0.2 6078 Thursday, February 28, 2013 Approved
Pester 2.0.1 568 Sunday, February 3, 2013 Approved
Pester 1.2.1 559 Sunday, February 3, 2013 Approved
Pester 1.1.0 611 Sunday, November 4, 2012 Approved
Pester 1.0.7-alpha-0 575 Tuesday, October 2, 2012 Approved
Pester 1.0.6 566 Sunday, August 12, 2012 Approved

This package has no dependencies.

Discussion for the Pester Package

Ground Rules:

  • This discussion is only about Pester and the Pester package. If you have feedback for Chocolatey, please contact the Google Group.
  • This discussion will carry over multiple versions. If you have a comment about a particular version, please note that in your comments.
  • The maintainers of this Chocolatey Package will be notified about new comments that are posted to this Disqus thread, however, it is NOT a guarantee that you will get a response. If you do not hear back from the maintainers after posting a message below, please follow up by using the link on the left side of this page or follow this link to contact maintainers. If you still hear nothing back, please follow the package triage process.
  • Tell us what you love about the package or Pester, or tell us what needs improvement.
  • Share your experiences with the package, or extra configuration or gotchas that you've found.
  • If you use a url, the comment will be flagged for moderation until you've been whitelisted. Disqus moderated comments are approved on a weekly schedule if not sooner. It could take between 1-5 days for your comment to show up.
comments powered by Disqus