PSBuilder.psm1

Set-StrictMode -Version Latest
$ErrorActionPreference='Stop'

##### BEGIN Convert-Dependency.ps1 #####
#.ExternalHelp PSBuilder-Help.xml
function Convert-Dependency
{
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [object]$InputObject,

        [string]$Repository
    )

    if ($InputObject -is [string])
    {
        $dependency = @{ Name = $InputObject }
    }
    elseif ($InputObject -is [hashtable])
    {
        $dependency = $InputObject
    }
    else
    {
        throw "Unknown dependency type $($InputObject.GetType()). Dependency must be either string or hashtable."
    }

    if (-not $dependency.ContainsKey("External"))
    {
        $dependency.External = $false
    }

    if (-not [string]::IsNullOrEmpty($Repository) -and -not $dependency.ContainsKey("Repository"))
    {
        $dependency.Repository = $Repository
    }

    $dependency
}

##### END Convert-Dependency.ps1 #####


##### BEGIN Exit-Powershell.ps1 #####
#.ExternalHelp PSBuilder-Help.xml
function Exit-Powershell {
    param ([int]$ExitCode=0)

    exit $ExitCode
 }
##### END Exit-Powershell.ps1 #####


##### BEGIN New-DataFile.ps1 #####
#.ExternalHelp PSBuilder-Help.xml
function ReplaceString($str)
{
    return "'" + ($str -Replace "'","''") + "'"
}

function ProcessValue($InputObject)
{
  if ($InputObject -is [string])
  {
        return ReplaceString $InputObject
  }
  elseif ($InputObject -is [array])
  {
      if ($InputObject.Count -eq 0)
      {
            return '@()'
      }
      elseif ($InputObject.Count -eq 1 -and $InputObject[0] -isnot [hashtable] -and $InputObject[0] -isnot [System.Collections.Specialized.OrderedDictionary])
      {
            return "@($((ProcessValue $InputObject[0]) -replace "`r`n", "`r`n`t"))"
      }
      else
      {
          $arrayBuilder = [System.Text.StringBuilder]::new()
          [void]$arrayBuilder.AppendLine("(")
          for ($i=0; $i -lt $InputObject.Count; $i++)
          {
              $suffix = if ($i+1 -eq $InputObject.Count) { "" } else { "," }
              $value = (ProcessValue $InputObject[$i]) -replace "`r`n", "`r`n`t"
              $value = $value -replace "`r`n`t$", ""
              [void]$arrayBuilder.AppendLine("`t$value$suffix")
          }
          [void]$arrayBuilder.AppendLine(")")
          return $arrayBuilder.ToString()
      }
  }
  elseif ($InputObject -is [hashtable] -or $InputObject -is [System.Collections.Specialized.OrderedDictionary])
  {
        $hashtableBuilder = [System.Text.StringBuilder]::new()
        [void]$hashtableBuilder.AppendLine("@{")
        foreach ($key in @($InputObject.Keys))
        {
            $value = (ProcessValue $InputObject[$key]) -replace "`r`n", "`r`n`t"
            $value = $value -replace "`r`n`t$", ""
            [void]$hashtableBuilder.AppendLine("`t$key = $value")
        }
        [void]$hashtableBuilder.AppendLine("}")
        return $hashtableBuilder.ToString()
  }
  elseif ($InputObject -is [bool])
  {
        if ($InputObject)
        {
            return '$true'
        }
        else
        {
            return '$false'
        }
  }
  else
  {
        throw "Unknown type: $($InputObject.GetType())"
  }
}

function New-DataFile
{
  param (
    [Parameter(Mandatory=$true)]
    [string]$Path,

    [Parameter(Mandatory=$true)]
    [object]$Data
  )

  $value = ProcessValue $Data
  [IO.File]::WriteAllText($Path, $value)
}
##### END New-DataFile.ps1 #####


##### BEGIN Invoke-CompileModule.ps1 #####
#.ExternalHelp PSBuilder-Help.xml
function Invoke-CompileModule
{
    param (
        [Parameter(Mandatory=$true)]
        [string]$Name,

        [Parameter(Mandatory=$true)]
        [string]$Source,

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

    if (-not [IO.Path]::IsPathRooted($Source)) { $Source = Resolve-Path -Path $Source }
    if (-not [IO.Path]::IsPathRooted($Destination)) { $Destination = [IO.Path]::GetFullPath($Destination) }

    $buildFolders = ("Classes", "Private", "Public")
    $SourceFile = Join-Path -Path $Source -ChildPath "$Name.psm1"
    if (Test-Path -Path $SourceFile)
    {
        Copy-Item -Path $SourceFile -Destination $Destination

        foreach ($buildFolder in $buildFolders)
        {
            $path = Join-Path -Path $Source -ChildPath $buildFolder
            if (Test-Path -Path $path)
            {
                Copy-Item -Path $path -Destination $BuildOutput -Recurse -Container -Force
            }
        }
    }
    else
    {
        $publicFolder = Join-Path -Path $Source -ChildPath "Public"
        $publicFunctions = @(Get-ChildItem -Path $publicFolder -Filter "*.ps1" -Recurse).ForEach({ $_.BaseName })

        $builder = [System.Text.StringBuilder]::new()
        [void]$builder.AppendLine("Set-StrictMode -Version Latest")
        [void]$builder.AppendLine("`$ErrorActionPreference='Stop'")

        foreach ($buildFolder in $buildFolders)
        {
            $path = Join-Path -Path $Source -ChildPath $buildFolder
            if (-not (Test-Path -Path $path)) { continue }
            $files = Get-ChildItem -Path $path -Filter "*.ps1" -Recurse

            foreach ($file in $files)
            {
                $content = Get-Content -Path $file.FullName -Raw
                [void]$builder.AppendLine("")
                [void]$builder.AppendLine("##### BEGIN $($file.Name) #####")
                [void]$builder.AppendLine("#.ExternalHelp $Name-Help.xml")
                [void]$builder.AppendLine($content)
                [void]$builder.AppendLine("##### END $($file.Name) #####")
                [void]$builder.AppendLine("")
            }
        }

        if ($publicFunctions.Count -gt 0)
        {
            [void]$builder.AppendLine("Export-ModuleMember -Function @($($publicFunctions.ForEach({ "'$_'" }) -join ", "))")
        }

        [IO.File]::WriteAllText($Destination, $builder.ToString())
    }
}
##### END Invoke-CompileModule.ps1 #####


##### BEGIN Invoke-CreateModuleManifest.ps1 #####
#.ExternalHelp PSBuilder-Help.xml
function Invoke-CreateModuleManifest
{
    param (
        [Parameter(Mandatory=$true)]
        [string]$Name,

        [Parameter(Mandatory=$true)]
        [string]$Guid,

        [Parameter(Mandatory=$true)]
        [string]$Author,

        [Parameter(Mandatory=$true)]
        [string]$Description,

        [Parameter(Mandatory=$true)]
        [string]$Path,

        [Parameter(Mandatory=$true)]
        [string]$ModuleFilePath,

        [Parameter(Mandatory=$true)]
        [string]$Version,

        [string]$CompanyName = $null,
        [object[]]$Dependencies,
        [string]$LicenseUri = $null,
        [string]$IconUri = $null,
        [string]$ProjectUri = $null,
        [string]$ReleaseNotes = $null,
        [string]$HelpInfoUri = $null,
        [string[]]$CompatiblePSEditions = $null,
        [string]$PowerShellVersion = $null,
        [string]$PowerShellHostName = $null,
        [string]$PowerShellHostVersion = $null,
        [string]$DotNetFrameworkVersion = $null,
        [string]$CLRVersion = $null,
        [string]$ProcessorArchitecture = $null,
        [string[]]$RequiredAssemblies = $null,
        [string[]]$ScriptsToProcess = $null,
        [string[]]$TypesToProcess = $null,
        [string[]]$FormatsToProcess = $null,
        [string[]]$NestedModules = $null,
        [string]$DefaultCommandPrefix = $null,
        [string[]]$Tags = $null,
        [string]$Prerelease = $null,
        [System.Collections.Specialized.OrderedDictionary]$PSData = $null,
        [bool]$RequireLicenseAcceptance=$false
    )

    $module = Get-Module -Name $ModuleFilePath -ListAvailable
    $Exports = @{
        "Aliases" = @($module.ExportedAliases.Keys)
        "Cmdlets" = @($module.ExportedCmdlets.Keys)
        "DscResources" = @($module.ExportedDscResources)
        "Functions" = @($module.ExportedFunctions.Keys)
        "Variables" = @($module.ExportedVariables.Keys)
    }

    $GalleryDependencies = [System.Collections.ArrayList]::new()
    $ExternalDependencies = [System.Collections.ArrayList]::new()

    foreach ($dependencyLine in $Dependencies)
    {
        $dependency = Convert-Dependency -InputObject $dependencyLine

        if ($dependency.External)
        {
            [void]$ExternalDependencies.Add($dependency.Name)
        }

        if ($dependency.ContainsKey("Repository")) { $dependency.Remove("Repository") }
        if ($dependency.ContainsKey("MinimumVersion"))
        {
            $dependency["ModuleVersion"] = $dependency["MinimumVersion"]
            $dependency.Remove("MinimumVersion")
        }
        $dependency["ModuleName"] = $dependency["Name"]
        $dependency.Remove("Name")
        $dependency.Remove("External")

        [void]$GalleryDependencies.Add($dependency)
    }

    $ManifestArguments = [ordered]@{
        "RootModule" = "$Name.psm1"
        "ModuleVersion" = $Version
        "GUID" = $Guid
        "Author" = $Author
        "CompanyName" = "Unknown"
        "Copyright" = "(c) $((Get-Date).Year) $Author. All rights reserved."
        "Description" = $Description
    }

    if (-not [string]::IsNullOrEmpty($CompanyName))
    {
        $ManifestArguments.CompanyName = $CompanyName
    }

    if ($null -ne $CompatiblePSEditions -and $CompatiblePSEditions.Count -gt 0)
    {
        $ManifestArguments.CompatiblePSEditions = $CompatiblePSEditions
    }

    if (-not [string]::IsNullOrEmpty($PowerShellVersion))
    {
        $ManifestArguments.PowerShellVersion = $PowerShellVersion
    }

    if (-not [string]::IsNullOrEmpty($PowerShellHostName))
    {
        $ManifestArguments.PowerShellHostName = $PowerShellHostName
    }

    if (-not [string]::IsNullOrEmpty($PowerShellHostVersion))
    {
        $ManifestArguments.PowerShellHostVersion = $PowerShellHostVersion
    }

    if (-not [string]::IsNullOrEmpty($DotNetFrameworkVersion))
    {
        $ManifestArguments.DotNetFrameworkVersion = $DotNetFrameworkVersion
    }

    if (-not [string]::IsNullOrEmpty($CLRVersion))
    {
        $ManifestArguments.CLRVersion = $CLRVersion
    }

    if (-not [string]::IsNullOrEmpty($ProcessorArchitecture))
    {
        $ManifestArguments.ProcessorArchitecture = $ProcessorArchitecture
    }

    if ($GalleryDependencies.Count -gt 0)
    {
        $ManifestArguments.RequiredModules = $GalleryDependencies.ToArray()
    }

    if ($null -ne $RequiredAssemblies -and $RequiredAssemblies.Count -gt 0)
    {
        $ManifestArguments.RequiredAssemblies = $RequiredAssemblies
    }

    if ($null -ne $ScriptsToProcess -and $ScriptsToProcess.Count -gt 0)
    {
        $ManifestArguments.ScriptsToProcess = $ScriptsToProcess
    }

    if ($null -ne $TypesToProcess -and $TypesToProcess.Count -gt 0)
    {
        $ManifestArguments.TypesToProcess = $TypesToProcess
    }

    if ($null -ne $FormatsToProcess -and $FormatsToProcess.Count -gt 0)
    {
        $ManifestArguments.FormatsToProcess = $FormatsToProcess
    }

    if ($null -ne $NestedModules -and $NestedModules.Count -gt 0)
    {
        $ManifestArguments.NestedModules = $NestedModules
    }

    if ($Exports.Functions.Count -gt 0)
    {
        $ManifestArguments.FunctionsToExport = $Exports.Functions
    }

    if ($Exports.Cmdlets.Count -gt 0)
    {
        $ManifestArguments.CmdletsToExport = $Exports.Cmdlets
    }

    if ($Exports.Variables.Count -gt 0)
    {
        $ManifestArguments.VariablesToExport = $Exports.Variables
    }

    if ($Exports.Aliases.Count -gt 0)
    {
        $ManifestArguments.AliasesToExport = $Exports.Aliases
    }

    if ($Exports.DscResources.Count -gt 0)
    {
        $ManifestArguments.DscResourcesToExport = $Exports.DscResources
    }

    if ($null -ne $Tags -and $Tags.Count -gt 0)
    {
        $ManifestArguments.Tags = $Tags
    }

    $ManifestArguments.PrivateData = [ordered]@{ "PSData" = [ordered]@{} }
    if (-not [string]::IsNullOrEmpty($LicenseUri))
    {
        $ManifestArguments.PrivateData.PSData.LicenseUri = $LicenseUri
    }

    if (-not [string]::IsNullOrEmpty($ProjectUri))
    {
        $ManifestArguments.PrivateData.PSData.ProjectUri = $ProjectUri
    }

    if (-not [string]::IsNullOrEmpty($IconUri))
    {
        $ManifestArguments.PrivateData.PSData.IconUri = $IconUri
    }

    if (-not [string]::IsNullOrEmpty($ReleaseNotes))
    {
        $ManifestArguments.PrivateData.PSData.ReleaseNotes = $ReleaseNotes
    }

    if (-not [string]::IsNullOrEmpty($Prerelease))
    {
        $ManifestArguments.PrivateData.PSData.Prerelease = $Prerelease
    }

    if ($RequireLicenseAcceptance)
    {
        $ManifestArguments.PrivateData.PSData.RequireLicenseAcceptance = $true
    }

    if ($ExternalDependencies.Count -gt 0)
    {
        $ManifestArguments.PrivateData.PSData.ExternalModuleDependencies = $ExternalDependencies.ToArray()
    }

    if (-not [string]::IsNullOrEmpty($HelpInfoUri))
    {
        $ManifestArguments.HelpInfoUri = $HelpInfoUri
    }

    if (-not [string]::IsNullOrEmpty($DefaultCommandPrefix))
    {
        $ManifestArguments.DefaultCommandPrefix = $DefaultCommandPrefix
    }

    if ($null -ne $PSData -and $PSData.Count -gt 0)
    {
        foreach ($key in $PSData.Keys)
        {
            $ManifestArguments.PrivateData.PSData.Add($key, $PSData[$key])
        }
    }

    New-DataFile -Path $Path -Data $ManifestArguments
}

##### END Invoke-CreateModuleManifest.ps1 #####


##### BEGIN Invoke-GenerateSelfSignedCert.ps1 #####
#.ExternalHelp PSBuilder-Help.xml
function Invoke-GenerateSelfSignedCert
{
    $AvailableCerts = @(Get-ChildItem -Path "Cert:\CurrentUser" -CodeSigningCert -Recurse).Where({ $_.Subject -eq "CN=Test Code Signing Certificate" -and (Test-Certificate -AllowUntrustedRoot -Cert $_) })
    $Certificate = $AvailableCerts | Sort-Object -Descending -Property NotAfter | Select-Object -First 1

    if ($null -eq $Certificate)
    {
        $CertArgs = @{
            Subject = "CN=Test Code Signing Certificate"
            KeyFriendlyName = "Test Code Signing Certificate"
            CertStoreLocation = "Cert:\CurrentUser"
            KeyAlgorithm = "RSA"
            KeyLength = 4096
            Provider = "Microsoft Enhanced RSA and AES Cryptographic Provider"
            KeyExportPolicy = "NonExportable"
            KeyUsage = "DigitalSignature"
            Type = "CodeSigningCert"
            Verbose = $VerbosePreference
        }

        $Certificate = New-SelfSignedCertificate @CertArgs
        "Generated $Certificate"
    }

    $RootPath = "Cert:LocalMachine\Root"
    $TrustedRootEntry = @(Get-ChildItem -Path $RootPath -Recurse).Where({ $_.Thumbprint -eq $Certificate.Thumbprint }) | Select-Object -First 1
    if ($null -eq $TrustedRootEntry)
    {
        $ExportPath = Join-Path -Path $Env:TEMP -ChildPath "cert.crt"
        Export-Certificate -Type CERT -FilePath $ExportPath -Cert $Certificate -Force | Out-Null
        Import-Certificate -FilePath $ExportPath -CertStoreLocation $RootPath | Out-Null
        Remove-Item -Path $ExportPath

        "Copied $Certificate to trusted root"
    }
}
##### END Invoke-GenerateSelfSignedCert.ps1 #####


##### BEGIN Invoke-Sign.ps1 #####
#.ExternalHelp PSBuilder-Help.xml
function Invoke-Sign
{
    param (
        [Parameter(Mandatory=$false)]
        [string]$CertificateThumbprint,

        [Parameter(Mandatory=$false)]
        [string]$CertificateSubject,

        [string]$CertificatePath,

        [securestring]$CertificatePassword,

        [string]$Name,

        [string]$Path,

        [string]$HashAlgorithm
    )

    if ($CertificatePath -like "Cert:*")
    {
        $Certificates = @(Get-ChildItem -Path $CertificatePath -CodeSigningCert)
    }
    else
    {
        $Certificates = @([System.Security.Cryptography.X509Certificates.X509Certificate2]::new($CertificatePath, $CertificatePassword))
    }

    if (-not [string]::IsNullOrEmpty($CertificateThumbprint))
    {
        $Certificates = $Certificates.Where({ $_.Thumbprint -eq $CertificateThumbprint })
    }
    elseif (-not [string]::IsNullOrEmpty($CertificateSubject))
    {
        $Certificates = $Certificates.Where({ $_.Subject -eq $CertificateSubject })
    }

    $Script:Certificate = @($Certificates).Where({ $_.HasPrivateKey -and $_.NotAfter -gt (Get-Date) }) | Sort-Object -Descending -Property NotAfter | Select-Object -First 1
    if ($null -eq $Certificate)
    {
        throw "$($Certificates.Count) code signing certificates were found but none are valid."
    }

    $files = @(Get-ChildItem -Path $Path -Recurse -Include $ExtensionsToSign).ForEach({ $_.FullName })

    foreach ($file in $files) {
        $setAuthSigParams = @{
            FilePath = $file
            Certificate = $certificate
            HashAlgorithm = $HashAlgorithm
            Verbose = $VerbosePreference
        }

        $result = Set-AuthenticodeSignature @setAuthSigParams
        if ($result.Status -ne 'Valid') {
            throw "Failed to sign: $file. Status: $($result.Status) $($result.StatusMessage)"
        }

        "Successfully signed: $file"
    }

    $catalogFile = "$Path\$Name.cat"
    $catalogParams = @{
        Path = $Path
        CatalogFilePath = $catalogFile
        CatalogVersion = 2.0
        Verbose = $VerbosePreference
    }
    New-FileCatalog @catalogParams | Out-Null

    $catalogSignParams = @{
        FilePath = $catalogFile
        Certificate = $certificate
        HashAlgorithm = $HashAlgorithm
        Verbose = $VerbosePreference
    }
    $result = Set-AuthenticodeSignature @catalogSignParams
    if ($result.Status -ne 'Valid') {
        throw "Failed to sign the catalog file. Status: $($result.Status) $($result.StatusMessage)"
    }
}
##### END Invoke-Sign.ps1 #####


##### BEGIN Invoke-CreateHelp.ps1 #####
#.ExternalHelp PSBuilder-Help.xml
function Invoke-CreateHelp
{
    param (
        [Parameter(Mandatory=$true)]
        [string]$Source,

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

        [string]$Language="en-US"
    )

    $destinationPath = Join-Path -Path $Destination -ChildPath $Language
    New-ExternalHelp -Path $Source -OutputPath $destinationPath -Force | Out-Null
}
##### END Invoke-CreateHelp.ps1 #####


##### BEGIN Invoke-CreateMarkdown.ps1 #####
#.ExternalHelp PSBuilder-Help.xml
function Invoke-CreateMarkdown
{
    param (
        [Parameter(Mandatory=$true)]
        [string]$Manifest,

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

    $module = Import-Module -Name $Manifest -Global -Force -PassThru
    if (-not (Test-Path -Path $Path))
    {
        New-Item -Path $Path -ItemType Directory -Force | Out-Null
    }

    $moduleFile = Join-Path -Path $Path -ChildPath "$($module.Name).md"
    if (-not (Test-Path -Path $moduleFile))
    {
        New-MarkdownHelp -Module $($module.Name) -OutputFolder $Path -WithModulePage | Out-Null
    }

    Update-MarkdownHelpModule -Path $Path -RefreshModulePage -Force | Out-Null
}
##### END Invoke-CreateMarkdown.ps1 #####


##### BEGIN Invoke-PublishToRepository.ps1 #####
#.ExternalHelp PSBuilder-Help.xml
function Invoke-PublishToRepository
{
    param (
        [Parameter(Mandatory=$true)]
        [string]$Path,

        [Parameter(Mandatory=$false)]
        [string]$Repository,

        [Parameter(Mandatory=$false)]
        [string]$NugetApiKey
    )

    $publishParams = @{ "Path" = $Path }
    if (-not [string]::IsNullOrEmpty($Repository)) { $publishParams["Repository"] = $Repository }
    if (-not [string]::IsNullOrEmpty($NugetApiKey)) { $publishParams["NuGetApiKey"] = $NugetApiKey }

    Publish-Module @publishParams -ErrorAction Stop -Force
}

##### END Invoke-PublishToRepository.ps1 #####


##### BEGIN Invoke-CodeAnalysis.ps1 #####
#.ExternalHelp PSBuilder-Help.xml
function Invoke-CodeAnalysis
{
    param (
        [Parameter(Mandatory=$true)]
        [string]$Path,

        [string]$FailureLevel,

        [string]$SettingsFile,

        [string]$ResultsFile,

        [string]$SummaryFile
    )

    $analysisParameters = @{}
    if (-not [string]::IsNullOrEmpty($SettingsFile) -and (Test-Path -Path $SettingsFile)) { $analysisParameters["Settings"] = $SettingsFile }
    $analysisResult = Invoke-ScriptAnalyzer -Path $Path -Recurse @analysisParameters
    $analysisResult | Format-Table -AutoSize | Out-String | Tee-Object -FilePath $SummaryFile
    ($analysisResult | ConvertTo-Xml -NoTypeInformation -Depth 3).Save($ResultsFile)

    $warnings = $analysisResult.Where({ $_.Severity -eq "Warning" -or $_.Severity -eq "Warning" }).Count
    $errors = $analysisResult.Where({ $_.Severity -eq "Error" }).Count
    "Script analyzer triggered {0} warnings and {1} errors" -f $warnings, $errors

    if ($FailureLevel -eq "Warning")
    {
        Assert ($warnings -eq 0 -and $errors -eq 0) "Build failed due to warnings or errors found in analysis."
    }
    elseif ($FailureLevel -eq "Error")
    {
        Assert ($errors -eq 0) "Build failed due to errors found in analysis."
    }
}
##### END Invoke-CodeAnalysis.ps1 #####


##### BEGIN Invoke-PesterTest.ps1 #####
#.ExternalHelp PSBuilder-Help.xml
function Invoke-PesterTest
{
    param (
        [string[]]$Tags = @(),
        [string]$Path,
        [string]$Module,
        [string]$OutputPath,
        [string]$CoverageOutputPath,
        [string]$CoverageSummaryPath,
        [int]$MinCoverage=0,
        [bool]$AllowFailingTests=$false
    )

    if ($Tags -eq "*") { $Tags = @() }

    if (-not (Test-Path -Path $Path))
    {
        $testCoverage = 0
    }
    else
    {
        Set-Location -Path $Path
        Import-Module -Name $Module -Force
        $files = @(Get-ChildItem -Path ([IO.Path]::GetDirectoryName($Module)) -Include "*.ps1","*.psm1" -File -Recurse)
        $pesterArgs = @{
            CodeCoverage = $files
            Tag = $Tags
            OutputFile = $OutputPath
            OutputFormat = "NUnitXml"
            CodeCoverageOutputFile = $CoverageOutputPath
            CodeCoverageOutputFileFormat = "JaCoCo"
            PassThru = $true
        }
        $testResult = Invoke-Pester @pesterArgs

        if (-not $AllowFailingTests)
        {
            assert ($testResult.FailedCount -eq 0) ('Failed {0} Unit tests. Aborting Build' -f $testResult.FailedCount)
        }

        if (0 -eq $testResult.CodeCoverage.NumberOfCommandsAnalyzed)
        {
            $testCoverage = 0
            $codeCoverageSummary = ""
        }
        else
        {
            $testCoverage = [int]($testResult.CodeCoverage.NumberOfCommandsExecuted / $testResult.CodeCoverage.NumberOfCommandsAnalyzed * 100)
            $codeCoverageSummary = $testResult.CodeCoverage.MissedCommands | Format-Table -AutoSize @{Name="File";Expr={ [IO.Path]::GetFileName($_.File) }}, Function, Line, Command | Out-String
        }


        [IO.File]::WriteAllText($CoverageSummaryPath, "Code coverage: ${testCoverage}%`r`n`r`n$codeCoverageSummary")
    }

    Write-Output "Code coverage: ${testCoverage}%"

    assert ($MinCoverage -le $testCoverage) ('Code coverage must be higher or equal to {0}%. Current coverage: {1}%' -f ($MinCoverage, $testCoverage))
}

##### END Invoke-PesterTest.ps1 #####


##### BEGIN Invoke-Builder.ps1 #####
#.ExternalHelp PSBuilder-Help.xml
function Invoke-Builder
{
    [CmdletBinding(DefaultParameterSetName="Default")]
    param (
        [Parameter(Position=0, ValueFromRemainingArguments=$true)]
        [string[]]$Tasks,

        [Parameter(Mandatory=$false)]
        [hashtable]$Parameters = $null,

        [switch]$ExitOnError
    )

    $buildRoot = (Get-Location).Path
    $buildFile = "$PSScriptRoot/files/build.tasks.ps1"
    $buildScript = Get-Command -Name $buildFile

    $buildParameters = @{ "BuildRoot" = $buildRoot }
    (Get-ChildItem -Path "Env:").Where({ $_.Name -like "PSBuilder*" }).ForEach({ $buildParameters[$_.Name.Substring(9)] = $_.Value })
    if ($null -ne $Parameters) { $buildParameters += $Parameters }

    foreach ($parameter in @($buildParameters.Keys))
    {
        if (-not $buildScript.Parameters.ContainsKey($parameter))
        {
            throw "Unknown parameter: $parameter"
        }

        $buildParameterType = $buildScript.Parameters[$parameter].ParameterType
        $currentValue = $buildParameters[$parameter]
        if ($buildParameterType -eq [string[]] -and $currentValue -is [string])
        {
            $buildParameters[$parameter] = $currentValue -split ","
        }
        elseif ($buildParameterType -eq [securestring])
        {
            $value = [securestring]::new()
            $currentValue.ToCharArray().ForEach({ $value.AppendChar($_) })
            $buildParameters[$parameter] = $value
        }
        elseif ($buildParameterType -eq [bool])
        {
            $buildParameters[$parameter] = [Convert]::ToBoolean($buildParameters[$parameter])
        }
        elseif ($buildParameterType -eq [int])
        {
            $buildParameters[$parameter] = [Convert]::ToInt32($buildParameters[$parameter])
        }
        elseif ($buildParameterType -eq [datetime])
        {
            $buildParameters[$parameter] = [Convert]::ToDateTime($buildParameters[$parameter])
        }
    }

    try
    {
        $failed = $false
        Invoke-Build -Task $Tasks -File $BuildFile -Result "result" @buildParameters
    }
    catch
    {
        $failed = $true
        if (-not $ExitOnError) { throw }
    }


    if ($failed -or $result.Errors.Count -gt 0)
    {
        if ($ExitOnError)
        {
            Exit-Powershell -ExitCode 1
        }
        else
        {
            throw "Build Failed..."
        }
    }
}
##### END Invoke-Builder.ps1 #####

Export-ModuleMember -Function @('Invoke-Builder')