DscResource.Test.psm1

#Region './Private/ConvertTo-OrderedDictionary.ps1' 0
function ConvertTo-OrderedDictionary
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')]
    [CmdletBinding()]
    [outputType([System.Object])]
    param
    (
        [Parameter(ValueFromPipeline = $true)]
        [Object]
        $InputObject
    )

    if ($null -eq $InputObject)
    {
        return $null
    }

    if ($InputObject -is [System.Collections.IDictionary])
    {
        $hashKeys = $InputObject.Keys
        # Making the Ordered Dict Case Insensitive
        $result = [ordered]@{ }
        foreach ($Key in $hashKeys)
        {
            $result[$Key] = ConvertTo-OrderedDictionary -InputObject $InputObject[$Key]
        }
        $result
    }
    elseif ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isNot [string])
    {
        $collection = @(
            foreach ($object in $InputObject)
            {
                ConvertTo-OrderedDictionary -InputObject $object
            }
        )

        , $collection
    }
    elseif ($InputObject -is [PSCustomObject])
    {
        $result = [ordered]@{ }
        foreach ($property in $InputObject.PSObject.Properties)
        {
            $result[$property.Name] = ConvertTo-OrderedDictionary -InputObject $property.Value
        }

        $result
    }
    else
    {
        $InputObject
    }
}
#EndRegion './Private/ConvertTo-OrderedDictionary.ps1' 54
#Region './Private/Get-ClassResourceNameFromFile.ps1' 0
<#
    .SYNOPSIS
        Retrieves the name(s) of any DSC class resources from a PowerShell file.

    .PARAMETER FilePath
        The full path to the file to test.

    .EXAMPLE
        Get-ClassResourceNameFromFile -FilePath 'c:\mymodule\myclassmodule.psm1'

        This command will get any DSC class resource names from the myclassmodule module.
#>

function Get-ClassResourceNameFromFile
{
    [OutputType([String[]])]
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [String]
        $FilePath
    )

    $classResourceNames = [String[]]@()

    if (Test-FileContainsClassResource -FilePath $FilePath)
    {
        $fileAst = [System.Management.Automation.Language.Parser]::ParseFile($FilePath, [ref]$null, [ref]$null)

        $typeDefinitionAsts = $fileAst.FindAll( {$args[0] -is [System.Management.Automation.Language.TypeDefinitionAst]}, $false)
        foreach ($typeDefinitionAst in $typeDefinitionAsts)
        {
            if ($typeDefinitionAst.Attributes.TypeName.Name -ieq 'DscResource')
            {
                $classResourceNames += $typeDefinitionAst.Name
            }
        }
    }

    return $classResourceNames
}
#EndRegion './Private/Get-ClassResourceNameFromFile.ps1' 41
#Region './Private/Get-CurrentModuleBase.ps1' 0
function Get-CurrentModuleBase
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param (
    )

    return $MyInvocation.MyCommand.Module.ModuleBase
}
#EndRegion './Private/Get-CurrentModuleBase.ps1' 9
#Region './Private/Get-DscResourceTestConfiguration.ps1' 0
function Get-DscResourceTestConfiguration
{
    [cmdletBinding()]
    param
    (
        [Parameter()]
        [Alias('Path')]
        [Object]
        $Configuration = (Join-Path $PWD '.MetaTestOptIn.json')
    )

    if ($Configuration -is [System.Collections.IDictionary])
    {
        Write-Debug "Configuration Object is a Dictionary"
    }
    elseif ($Configuration -is [System.Management.Automation.PSCustomObject])
    {
        Write-Debug "Configuration Object is a PSCustomObject"
    }
    elseif ( $Configuration -is [System.String])
    {
        Write-Debug "Configuration Object is a String, probably a Path"
        $Configuration = Get-StructuredObjectFromFile -Path $Configuration
    }
    else
    {
        throw "Could not resolve Configuration parameter $Configuration of Type $($Configuration.GetType().ToString())"
    }

    $NormalizedConfigurationObject = ConvertTo-OrderedDictionary -InputObject $Configuration

    return $NormalizedConfigurationObject
}
#EndRegion './Private/Get-DscResourceTestConfiguration.ps1' 33
#Region './Private/Get-FileParseError.ps1' 0
<#
    .SYNOPSIS
        Retrieves the parse errors for the given file.

    .PARAMETER FilePath
        The path to the file to get parse errors for.
#>

function Get-FileParseError
{
    [OutputType([System.Management.Automation.Language.ParseError[]])]
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [String]
        $FilePath
    )

    $parseErrors = $null
    $null = [System.Management.Automation.Language.Parser]::ParseFile($FilePath, [ref] $null, [ref] $parseErrors)

    return $parseErrors
}
#EndRegion './Private/Get-FileParseError.ps1' 23
#Region './Private/Get-ModuleScriptResourceName.ps1' 0

<#
    .SYNOPSIS
        Retrieves the names of all script resources for the given module.

    .PARAMETER ModulePath
        The path to the module to retrieve the script resource names of.
#>

function Get-ModuleScriptResourceName
{
    [OutputType([String[]])]
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [String]
        $ModulePath
    )

    $scriptResourceNames = @()

    $dscResourcesFolderFilePath = Join-Path -Path $ModulePath -ChildPath 'DscResources'
    $mofSchemaFiles = Get-ChildItem -Path $dscResourcesFolderFilePath -Filter '*.schema.mof' -File -Recurse

    foreach ($mofSchemaFile in $mofSchemaFiles)
    {
        $scriptResourceName = $mofSchemaFile.BaseName -replace '.schema', ''
        $scriptResourceNames += $scriptResourceName
    }

    return $scriptResourceNames
}
#EndRegion './Private/Get-ModuleScriptResourceName.ps1' 32
#Region './Private/Get-Psm1FileList.ps1' 0
<#
    .SYNOPSIS
        Retrieves all .psm1 files under the given file path.

    .PARAMETER FilePath
        The root file path to gather the .psm1 files from.
#>

function Get-Psm1FileList
{
    [OutputType([Object[]])]
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [String]
        $FilePath
    )

    return Get-ChildItem -Path $FilePath -Filter '*.psm1' -File -Recurse
}
#EndRegion './Private/Get-Psm1FileList.ps1' 20
#Region './Private/Get-RelativePathFromModuleRoot.ps1' 0

<#
    .SYNOPSIS
        This returns a string containing the relative path from the module root.

    .PARAMETER FilePath
        The file path to remove the module root path from.

    .PARAMETER ModuleRootFilePath
        The root path to remove from the file path.
#>

function Get-RelativePathFromModuleRoot
{
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $FilePath,

        [Parameter(Mandatory = $true)]
        [System.String]
        $ModuleRootFilePath
    )

    <#
        Removing the module root path from the file path so that the path
        doesn't get so long in the Pester output.
    #>

    return ($FilePath -replace [Regex]::Escape($ModuleRootFilePath), '').Trim([io.path]::DirectorySeparatorChar)
}
#EndRegion './Private/Get-RelativePathFromModuleRoot.ps1' 30
#Region './Private/Get-StructuredObjectFromFile.ps1' 0
function Get-StructuredObjectFromFile
{
    [cmdletBinding()]
    param
    (
        [Parameter()]
        [String]
        $Path
    )

    $ioPath = [System.IO.FileInfo]($PSCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path))
    switch -regex ($ioPath.Extension)
    {
        '^\.psd1$'
        {
            $ObjectFromFile = Import-PowerShellDataFile -Path $ioPath -ErrorAction Stop
        }

        '^\.y[a]?ml$'
        {
            Import-Module Powershell-yaml -ErrorAction Stop
            $FileContent = Get-Content -Raw -Path $ioPath -ErrorAction Stop
            $ObjectFromFile = ConvertFrom-Yaml -Ordered -Yaml $FileContent -ErrorAction Stop
        }

        '^\.json$'
        {
            $FileContent = Get-Content -Raw -Path $ioPath -ErrorAction Stop
            $ObjectFromFile = ConvertFrom-Json -InputObject $FileContent -ErrorAction Stop
        }

        Default
        {
            throw "File extension $($ioPath.Extension) not recognized."
        }
    }

    return $ObjectFromFile
}
#EndRegion './Private/Get-StructuredObjectFromFile.ps1' 39
#Region './Private/Get-SuppressedPSSARuleNameList.ps1' 0


<#
    .SYNOPSIS
        Retrieves the list of suppressed PSSA rules in the file at the given path.

    .PARAMETER FilePath
        The path to the file to retrieve the suppressed rules of.
#>

function Get-SuppressedPSSARuleNameList
{
    [OutputType([String[]])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $FilePath
    )

    $suppressedPSSARuleNames = [String[]]@()

    $fileAst = [System.Management.Automation.Language.Parser]::ParseFile($FilePath, [ref]$null, [ref]$null)

    # Overall file attributes
    $attributeAsts = $fileAst.FindAll( {$args[0] -is [System.Management.Automation.Language.AttributeAst]}, $true)

    foreach ($attributeAst in $attributeAsts)
    {
        if ([System.Diagnostics.CodeAnalysis.SuppressMessageAttribute].FullName.ToLower().Contains($attributeAst.TypeName.FullName.ToLower()))
        {
            $suppressedPSSARuleNames += $attributeAst.PositionalArguments.Extent.Text
        }
    }

    return $suppressedPSSARuleNames
}
#EndRegion './Private/Get-SuppressedPSSARuleNameList.ps1' 37
#Region './Private/Get-TextFilesList.ps1' 0

<#
    .SYNOPSIS
        Retrieves all text files under the given root file path.

    .PARAMETER Root
        The root file path under which to retrieve all text files.

    .NOTES
        Retrieves all files with the '.gitignore', '.gitattributes', '.ps1', '.psm1', '.psd1',
        '.json', '.xml', '.cmd', or '.mof' file extensions.
#>

function Get-TextFilesList
{
    [OutputType([System.IO.FileInfo[]])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $Root
    )

    $textFileExtensions = @('.gitignore', '.gitattributes', '.ps1', '.psm1', '.psd1', '.json', '.xml', '.cmd', '.mof', '.md', '.js', '.yml')

    return Get-ChildItem -Path $Root -File -Recurse | Where-Object { $textFileExtensions -contains $_.Extension }
}
#EndRegion './Private/Get-TextFilesList.ps1' 27
#Region './Private/Test-FileContainsClassResource.ps1' 0
<#
    .SYNOPSIS
        Tests if a PowerShell file contains a DSC class resource.

    .PARAMETER FilePath
        The full path to the file to test.

    .EXAMPLE
        Test-ContainsClassResource -ModulePath 'c:\mymodule\myclassmodule.psm1'

        This command will test myclassmodule for the presence of any class-based
        DSC resources.
#>

function Test-FileContainsClassResource
{
    [OutputType([Boolean])]
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [String]
        $FilePath
    )

    $fileAst = [System.Management.Automation.Language.Parser]::ParseFile($FilePath, [ref]$null, [ref]$null)

    foreach ($fileAttributeAst in $fileAst.FindAll( {$args[0] -is [System.Management.Automation.Language.AttributeAst]}, $false))
    {
        if ($fileAttributeAst.Extent.Text -ieq '[DscResource()]')
        {
            return $true
        }
    }

    return $false
}
#EndRegion './Private/Test-FileContainsClassResource.ps1' 36
#Region './Private/Test-FileHasByteOrderMark.ps1' 0

<#
    .SYNOPSIS
        Tests if a file contains Byte Order Mark (BOM).

    .PARAMETER FilePath
        The file path to evaluate.
#>

function Test-FileHasByteOrderMark
{
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $FilePath
    )

    $getContentParameters = @{
        Path       = $FilePath
        ReadCount  = 3
        TotalCount = 3
    }

    # Need to treat Windows Powershell and PowerShell Core different.
    if ($PSVersionTable.PSEdition -eq 'Core')
    {
        $getContentParameters['AsByteStream'] = $true
    }
    else
    {
        $getContentParameters['Encoding'] = 'Byte'
    }

    # This reads the first three bytes of the first row.
    $firstThreeBytes = Get-Content @getContentParameters

    # Check for the correct byte order (239,187,191) which equal the Byte Order Mark (BOM).
    return ($firstThreeBytes[0] -eq 239 `
            -and $firstThreeBytes[1] -eq 187 `
            -and $firstThreeBytes[2] -eq 191)
}
#EndRegion './Private/Test-FileHasByteOrderMark.ps1' 41
#Region './Private/Test-FileInUnicode.ps1' 0
<#
    .SYNOPSIS
        Tests if a file is encoded in Unicode.

    .PARAMETER FileInfo
        The file to test.
#>

function Test-FileInUnicode
{
    [OutputType([Boolean])]
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [System.IO.FileInfo]
        $FileInfo
    )

    $filePath = $FileInfo.FullName
    $fileBytes = [System.IO.File]::ReadAllBytes($filePath)
    $zeroBytes = @( $fileBytes -eq 0 )

    return ($zeroBytes.Length -ne 0)
}
#EndRegion './Private/Test-FileInUnicode.ps1' 24
#Region './Private/Test-ModuleContainsClassResource.ps1' 0

<#
    .SYNOPSIS
        Tests if a module contains a class resource.

    .PARAMETER ModulePath
        The path to the module to test.
#>

function Test-ModuleContainsClassResource
{
    [OutputType([Boolean])]
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [String]
        $ModulePath
    )

    $psm1Files = Get-Psm1FileList -FilePath $ModulePath

    foreach ($psm1File in $psm1Files)
    {
        if (Test-FileContainsClassResource -FilePath $psm1File.FullName)
        {
            return $true
        }
    }

    return $false
}
#EndRegion './Private/Test-ModuleContainsClassResource.ps1' 31
#Region './Private/Test-TestShouldBeSkipped.ps1' 0
function Test-TestShouldBeSkipped
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [System.String[]]
        $TestNames,

        [Parameter(Mandatory = $true)]
        [AllowNull()]
        [System.String[]]
        $Tag,

        [Parameter(Mandatory = $true)]
        [AllowNull()]
        [System.String[]]
        $ExcludeTag
    )

    if ($ExcludeTag)
    {
        $IsTagExcluded = Compare-Object -ReferenceObject $TestNames -DifferenceObject $ExcludeTag -IncludeEqual -ExcludeDifferent
    }
    else
    {
        $IsTagExcluded = $false
    }

    $IsTagIncluded = Compare-Object -ReferenceObject $TestNames -DifferenceObject $Tag -IncludeEqual -ExcludeDifferent

    # Should be skipped if It's excluded or Tags are in use and it's not included
    $ShouldBeSkipped = ($IsTagExcluded -or ($Tag -and -Not $isTagIncluded))

    if ($ShouldBeSkipped)
    {
        Write-Warning "The tests for $($TestNames -join ',') is not being enforced. Please Opt-in!"
    }

    return $ShouldBeSkipped
}
#EndRegion './Private/Test-TestShouldBeSkipped.ps1' 40
#Region './Public/Invoke-DscResourceTest.ps1' 0
function Invoke-DscResourceTest
{
    [CmdletBinding(DefaultParameterSetName = 'ByProjectPath')]
    param (
        [Parameter(ParameterSetName = 'ByModuleNameOrPath', Position = 0)]
        [System.String]
        ${Module},

        [Parameter(ParameterSetName = 'ByModuleSpecification', Position = 0)]
        [Microsoft.PowerShell.Commands.ModuleSpecification]
        $FullyQualifiedModule,

        [Parameter(ParameterSetName = 'ByProjectPath', Position = 0)]
        [System.String]
        ${ProjectPath},

        [Parameter(Position = 1)]
        [Alias('Path', 'relative_path')]
        [System.Object[]]
        ${Script},

        [Parameter(Position = 2)]
        [Alias('Name')]
        [string[]]
        ${TestName},

        [Parameter(Position = 3)]
        [switch]
        ${EnableExit},

        [Parameter(Position = 5)]
        [Alias('Tags')]
        [string[]]
        ${Tag},

        [Parameter()]
        [string[]]
        ${ExcludeTag},

        [Parameter()]
        [switch]
        ${PassThru},

        [Parameter()]
        [System.Object[]]
        ${CodeCoverage},

        [Parameter()]
        [string]
        ${CodeCoverageOutputFile},

        [Parameter()]
        [ValidateSet('JaCoCo')]
        [string]
        ${CodeCoverageOutputFileFormat},

        [Parameter()]
        [switch]
        ${Strict},

        [Parameter(ParameterSetName = 'NewOutputSet', Mandatory = $true)]
        [string]
        ${OutputFile},

        [Parameter(ParameterSetName = 'NewOutputSet')]
        [ValidateSet('NUnitXml', 'JUnitXml')]
        [string]
        ${OutputFormat},

        [Parameter()]
        [switch]
        ${Quiet},

        [Parameter()]
        [System.Object]
        ${PesterOption},

        [Parameter()]
        [Pester.OutputTypes]
        ${Show},

        [Parameter()]
        [Hashtable]
        $Settings

    )

    begin
    {
        # Make sure Invoke-DscResourceTest runs against the Built Module either:
        # By $Module (Name, Path, ModuleSpecification): enables to run some tests on installed modules (even without source)
        # By $ProjectPath (detect source from there based on .psd1): Target both the source when relevant and the expected files

        switch ($PSCmdlet.ParameterSetName)
        {
            'ByModuleNameOrPath'
            {
                Write-Verbose "Calling DscResource Test by Module Name (Or Path)"
                if (!$PSBoundParameters.ContainsKey('Script'))
                {
                    $PSBoundParameters['Script'] = Join-Path -Path $MyInvocation.MyCommand.Module.ModuleBase -ChildPath 'Tests\QA\BuiltModule'
                }
                $null = $PSBoundParameters.Remove('Module')
                $ModuleUnderTest = Import-Module -Name $Module -ErrorAction Stop -Force -PassThru
            }

            'ByModuleSpecification'
            {
                Write-Verbose "Calling DscResource Test by Module Specification"
                if (!$PSBoundParameters.ContainsKey('Script'))
                {
                    $PSBoundParameters['Script'] = Join-Path -Path $MyInvocation.MyCommand.Module.ModuleBase -ChildPath 'Tests\QA\BuiltModule'
                }
                $null = $PSBoundParameters.Remove('FullyQualifiedModule')
                $ModuleUnderTest = Import-Module -FullyQualifiedName $FullyQualifiedModule -Force -PassThru -ErrorAction Stop
            }

            'ByProjectPath'
            {
                Write-Verbose "Calling DscResource Test by Project Path"
                if (!$ProjectPath)
                {
                    $ProjectPath = $PWD.Path
                }

                try
                {
                    $null = $PSBoundParameters.Remove('ProjectPath')
                }
                catch
                {
                    Write-Debug -Message "The function was called via default param set. Using `$PWD for Project Path"
                }

                if (!$PSBoundParameters.ContainsKey('Script'))
                {
                    $PSBoundParameters['Script'] = Join-Path -Path $MyInvocation.MyCommand.Module.ModuleBase -ChildPath 'Tests\QA'
                }
                # Find the Source Manifest under ProjectPath
                $SourceManifest = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{
                        ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and
                        $(try
                            {
                                Test-ModuleManifest $_.FullName -ErrorAction Stop
                            }
                            catch
                            {
                                $false
                            } )
                    })

                $SourcePath = $SourceManifest.Directory.FullName

                $GetOutputModuleParams = @{
                    Path        = (Join-Path $ProjectPath 'output')
                    Include     = $SourceManifest.Name
                    Recurse     = $true
                    Exclude     = 'RequiredModules'
                    ErrorAction = 'Stop'
                }

                Write-Verbose (
                    "Finding Output Module with `r`n {0}" -f
                    ($GetOutputModuleParams | Format-Table -AutoSize | Out-String)
                )

                $ModulePsd1 = Get-ChildItem @GetOutputModuleParams
                $ModuleUnderTest = Import-Module -Name $ModulePsd1 -ErrorAction Stop -PassThru
            }
        }

        # In case of ByProjectPath Opt-ins will be done by tags:
        # The Describe Name will be one of the Tag for the Describe block
        # If a Opt-In file is found, it will default to auto-populate -Tag (cumulative from Command parameters)
        if ($ProjectPath)
        {
            $ExpectedMetaOptInFile = Join-Path -Path $ProjectPath -ChildPath '.MetaTestOptIn.json'
            if ($PSCmdlet.ParameterSetName -eq 'ByProjectPath' -and (Test-Path $ExpectedMetaOptInFile))
            {
                Write-Verbose -Message "Loading OptIns from $ExpectedMetaOptInFile"
                $OptIns = Get-StructuredObjectFromFile -Path $ExpectedMetaOptInFile -ErrorAction Stop
            }
            # Opt-Outs should be preferred, and we can do similar ways with ExcludeTags
            $ExpectedMetaOptOutFile = Join-Path -Path $ProjectPath -ChildPath '.MetaTestOptOut.json'
            if ($PSCmdlet.ParameterSetName -eq 'ByProjectPath' -and (Test-Path $ExpectedMetaOptOutFile))
            {
                Write-Verbose -Message "Loading OptOuts from $ExpectedMetaOptOutFile"
                $OptOuts = Get-StructuredObjectFromFile -Path $ExpectedMetaOptOutFile -ErrorAction Stop
            }
        }

        # For each Possible parameters, use BoundParameters if exists, or use $Settings.ParameterName if exists otherwise
        $PossibleParamName = $PSCmdlet.MyInvocation.MyCommand.Parameters.Name
        foreach ($ParamName in $PossibleParamName)
        {
            if ( !$PSBoundParameters.ContainsKey($ParamName) -and
                ($ParamValue = $Settings.($ParamName))
            )
            {
                Write-Verbose -Message "Adding setting $ParamName"
                $PSBoundParameters.Add($ParamName, $ParamValue)
            }
        }

        $newTag = @()
        $newExcludeTag = @()

        # foreach OptIns, add them to `-Tag`, unless in the ExcludeTags or already in Tag
        foreach ($OptInTag in $OptIns)
        {
            if ( $OptInTag -notIn $PSBoundParameters['ExcludeTag'] -and
                $OptInTag -notIn $PSBoundParameters['Tag']
            )
            {
                Write-Debug -Message "Adding tag $OptInTag"
                $newTag += $OptInTag
            }
        }

        if ($newTag.Count -gt 0)
        {
            $PSBoundParameters['Tag'] = $newTag
        }

        # foreach OptOuts, add them to `-ExcludeTag`, unless in `-Tag`
        foreach ($OptOutTag in $OptOuts)
        {
            if ( $OptOutTag -notIn $PSBoundParameters['Tag'] -and
                $OptOutTag -notIn $PSBoundParameters['ExcludeTag']
            )
            {
                Write-Debug -Message "Adding ExcludeTag $OptOutTag"
                $newExcludeTag += $OptOutTag
            }
        }

        if ($newExcludeTag.Count -gt 0)
        {
            $PSBoundParameters['ExcludeTag'] = $newExcludeTag
        }

        # This won't display the warning message for the skipped blocks
        # But should save time by not running initialization code within a Describe Block
        # And we can add such warning if we create a static list of the things we can opt-in
        # I'd prefer to not keep anything static, and AST risks not to cover 100% (maybe...), and OptOut is prefered

        # Most tests should run against the built module
        # PSSA could be run against source, or against built module & convert lines/file

        $ModuleUnderTestManifest = Join-Path -Path $ModuleUnderTest.ModuleBase -ChildPath "$($ModuleUnderTest.Name).psd1"

        $ScriptItems = foreach ($item in $PSBoundParameters['Script'])
        {
            if ($item -is [System.Collections.IDictionary])
            {
                if ($item['Parameters'] -isNot [System.Collections.IDictionary])
                {
                    $item['Parameters'] = @{ }
                }
                $item['Parameters']['ModuleBase']     = $ModuleUnderTest.ModuleBase
                $item['Parameters']['ModuleName']     = $ModuleUnderTest.Name
                $item['Parameters']['ModuleManifest'] = $ModuleUnderTestManifest
                $item['Parameters']['ProjectPath']    = $ProjectPath
                $item['Parameters']['SourcePath']     = $SourcePath
                $item['Parameters']['SourceManifest'] = $SourceManifest.FullName
                $item['Parameters']['Tag']            = $PSBoundParameters['Tag']
                $item['Parameters']['ExcludeTag']     = $PSBoundParameters['ExcludeTag']
            }
            else
            {
                $item = @{
                    Path       = $item
                    Parameters = @{
                        ModuleBase     = $ModuleUnderTest.ModuleBase
                        ModuleName     = $ModuleUnderTest.Name
                        ModuleManifest = $ModuleUnderTestManifest
                        ProjectPath    = $ProjectPath
                        SourcePath     = $SourcePath
                        SourceManifest = $SourceManifest.FullName
                        Tag            = $PSBoundParameters['Tag']
                        ExcludeTag     = $PSBoundParameters['ExcludeTag']
                    }
                }
            }

            $item
        }

        $PSBoundParameters['Script'] = $ScriptItems

        # Below is default command proxy handling
        try
        {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
            {
                $PSBoundParameters['OutBuffer'] = 1
            }
            $wrappedCmd = Get-Command -CommandType Function -Name Invoke-Pester
            $scriptCmd = { & $wrappedCmd @PSBoundParameters }
            $steppablePipeline = $scriptCmd.GetSteppablePipeline()
            $steppablePipeline.Begin($PSCmdlet)
        }
        catch
        {
            throw
        }
    }

    process
    {
        try
        {
            $steppablePipeline.Process($_)
        }
        catch
        {
            throw
        }
    }

    end
    {
        try
        {
            $steppablePipeline.End()
        }
        catch
        {
            throw
        }
    }
    <#

.ForwardHelpTargetName Invoke-Pester
.ForwardHelpCategory Function

#>



}
#EndRegion './Public/Invoke-DscResourceTest.ps1' 341