KMT.ModuleBuilder.psm1


using namespace System.Collections.Generic
using namespace System.ComponentModel.Composition
using namespace System.IO
using namespace System.Management.Automation

$Script:PSModuleRoot = $PSScriptRoot
# Importing from [/home/vsts/work/1/s/KMT.ModuleBuilder\Private]
# ./KMT.ModuleBuilder/Private/Get-KmtBuildVariable.ps1
function Get-KmtBuildVariable
{
    <#
        .Synopsis
        Retreives initialization variables
        .Example
        $buildVar = Get-KmtBuildVariable

        .Notes

    #>

    [cmdletbinding()]
    param()

    end
    {
        $script:buildInit
    }
}

# ./KMT.ModuleBuilder/Private/Get-KmtSpecification.ps1
function Get-KmtSpecification
{
    <#
        .Synopsis
        Gets the module.kmt.json file for the module

        .Example
        Get-KmtSpecification -Path $Path

        .Notes
        Searches specified folder first
        Then sub folders
        Then parent folders
    #>


    [cmdletbinding()]
    param(
        [Parameter(
            Position = 0,
            ValueFromPipeline
        )]
        [string]
        $Path = (Get-Location)
    )

    process
    {
        try
        {
            $filter = @{filter = 'module.kmt.json'}

            Write-Debug "Load from local folder [$path]"
            $specFile = Get-ChildItem $Path @filter
            if(-not $specFile)
            {
                Write-Debug 'Check all child folders'
                $specFile = Get-ChildItem $Path @filter -Recurse |
                    Select -First 1
            }

            if(-not $specFile)
            {
                while($Path = Split-Path $Path)
                {
                    Write-Debug "Walk parent folders [$Path]"
                    $specFile = Get-ChildItem $Path @filter
                }
            }

            if(-not $specFile)
            {
                Write-Debug 'Specification was not found'
                throw [System.IO.FileNotFoundException]::new(
                    'Unable to find [module.kmt.json] specification',
                    'module.kmt.json'
                )
            }

            Write-Verbose "Loading KMT Specification [$($specFile.FullName)]"
            $specification = Get-Content -Path $specFile -Raw |
                ConvertFrom-Json

            # attach spec path so we can find the root of the project
            $member = @{
                MemberType = 'NoteProperty'
                Name = 'specificationPath'
                Value = $specFile.FullName
            }
            $specification | Add-Member @member

            $specification
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./KMT.ModuleBuilder/Private/Get-ModulePublicInterfaceMap.ps1
function Get-ModulePublicInterfaceMap
{
    <#
    .Notes
    Imports a module and build a map of the public interface that is easy to compareS
    #>

    param($Path)
    $module = Import-KmtModule -Path $Path -PassThru -Verbose:$false
    $exportedCommands = @(
        $module.ExportedFunctions.values
        $module.ExportedCmdlets.values
        $module.ExportedAliases.values
    )

    foreach($command in $exportedCommands)
    {
        foreach ($parameter in $command.Parameters.Keys)
        {
            if($false -eq $command.Parameters[$parameter].IsDynamic)
            {
                '{0}:{1}' -f $command.Name, $command.Parameters[$parameter].Name
                foreach ($alias in $command.Parameters[$parameter].Aliases)
                {
                    '{0}:{1}' -f $command.Name, $alias
                }
            }
        }
    }
}

# ./KMT.ModuleBuilder/Private/Initialize-KmtModuleProject.ps1
function Initialize-KmtModuleProject
{
    <#
        .Synopsis
        Initializes several project values used by other functions

        .Example
        Initialize-KmtModuleProject -Path $Path

        .Notes

    #>

    [Export()]
    [cmdletbinding()]
    param(
        [string]
        # Root folder of the project
        $Path = (Get-Location)
    )

    end
    {
        try
        {
            $spec = Get-KmtSpecification -Path $Path
            $moduleName = $spec.moduleName
            $buildRoot = Split-Path -Path $spec.specificationPath
            $folders = $spec.folders

            Write-Verbose "Initializing build variables"
            $output = Join-Path -Path $buildRoot -ChildPath 'Output'
            $destination = Join-Path -Path $Output -ChildPath $moduleName
            $script:buildInit = @{
                ModuleName   = $moduleName
                BuildRoot    = $buildRoot
                DocsPath     = Join-Path -Path $buildRoot -ChildPath 'Docs'
                Output       = $output
                Source       = Join-Path -Path $buildRoot -ChildPath $moduleName
                Destination  = $destination
                ManifestPath = Join-Path -Path $destination -ChildPath "$moduleName.psd1"
                ModulePath   = Join-Path -Path $destination -ChildPath "$moduleName.psm1"
                Folders      = $folders
                TestFile     = Join-Path -Path $output -ChildPath "TestResults_PS${PSVersion}_$TimeStamp.xml"
                PSRepository = 'PSGallery'
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./KMT.ModuleBuilder/Private/Move-Statement.ps1

function Move-Statement
{
<#
.SYNOPSIS
    Moves statements containing a specified token to the specified index in a file.
.DESCRIPTION
    Move-Statement moves statements containing a specified token, to the specified index
    in a file. This can be used when building a module to move any using directives and
    #Requires statements to the top of a file.
.PARAMETER Path
    Specifies the path to an item to get its contents.
.PARAMETER Type
    Specifies the type of tokens to examine. Accepted values include "Comment" and "Keyword".
.PARAMETER Token
    Specifies the contents to filter on when examining a supplied token.
.PARAMETER Index
    Specifies the line to move a statement to. Each line in an item has a corresponding
    index, starting from 0.
.EXAMPLE
    Move-Statement -Path $Path -Type 'Comment', 'Keyword' -Token '#Requires', 'using' -Index 0

    Moves any using directives or #Requires statements to the top of a file.
.NOTES
    Copy/Paste from LDModuleBuilder
#>

    [CmdletBinding(SupportsShouldProcess)]

    param(
        [Parameter(Mandatory,
                   Position = 0,
                   ValueFromPipeline,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ Test-Path -Path $PSItem })]
        [string] $Path,

        [Parameter(Position = 1,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet('Comment', 'Keyword')]
        [string[]] $Type = ('Comment', 'Keyword'),

        [Parameter(Position = 2,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string[]] $Token = ('#Requires', 'using'),

        [Parameter(Position = 3,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [int] $Index = 0
    )

    process
    {
        try
        {
            $statements = [SortedSet[String]]::new(
                [StringComparer]::InvariantCultureIgnoreCase
            )

            Write-Verbose -Message "Reading content from $Path..."
            $content = [List[String]] (Get-Content $Path)

            Write-Verbose -Message "Tokenizing content from $Path..."
            $tokens = [PSParser]::Tokenize($content, [ref] $null)

            $match = $Token -join '|'

            Write-Verbose -Message 'Matching tokens...'
            Write-Verbose -Message "Type = [$Type]; Token = [$Token]"
            $keywords = $tokens.Where({
                $PSItem.Type -in $Type -and
                $PSItem.Content -imatch "^(?:$match)"
            })

            if (-not $keywords) {
                Write-Verbose -Message 'No matching tokens found! Returning...'
                return
            }

            $offset = 1
            foreach ($keyword in $keywords)
            {
                $line = $keyword.StartLine - $offset

                Write-Verbose -Message "Moving [$($content[$line])] to Index [$Index]..."
                $null = $statements.Add($content[$line]),
                        $content.RemoveAt($line)
                $offset++
            }

            [string[]] $comments, [string[]] $statements = $statements.Where({
                $PSItem -match '^#'
            }, 'Split')

            foreach ($item in ($statements, $comments))
            {
                $content.Insert($Index, '')
                $content.InsertRange($Index, $item)
            }

            if ($PSCmdlet.ShouldProcess($Path, $MyInvocation.MyCommand.Name))
            {
                Write-Verbose -Message "Writing content to $Path..."
                Set-Content -Path  $Path -Value $content -Encoding 'UTF8'
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError($PSItem)
        }
    }
}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KmtAnalyzeTask.ps1
function Invoke-KmtAnalyzeTask
{
    <#
        .Synopsis
        Invokes script analzer
        .Example
        Invoke-KmtAnalyzeTask -Path $Path

        .Notes

    #>

    [cmdletbinding()]
    param()

    try
    {
        $BuildVariables = Get-KmtBuildVariable

        $scripts = Get-ChildItem -Path (Join-Path $PSModuleRoot 'Pester') |
            Foreach-Object {@{
                Path       = $_.FullName
                Parameters = @{BuildVariables = $BuildVariables}
            }}

        $pester = @{
            Script = $scripts
            PassThru = $true
            Show     = 'Failed', 'Fails', 'Summary'
        }
        $results = Invoke-Pester @pester
        if ($results.FailedCount -gt 0)
        {
            Write-Error -Message "Failed [$($results.FailedCount)] Ktm Pester tests." -ErrorAction Stop
        }
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError( $PSItem )
    }
}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KmtBuildManifestTask.ps1
function Invoke-KmtBuildManifestTask
{
    <#
        .Synopsis
        Copies the manifest and updates the functions to export
        .Example
        Invoke-KmtBuildManifestTask -Path $Path

        .Notes

    #>

    [cmdletbinding()]
    param()

    end
    {
        try
        {
            $ManifestPath = (Get-KmtBuildVariable).ManifestPath
            $ModuleName = (Get-KmtBuildVariable).ModuleName
            $Source = (Get-KmtBuildVariable).Source

            Write-Verbose "Updating [$ManifestPath]..."
            Copy-Item -Path "$Source\$ModuleName.psd1" -Destination $ManifestPath

            $functions = Get-ChildItem -Path "$ModuleName\Public" -Recurse -Filter *.ps1 -ErrorAction 'Ignore' |
                Where-Object 'Name' -notmatch 'Tests'

            if ($functions)
            {
                Write-Verbose 'Setting FunctionsToExport...'
                Set-ModuleFunction -Name $ManifestPath -FunctionsToExport $functions.BaseName
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KmtBuildModuleTask.ps1
function Invoke-KmtBuildModuleTask
{
    <#
        .Synopsis
        Executes the build module task
        .Example
        Invoke-KmtBuildModuleTask

        .Notes

    #>

    [cmdletbinding()]
    param()

    try
    {
        $source = (Get-KmtBuildVariable).Source
        $modulePath = (Get-KmtBuildVariable).ModulePath

        $buildRoot = (Get-KmtBuildVariable).BuildRoot
        $folders = (Get-KmtBuildVariable).Folders

        $sb = [Text.StringBuilder]::new()
        $null = $sb.AppendLine('$Script:PSModuleRoot = $PSScriptRoot')

        # Class importer
        $root = Join-Path -Path $source -ChildPath 'Classes'
        Write-Verbose "Load classes from [$root]"
        $classFiles = Get-ChildItem -Path $root -Filter '*.ps1' -Recurse |
            Where-Object Name -notlike '*.Tests.ps1'

        $classes = @{ }

        foreach ($file in $classFiles)
        {
            $name = $file.BaseName
            $classes[$name] = @{
                Name = $name
                Path = $file.FullName
            }
            $data = Get-Content $file.fullname
            foreach ($line in $data)
            {
                if ($line -match "\s+($Name)\s*(:|requires)\s*(?<baseclass>\w*)|\[(?<baseclass>\w+)\]")
                {
                    $classes[$name].Base += @($Matches.baseclass)
                }
            }
        }

        $importOrder = $classes.GetEnumerator() | Resolve-DependencyOrder  -Key { $_.Name } -DependsOn { $_.Value.Base }

        foreach ($class in $importOrder)
        {
            $classPath = $class.Value.Path
            Write-Verbose "Importing [$classPath]..."
            $null = $sb.AppendLine("# .$classPath")
            $null = $sb.AppendLine([IO.File]::ReadAllText($classPath))
        }

        foreach ($folder in ($Folders -ne 'Classes'))
        {
            if (Test-Path -Path "$Source\$folder")
            {
                $null = $sb.AppendLine("# Importing from [$Source\$folder]")
                $files = Get-ChildItem -Path "$Source\$folder" -Recurse -Filter *.ps1 |
                    Where-Object 'Name' -notlike '*.Tests.ps1'

                foreach ($file in $files)
                {
                    $name = $file.Fullname.Replace($buildroot, '')

                    Write-Verbose "Importing [$($file.FullName)]..."
                    $null = $sb.AppendLine("# .$name")
                    $null = $sb.AppendLine([IO.File]::ReadAllText($file.FullName))
                }
            }
        }

        Write-Verbose "Creating Module [$ModulePath]..."
        $null = New-Item -Path (Split-path $ModulePath) -ItemType Directory -ErrorAction SilentlyContinue -Force
        Set-Content -Path  $ModulePath -Value $sb.ToString() -Encoding 'UTF8'

        Write-Verbose 'Moving "#Requires" statements and "using" directives...'
        Move-Statement -Path $ModulePath -Type 'Comment', 'Keyword' -Token '#Requires', 'using' -Index 0
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError( $PSItem )
    }

}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KmtCleanTask.ps1
function Invoke-KmtCleanTask
{
    <#
        .Synopsis
        Cleans the project

        .Example
        Invoke-KmtCleanTask

        .Notes

    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
    [cmdletbinding(SupportsShouldProcess)]
    param()

    try
    {
        Initialize-KmtModuleProject -Path "$testdrive\SampleModule"
        $Output = (Get-KmtBuildVariable).Output
        if (Test-Path $Output)
        {
            Write-Verbose "Cleaning Output files in [$Output]..."
            $null = Get-ChildItem -Path $Output -File -Recurse |
                Remove-Item -Force -ErrorAction 'Ignore'

            Write-Verbose "Cleaning Output directories in [$Output]..."
            $null = Get-ChildItem -Path $Output -Directory -Recurse |
                Remove-Item -Recurse -Force -ErrorAction 'Ignore'
        }
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError( $PSItem )
    }

}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KmtCopyTask.ps1
function Invoke-KmtCopyTask
{
    <#
        .Synopsis
        Copy module files and structure into build output

        .Example
        Invoke-KmtCopyTask

        .Notes

    #>

    [cmdletbinding()]
    param(

    )

    begin
    {

    }

    end
    {
        try
        {
            $source = (Get-KmtBuildVariable).Source
    $destination = (Get-KmtBuildVariable).Destination
    $moduleName = (Get-KmtBuildVariable).ModuleName
    $buildRoot = (Get-KmtBuildVariable).BuildRoot
    $folders = (Get-KmtBuildVariable).Folders

    "Creating Directory [$Destination]..."
    $null = New-Item -ItemType 'Directory' -Path $Destination -ErrorAction 'Ignore'

    $files = Get-ChildItem -Path $Source -File |
        Where-Object 'Name' -notmatch "$ModuleName\.ps[dm]1"

    foreach ($file in $files)
    {
        'Creating [.{0}]...' -f $file.FullName.Replace($buildroot, '')
        Copy-Item -Path $file.FullName -Destination $Destination -Force
    }

    $directories = Get-ChildItem -Path $Source -Directory |
        Where-Object 'Name' -notin $Folders

    foreach ($directory in $directories)
    {
        'Creating [.{0}]...' -f $directory.FullName.Replace($buildroot, '')
        Copy-Item -Path $directory.FullName -Destination $Destination -Recurse -Force
    }

    $license = Join-Path -Path $buildroot -ChildPath 'LICENSE'
    if ( Test-Path -Path $license -PathType Leaf )
    {
        Copy-Item -Path $license -Destination $Destination
    }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KmtDotNetCompileTask.ps1
function Invoke-KmtDotNetCompileTask
{
    <#
        .Synopsis
        Compiles DotNet binary module
        .Example
        Invoke-KmtDotNetCompileTask -Path $Path

        .Notes

    #>

    [cmdletbinding()]
    param()

    end
    {
        try
        {
            $buildRoot = (Get-KmtBuildVariable).BuildRoot
            $destination = (Get-KmtBuildVariable).Destination

            $csproj = Get-ChildItem -Path $buildRoot -Include *.csproj -Recurse
            if ($csproj)
            {

                # This build command requires .Net Core
                # TODO add check for dotnet
                Write-Verbose "Building Binary Module"
                $csproj = Get-ChildItem -Path $BuildRoot -Include *.csproj -Recurse
                $folder = Split-Path $csproj
                dotnet build $folder -c Release -o $Destination\bin
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KmtGenerateHelpTask.ps1
function Invoke-KmtGenerateHelpTask
{
    <#
        .Synopsis
        Generates help from markdown files

        .Example
        Invoke-KmtGenerateHelpTask

        .Notes

    #>

    [cmdletbinding()]
    param()


    try
    {
        $docsPath = (Get-KmtBuildVariable).DocsPath
        $destination = (Get-KmtBuildVariable).Destination
        $moduleName = (Get-KmtBuildVariable).ModuleName

        if (-not(Get-ChildItem -Path $DocsPath -Filter '*.md' -Recurse -ErrorAction 'Ignore'))
        {
            Write-Verbose "No Markdown help files to process. Skipping help file generation..."
            return
        }

        $locales = (Get-ChildItem -Path $DocsPath -Directory).Name
        foreach ($locale in $locales)
        {
            $params = @{
                ErrorAction = 'SilentlyContinue'
                Force       = $true
                OutputPath  = "$Destination\en-US"
                Path        = "$DocsPath\en-US"
            }

            # Generate the module's primary MAML help file.
            Write-Verbose "Creating new External help for [$ModuleName]..."
            $null = New-ExternalHelp @params
        }
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError( $PSItem )
    }

}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KmtGenerateMarkdown.ps1
function Invoke-KmtGenerateMarkdown
{
    <#
        .Synopsis
        Generates mardown files for all functions

        .Example
        Invoke-KmtGenerateMarkdown

        .Notes

    #>

    [cmdletbinding()]
    param(

    )


    try
    {
        $manifestPath = (Get-KmtBuildVariable).ManifestPath
        $docsPath = (Get-KmtBuildVariable).DocsPath
        $moduleName = (Get-KmtBuildVariable).ModuleName

        $module = Import-KmtModule -Path $ManifestPath -PassThru

        try
        {
            if ($module.ExportedFunctions.Count -eq 0)
            {
                Write-Verbose 'No functions have been exported for this module. Skipping Markdown generation...'
                return
            }

            if (Get-ChildItem -Path $DocsPath -Filter '*.md' -Recurse)
            {
                $items = Get-ChildItem -Path $DocsPath -Directory -Recurse
                foreach ($item in $items)
                {
                    Write-Verbose "Updating Markdown help in [$($item.BaseName)]..."
                    $null = Update-MarkdownHelp -Path $item.FullName -AlphabeticParamsOrder
                }
            }

            $params = @{
                AlphabeticParamsOrder = $true
                ErrorAction           = 'SilentlyContinue'
                Locale                = 'en-US'
                Module                = $ModuleName
                OutputFolder          = "$DocsPath\en-US"
                WithModulePage        = $true
            }

            # ErrorAction is set to SilentlyContinue so this
            # command will not overwrite an existing Markdown file.
            Write-Verbose "Creating new Markdown help for [$ModuleName]..."
            $null = New-MarkdownHelp @params
        }
        finally
        {
            Remove-Module -Name $ModuleName -Force
        }
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError( $PSItem )
    }


}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KmtImportBuiltModuleTask.ps1
function Invoke-KmtImportBuiltModuleTask
{
    <#
        .Synopsis
        Imports the built module

        .Example
        Invoke-KmtImportBuiltModuleTask

        .Notes

    #>

    [cmdletbinding()]
    param()

    try
    {
        $path = (Get-KmtBuildVariable).ManifestPath
        Import-KmtModule -Path $path -Force
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError( $PSItem )
    }
}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KmtImportDevModuleTask.ps1
function Invoke-KmtImportDevModuleTask
{
    <#
        .Synopsis
        Import the dev version of the module

        .Example
        Invoke-KmtImportDevModuleTask

        .Notes

    #>

    [cmdletbinding()]
    param()

    try
    {
        $source = (Get-KmtBuildVariable).Source
        $moduleName = (Get-KmtBuildVariable).ModuleName
        Import-KmtModule -Path "$Source\$ModuleName.psd1" -Force
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError( $PSItem )
    }

}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KmtInstallModule.ps1
function Invoke-KmtInstallModule
{
    <#
        .Synopsis
        Installs this module to the system

        .Example
        Invoke-KmtInstallModule

        .Notes

    #>

    [cmdletbinding()]
    param(
        # Parameter help description
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $Path
    )


    try
    {
        $manifestPath = (Get-KmtBuildVariable).ManifestPath
        $moduleName = (Get-KmtBuildVariable).ModuleName
        $destination = (Get-KmtBuildVariable).Destination

        $version = [version] (Get-Metadata -Path $manifestPath -PropertyName 'ModuleVersion')

        $path = $env:PSModulePath.Split(';').Where( {
                $_ -like 'C:\Users\*'
            }, 'First', 1)

        if ($path -and (Test-Path -Path $path))
        {
            Write-Verbose "Using [$path] as base path..."
            $path = Join-Path -Path $path -ChildPath $ModuleName
            $path = Join-Path -Path $path -ChildPath $version

            Write-Verbose "Creating directory at [$path]..."
            New-Item -Path $path -ItemType 'Directory' -Force -ErrorAction 'Ignore'

            Write-Verbose "Copying items from [$Destination] to [$path]..."
            Copy-Item -Path "$Destination\*" -Destination $path -Recurse -Force
        }
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError( $PSItem )
    }

}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KmtPesterTask.ps1
function Invoke-KmtPesterTask
{
    <#
        .Synopsis
        Invokes Pester

        .Example
        Invoke-KmtPesterTask -Path $Path

        .Notes

    #>

    [cmdletbinding()]
    param(
    )

    try
    {
        $testFile = (Get-KmtBuildVariable).TestFile

        $requiredPercent = $Script:CodeCoveragePercent

        $params = @{
            OutputFile   = $testFile
            OutputFormat = 'NUnitXml'
            PassThru     = $true
            Path         = 'Tests'
            Show         = 'Failed', 'Fails', 'Summary'
            Tag          = 'Build'
        }

        if ($requiredPercent -gt 0.00)
        {
            $params['CodeCoverage'] = 'Output\*\*.psm1'
            $params['CodeCoverageOutputFile'] = 'Output\codecoverage.xml'
        }

        $results = Invoke-Pester @params
        if ($results.FailedCount -gt 0)
        {
            Write-Error -Message "Failed [$($results.FailedCount)] Pester tests."
        }

        if ($results.codecoverage.NumberOfCommandsAnalyzed -gt 0)
        {
            $codeCoverage = $results.codecoverage.NumberOfCommandsExecuted / $results.codecoverage.NumberOfCommandsAnalyzed

            if ($codeCoverage -lt $requiredPercent)
            {
                Write-Error ("Failed Code Coverage [{0:P}] below {1:P}" -f $codeCoverage, $requiredPercent)
            }
        }
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError( $PSItem )
    }
}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KmtPublishModuleTask.ps1
function Invoke-KmtPublishModuleTask
{
    <#
        .Synopsis
        Publishes to Repository
        .Example
        Invoke-KmtPublishModuleTask -Path $Path

        .Notes

    #>

    [cmdletbinding()]
    param(

    )


    try
    {
        $Destination = (Get-KmtBuildVariable).Description
        $PSRepository = (Get-KmtBuildVariable).PSRepository

        if ( $ENV:BHBuildSystem -ne 'Unknown' -and
            $ENV:BHBranchName -eq "master" -and
            -not [string]::IsNullOrWhiteSpace($ENV:nugetapikey))
        {
            $publishModuleSplat = @{
                Path        = $Destination
                NuGetApiKey = $ENV:nugetapikey
                Verbose     = $true
                Force       = $true
                Repository  = $PSRepository
                ErrorAction = 'Stop'
            }
            Write-Verbose "Files in module output:"
            Get-ChildItem $Destination -Recurse -File |
                Select-Object -Expand FullName

            Write-Verbose "Publishing [$Destination] to [$PSRepository]"

            Publish-Module @publishModuleSplat
        }
        else
        {
            Write-Verbose ("Skipping deployment: To deploy, ensure that...`n" +
            "`t* You are in a known build system (Current: $ENV:BHBuildSystem)`n" +
            "`t* You are committing to the master branch (Current: $ENV:BHBranchName) `n" +
            "`t* The repository APIKey is defined in `$ENV:nugetapikey (Current: $(![string]::IsNullOrWhiteSpace($ENV:nugetapikey))) `n" +
            "`t* This is not a pull request")
        }
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError( $PSItem )
    }

}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KmtPublishVersionTask.ps1
function Invoke-KmtPublishVersionTask
{
    <#
        .Synopsis
        Publishes the version back to the build system

        .Example
        Invoke-KmtPublishVersionTask -Path $Path

        .Notes

    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '')]
    [cmdletbinding()]
    param(

    )


    try
    {
        $manifestPath = (Get-KmtBuildVariable).ManifestPath
        [version] $sourceVersion = (Get-Metadata -Path $manifestPath -PropertyName 'ModuleVersion')
        Write-Host "##vso[build.updatebuildnumber]$sourceVersion"

        # Do the same for appveyor
        # https://www.appveyor.com/docs/build-worker-api/#update-build-details
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError( $PSItem )
    }

}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KmtSemVerTask.ps1

function Invoke-KmtSemVerTask
{
    <#
        .Synopsis
        Calculates the SemVersion and instersts it into the manifest file
        .Example
        Invoke-KmtSemVerTask

        .Notes

    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(

        #$SourcePath,
        #[parameter(Mandatory)]
        #$ManifestPath,
        #$ModuleName,
        #$Repository = 'PSGallery'
    )

    $ManifestPath = (Get-KmtBuildVariable).ManifestPath
    $ModuleName = (Get-KmtBuildVariable).ModuleName

    try
    {
        if ($PSCmdlet.ShouldProcess($ManifestPath))
        {
            $output = (Get-KmtBuildVariable).Output

            $version = [version]"0.1.0"
            $publishedModule = $null
            $bumpVersionType = 'Patch'
            $versionStamp = (git rev-parse origin/master) + (git rev-parse head)

            Write-Verbose "Load current version from [$manifestPath]"
            [version] $sourceVersion = (Get-Metadata -Path $manifestPath -PropertyName 'ModuleVersion')
            Write-Verbose " Source version [$sourceVersion]"

            $downloadFolder = Join-Path -Path $output downloads
            $null = New-Item -ItemType Directory -Path $downloadFolder -Force -ErrorAction Ignore

            $versionFile = Join-Path $downloadFolder versionfile
            if (Test-Path $versionFile)
            {
                $versionFileData = Get-Content $versionFile -raw
                if ($versionFileData -eq $versionStamp)
                {
                    return
                }
            }

            Write-Verbose "Checking for published version"
            $publishedModule = Find-Module -Name $ModuleName -ErrorAction 'Ignore' |
                Sort-Object -Property { [version]$_.Version } -Descending |
                Select -First 1

            if ($null -ne $publishedModule)
            {
                [version] $publishedVersion = $publishedModule.Version
                Write-Verbose " Published version [$publishedVersion]"

                $version = $publishedVersion

                Write-Verbose "Downloading published module to check for breaking changes"
                $publishedModule | Save-Module -Path $downloadFolder

                [HashSet[string]] $publishedInterface =
                @(Get-ModulePublicInterfaceMap -Path (Join-Path $downloadFolder $ModuleName))
                [HashSet[string]] $buildInterface =
                @(Get-ModulePublicInterfaceMap -Path $ManifestPath)

                if (-not $publishedInterface.IsSubsetOf($buildInterface))
                {
                    $bumpVersionType = 'Major'
                }
                elseif ($publishedInterface.count -ne $buildInterface.count)
                {
                    $bumpVersionType = 'Minor'
                }
            }

            if ($version -lt ([version] '1.0.0'))
            {
                Write-Verbose "Module is still in beta; don't bump major version."
                if ($bumpVersionType -eq 'Major')
                {
                    $bumpVersionType = 'Minor'
                }
                else
                {
                    $bumpVersionType = 'Patch'
                }
            }

            Write-Verbose " Steping version [$bumpVersionType]"
            $version = [version] (Step-Version -Version $version -Type $bumpVersionType)

            Write-Verbose " Comparing to source version [$sourceVersion]"
            if ($sourceVersion -gt $version)
            {
                Write-Verbose " Using existing version"
                $version = $sourceVersion
            }

            if ( -not [string]::IsNullOrEmpty( $env:Build_BuildID ) )
            {
                $build = $env:Build_BuildID
                $version = [version]::new($version.Major, $version.Minor, $version.Build, $build)
            }
            elseif ( -not [string]::IsNullOrEmpty( $env:APPVEYOR_BUILD_ID ) )
            {
                $build = $env:APPVEYOR_BUILD_ID
                $version = [version]::new($version.Major, $version.Minor, $version.Build, $build)
            }

            Write-Verbose " Setting version [$version]"
            Update-Metadata -Path $ManifestPath -PropertyName 'ModuleVersion' -Value $version

            (Get-Content -Path $ManifestPath -Raw -Encoding UTF8) |
                ForEach-Object { $_.TrimEnd() } |
                Set-Content -Path $ManifestPath -Encoding UTF8

            Set-Content -Path $versionFile -Value $versionStamp -NoNewline -Encoding UTF8

        }
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError( $PSItem )
    }

    # This cleans up files from previous implementation
    $BuildRoot = (Get-KmtBuildVariable).BuildRoot
    if (Test-Path $BuildRoot\fingerprint)
    {
        Remove-Item $BuildRoot\fingerprint
    }

}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KmtUpdateSourceTask.ps1
function Invoke-KmtUpdateSourceTask
{
    <#
        .Synopsis
        Moves manifest changes back into source

        .Example
        Invoke-KmtUpdateSourceTask

        .Notes

    #>

    [cmdletbinding()]
    param(

    )

    end
    {
        try
        {
            $source = (Get-KmtBuildVariable).Source
            $manifestPath = (Get-KmtBuildVariable).ManifestPath
            $moduleName = (Get-KmtBuildVariable).moduleName

            Copy-Item -Path $ManifestPath -Destination "$Source\$ModuleName.psd1"

            $content = Get-Content -Path "$Source\$ModuleName.psd1" -Raw -Encoding UTF8
            $content.Trim() | Set-Content -Path "$Source\$ModuleName.psd1" -Encoding UTF8
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./KMT.ModuleBuilder/Private/Tasks/Invoke-KtmUninstallModule.ps1
function Invoke-KtmUninstallModule
{
    <#
        .Synopsis
        Uninstalls this module

        .Example
        Invoke-KtmUninstallModule -Path $Path

        .Notes

    #>

    [cmdletbinding()]
    param(
        # Parameter help description
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $Path
    )


    try
    {
        $moduleName = (Get-KmtBuildVariable).ModuleName

        Write-Verbose 'Unloading Modules...'
        Get-Module -Name $ModuleName -ErrorAction 'Ignore' | Remove-Module -Force

        Write-Verbose 'Uninstalling Module packages...'
        $modules = Get-Module $ModuleName -ErrorAction 'Ignore' -ListAvailable
        foreach ($module in $modules)
        {
            Uninstall-Module -Name $module.Name -RequiredVersion $module.Version -ErrorAction 'Ignore'
        }

        Write-Verbose 'Cleaning up manually installed Modules...'
        $path = $env:PSModulePath.Split(';').Where( {
                $_ -like 'C:\Users\*'
            }, 'First', 1)

        $path = Join-Path -Path $path -ChildPath $ModuleName
        if ($path -and (Test-Path -Path $path))
        {
            Write-Verbose 'Removing files... (This may fail if any DLLs are in use.)'
            Get-ChildItem -Path $path -File -Recurse |
                Remove-Item -Force | ForEach-Object 'FullName'

                Write-Verbose 'Removing folders... (This may fail if any DLLs are in use.)'
            Remove-Item $path -Recurse -Force
        }
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError( $PSItem )
    }
}



# Importing from [/home/vsts/work/1/s/KMT.ModuleBuilder\Public]
# ./KMT.ModuleBuilder/Public/Build-KmtModule.ps1
function Build-KmtModule
{
    <#
        .Synopsis
        Executes all the build actions for a module

        .Example
        Build-KmtModule -Path $Path

        .Notes

    #>

    [cmdletbinding()]
    param(
        [Alias('FullName')]
        [Parameter(
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [string]
        $Path = (Get-Location)
    )


    try
    {
        #$Script:ModuleName = Get-ChildItem .\*\*.psm1 | Select-object -ExpandProperty BaseName
        #$Script:CodeCoveragePercent = 0.0 # 0 to 1

        Initialize-KmtModuleProject -Path $Path
        $init = Get-KmtBuildVariable
        foreach($key in $init.Keys)
        {
            Write-Verbose " $key [$($init[$key])]" -Verbose
        }
        #Build
        Write-Verbose 'Copy'
        Invoke-KmtCopyTask
        Write-Verbose 'Compile'
        Invoke-KmtDotNetCompileTask
        Write-Verbose 'BuildModule'
        Invoke-KmtBuildModuleTask
        Write-Verbose 'BuildManifest'
        Invoke-KmtBuildManifestTask
        Write-Verbose 'SetVersion'
        Invoke-KmtSemVerTask
        Write-Verbose 'GenerateMarkdown'
        Invoke-KmtGenerateMarkdown
        Write-Verbose 'GenerateHelp'
        Invoke-KmtGenerateHelpTask
        Write-Verbose 'ImportModule'
        Invoke-KmtImportBuiltModuleTask
        Write-Verbose 'Analyze'
        Invoke-KmtAnalyzeTask
        Write-Verbose 'Pester'
        Invoke-KmtPesterTask
        Write-Verbose 'UpdateSource'
        Invoke-KmtUpdateSourceTask
    }
    catch
    {
        #Write-Error -ErrorRecord $PSItem -ErrorAction Stop
        $PSCmdlet.ThrowTerminatingError( $PSItem )
    }

}

# ./KMT.ModuleBuilder/Public/Import-KmtModule.ps1
function Import-KmtModule
{
    <#
        .SYNOPSIS
        Unloads existing module before importing from a path

        .Example
        Import-KmtModule -Path $Path
    #>

    param(
        [string]$path,
        [switch]$PassThru
    )

    if (-not(Test-Path -Path $path))
    {
        Write-Verbose "Cannot find [$path]."
        Write-Error -Message "Could not find module manifest [$path]"
    }
    else
    {
        $file = Get-Item $path
        $name = $file.BaseName

        $loaded = Get-Module -Name $name -All -ErrorAction Ignore
        if ($loaded)
        {
            Write-Verbose "Unloading Module [$name] from a previous import..."
            $loaded | Remove-Module -Force
        }

        Write-Verbose "Importing Module [$name] from [$($file.fullname)]..."
        $splat = @{
            Name = $file.fullname
            Force = $true
            PassThru = $PassThru
            Scope = 'Global'
            Verbose = $false
        }
        Import-Module @splat
    }
}

# ./KMT.ModuleBuilder/Public/Reset-KtmModule.ps1
function Reset-KtmModule
{
    <#
        .Synopsis
        Clears the build output for the module

        .Example
        Reset-KtmModule -Path $Path

        .Notes

    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
    [Alias('Clean-KtmModule')]
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [Alias('FullName')]
        [Parameter(
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $Path = (Get-Location)
    )

    process
    {
        try
        {
            foreach($folder in $Path)
            {
                Initialize-KmtModuleProject -Path $Folder
                Invoke-KmtCleanTask
            }

        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }

    end
    {

    }
}