plaster/ModuleBuild/scaffold/modulename.build.ps1

param (
    [parameter(Position = 0)]
    [string]$BuildFile = (Join-Path $BuildRoot 'build\<%=$PLASTER_PARAM_ModuleName%>.buildenvironment.ps1'),
    [parameter(Position = 1)]
    [version]$NewVersion = $null,
    [parameter(Position = 2)]
    [string]$ReleaseNotes,
    [parameter(Position = 3)]
    [switch]$Force
)

if (Test-Path $BuildFile) {
    . $BuildFile
}
else {
    throw "Without a build environment file we are at a loss as to what to do!"
}

# These are required for a full build process and will be automatically installed if they aren't available
$RequiredModules = @('PlatyPS', 'Pester')

# Some optional modules
if ($Script:BuildEnv.OptionAnalyzeCode) {
    $RequiredModules += 'PSScriptAnalyzer'
}

if ($Script:BuildEnv.OptionGenerateReadTheDocs) {
    $RequiredModules += 'Powershell-YAML'
}

# You really shouldn't change this for a powershell module (if you want it to publish to the psgallery correctly)
$CurrentReleaseFolder = $Script:BuildEnv.ModuleToBuild

# Put together our full paths. Generally leave these alone
$ModuleFullPath = Join-Path $BuildRoot "$($Script:BuildEnv.ModuleToBuild).psm1"
$ModuleManifestFullPath = Join-Path $BuildRoot "$($Script:BuildEnv.ModuleToBuild).psd1"
$BuildDocsPath = Join-Path $BuildRoot "$($Script:BuildEnv.BuildToolFolder)\docs\"
$ProjectDocsPath = Join-Path $BuildRoot 'docs'
$ScratchPath = Join-Path $BuildRoot $Script:BuildEnv.ScratchFolder
$ReleasePath = Join-Path $BuildRoot $Script:BuildEnv.BaseReleaseFolder
$CurrentReleasePath = Join-Path $ReleasePath $CurrentReleaseFolder

# Just before releasing the module we stage some changes in this location.
$StageReleasePath = Join-Path $ScratchPath $Script:BuildEnv.BaseReleaseFolder
$ReleaseModule = "$($StageReleasePath)\$($Script:BuildEnv.ModuleToBuild).psm1"

# Additional build scripts and tools are found here (note that any dot sourced functions must be scoped at the script level)
$BuildToolPath = Join-Path $BuildRoot $Script:BuildEnv.BuildToolFolder

# Used later to determine if we are in a configured state or not
$IsConfigured = $False

# Used to update our function CBH to external help reference
$ExternalHelp = @"
<#
    .EXTERNALHELP $($Script:BuildEnv.ModuleToBuild)-help.xml
    .LINK
        {{LINK}}
    #>
"@


if ($Script:BuildEnv.OptionTranscriptEnabled) {
    Write-Build White 'Transcript logging: TRUE'
    $TranscriptLog = Join-Path $BuildToolPath $Script:BuildEnv.OptionTranscriptLogFile
    Write-Build White "TranscriptLog: $($TranscriptLog)"
    Start-Transcript -Path $TranscriptLog -Append -WarningAction:SilentlyContinue
}

#Synopsis: Validate system requirements are met
task ValidateRequirements {
    Write-Build White ' Running Powershell version 5?'
    assert ($PSVersionTable.PSVersion.Major.ToString() -eq '5') 'Powershell 5 is required for this build to function properly (you can comment this assert out if you are able to work around this requirement)'
}

#Synopsis: Load required modules if available. Otherwise try to install, then load it.
task LoadRequiredModules {
    $RequiredModules | Foreach-Object {
        if ((get-module $_ -ListAvailable) -eq $null) {
            Write-Build White " Installing $($_) Module"
            $null = Install-Module $_ -Scope:CurrentUser
        }
        if (get-module $_ -ListAvailable) {
            Write-Build White " Importing $($_) Module"
            Import-Module $_ -Force
        }
        else {
            throw 'How did you even get here?'
        }
    }
}

#Synopsis: Load dot sourced functions into this build session
task LoadBuildTools {
    # Dot source any build script functions we need to use
    Get-ChildItem $BuildToolPath/dotSource -Recurse -Filter "*.ps1" -File | ForEach-Object {
        Write-Build White " Dot sourcing script file: $($_.Name)"
        . $_.FullName
    }
}

# Synopsis: Create new module manifest
task CreateModuleManifest -After CreateModulePSM1 {
    $PSD1OutputFile = "$($StageReleasePath)\$($Script:BuildEnv.ModuleToBuild).psd1"
    $ThisPSD1OutputFile = ".\$($Script:BuildEnv.ScratchFolder)\$($Script:BuildEnv.BaseReleaseFolder)\$($Script:BuildEnv.ModuleToBuild).psd1"
    Write-Build White " Attempting to update the release module manifest file: $ThisPSD1OutputFile"
    $null = Copy-Item -Path $ModuleManifestFullPath -Destination $PSD1OutputFile -Force
    Update-ModuleManifest -Path $PSD1OutputFile -FunctionsToExport $Script:FunctionsToExport
}

# Synopsis: Load the module project
task LoadModule {
    Write-Build White ' Attempting to load the project module.'
    try {
        $Script:Module = Import-Module $ModuleFullPath -Force -PassThru
    }
    catch {
        throw "Unable to load the project module: $($ModuleFullPath)"
    }
}

# Synopsis: Import the current module manifest file for processing
task LoadModuleManifest {
    assert (test-path $ModuleManifestFullPath) "Unable to locate the module manifest file: $ModuleManifestFullPath"
    Write-Build White ' Loading the existing module manifest for this module'
    $Script:Manifest = Test-ModuleManifest -Path $ModuleManifestFullPath
}

# Synopsis: Valiates there is no version mismatch.
task Version LoadModuleManifest, {
    Write-Build White ' Manifest version and the release version in the build configuration file are the same?'
    assert ( ($Script:Manifest).Version.ToString() -eq (($Script:BuildEnv.ModuleVersion)) ) "The module manifest version ( $(($Script:Manifest).Version.ToString()) ) and release version ($($Script:BuildEnv.ModuleVersion)) are mismatched. These must be the same before continuing. Consider running the UpdateRelease task to make the module manifest version the same as the release version."
}

#Synopsis: Validate script requirements are met, load required modules, load project manifest and module, and load additional build tools.
task Configure -if {-not $Script:IsConfigured} ValidateRequirements, LoadRequiredModules, LoadModuleManifest, LoadModule, Version, LoadBuildTools, {
    # If we made it this far then we are configured!
    $Script:IsConfigured = $True
    Write-Build White ' Configuring build environment'
}

# Synopsis: Set a new version of the module
task NewVersion -if {$null -ne $NewVersion} LoadBuildTools, LoadModuleManifest, {
    Write-Build White ' Updating module build version'

    $ReleasePath = Join-Path $BuildRoot $Script:BuildEnv.BaseReleaseFolder
    $AllReleases = @((Get-ChildItem $ReleasePath -Directory | Where-Object {$_.Name -match '^([0-9].[0-9].[0-9])$'} | Select-Object).Name | ForEach-Object {[version]$_})

    if (($AllReleases -contains $NewVersion) -and (-not $Force)) {
        Write-Build Red 'The module version already has been released (the folder exists within the releases folder. In order to set your build project to this version you will need to pass the -Force switch to this build script with the -NewVersion parameter'
        throw 'Unable to update build project version!'
    }

    $Script:BuildEnv.ModuleVersion = $NewVersion.ToString()

    Save-BuildData
}

# Synopsis: Update current module manifest with the version defined in the build config file (if they differ)
task UpdateRelease LoadBuildTools, LoadModuleManifest, {
    # If there is a version mismatch then we need to put in some release notes and update the module manifest
    if ($null -eq $ReleaseNotes) {
        do {
            $ReleaseNotes = Read-Host -Prompt 'Enter brief release notes for this new version'
            if ([string]::IsNullOrEmpty($ReleaseNotes)) {
                Write-Build Red "You need to enter some kind of notes for your new release to update the manifest with!"
            }
        } while ([string]::IsNullOrEmpty($NewReleaseNotes))
    }

    Update-ModuleManifest -Path $ModuleManifestFullPath -ModuleVersion $Script:BuildEnv.ModuleVersion -ReleaseNotes $ReleaseNotes
}

# Synopsis: Regenerate scratch staging directory
task Clean {
    Write-Build White " Clean up our scratch/staging directory at .\$($Script:BuildEnv.ScratchFolder)"
    $null = Remove-Item $ScratchPath -Force -Recurse -ErrorAction 0
    $null = New-Item $ScratchPath -ItemType:Directory
}

# Synopsis: Create base content tree in scratch staging area
task PrepareStage {
    Write-Build White " Populate the skeleton scratch/staging directory"

    # Create the directories
    $null = New-Item "$($ScratchPath)\src" -ItemType:Directory -Force
    $null = New-Item $StageReleasePath -ItemType:Directory -Force

    Copy-Item -Path "$($BuildRoot)\*.psm1" -Destination $ScratchPath
    Copy-Item -Path "$($BuildRoot)\*.psd1" -Destination $ScratchPath
    Copy-Item -Path "$($BuildRoot)\$($Script:BuildEnv.PublicFunctionSource)" -Recurse -Destination "$($ScratchPath)\$($Script:BuildEnv.PublicFunctionSource)"
    Copy-Item -Path "$($BuildRoot)\$($Script:BuildEnv.PrivateFunctionSource)" -Recurse -Destination "$($ScratchPath)\$($Script:BuildEnv.PrivateFunctionSource)"
    Copy-Item -Path "$($BuildRoot)\$($Script:BuildEnv.OtherModuleSource)" -Recurse -Destination "$($ScratchPath)\$($Script:BuildEnv.OtherModuleSource)"
    Copy-Item -Path (Join-Path $BuildDocsPath 'en-US') -Recurse -Destination $ScratchPath
    $Script:BuildEnv.AdditionalModulePaths | ForEach-Object {
        Copy-Item -Path $_ -Recurse -Destination $ScratchPath -Force
    }
}

# Synopsis: Update public functions to include a template comment based help.
task UpdateCBHtoScratch {
    Write-Build White " Attempting to insert comment based help into functions (saving to our scratch directory only)."

    $CBHPattern = "(?ms)(^\s*\<#.*\.SYNOPSIS.*?#>)"
    $CBHUpdates = 0

    # Create the directories
    $null = New-Item "$($ScratchPath)\src" -ItemType:Directory -Force
    $null = New-Item "$($ScratchPath)\$($Script:BuildEnv.PublicFunctionSource)" -ItemType:Directory -Force
    Get-ChildItem "$($BuildRoot)\$($Script:BuildEnv.PublicFunctionSource)" -Filter *.ps1 | ForEach-Object {
        $FileName = $_.Name
        $FullFilePath = $_.FullName
        Write-Build White " Public function - $($FileName)"
        $currscript = Get-Content $FullFilePath -Raw
        $CBH = $currscript | New-CommentBasedHelp
        $currscriptblock = [scriptblock]::Create($currscript)
        . $currscriptblock
        $currfunct = get-command $CBH.FunctionName


        if ($currfunct.definition -notmatch $CBHPattern) {
            $CBHUpdates++
            Write-Build White " Inserting template CBH and writing to : $($Script:BuildEnv.ScratchFolder)\$($Script:BuildEnv.PublicFunctionSource)\$($FileName)"
            $UpdatedFunct = 'Function ' + $currfunct.Name + ' {' + "`r`n" + $CBH.CBH + "`r`n" + $currfunct.definition + "`r`n" + '}'
            $UpdatedFunct | Out-File "$($ScratchPath)\$($Script:BuildEnv.PublicFunctionSource)\$($FileName)" -Encoding $Script:BuildEnv.Encoding -force
        }
        else {
            Write-Build Yellow " Comment based help already exists!"
        }

        Remove-Item Function:\$($currfunct.Name)
    }
    Write-Build White ''
    Write-Build  Yellow '****************************************************************************************************'
    Write-Build  Yellow " Updated Functions: $CBHUpdates"
    if ($CBHUpdates -gt 0) {
        Write-Build White ''
        Write-Build Yellow " Updated Function Location: $($ScratchPath)\$($Script:BuildEnv.PublicFunctionSource)"
        Write-Build White ''
        Write-Build Yellow " NOTE: Please inspect these files closely. If they look good merge them back into your project"
    }
    Write-Build  Yellow '****************************************************************************************************'
    $null = Read-Host 'Press Enter to continue...'
}

# Synopsis: Collect a list of our public methods for later module manifest updates
task GetPublicFunctions {
    Write-Build White ' Parsing for public (exported) function names'
    $Exported = @()
    Get-ChildItem (Join-Path $BuildRoot $Script:BuildEnv.PublicFunctionSource) -Recurse -Filter "*.ps1" -File | Sort-Object Name | ForEach-Object {
        $Exported += ([System.Management.Automation.Language.Parser]::ParseInput((Get-Content -Path $_.FullName -Raw), [ref]$null, [ref]$null)).FindAll( { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $false) | ForEach-Object {$_.Name}
    }

    if ($Exported.Count -eq 0) {
        Write-Error 'There are no public functions to export!'
    }
    $Script:FunctionsToExport = $Exported
    Write-Build White " Number of exported functions found = $($Exported.Count)"
}

# Synopsis: Assemble the module for release
task CreateModulePSM1 {
    if ($Script:BuildEnv.OptionCombineFiles) {
        Write-Build White " Option to combine PSM1 is enabled, combining source files now..."

        $CombineFiles = ''
        $PreloadFilePath = (Join-Path $ScratchPath "$($Script:BuildEnv.OtherModuleSource)\PreLoad.ps1")
        if (Test-Path $PreloadFilePath) {
            Write-Build White " Starting with Preload.ps1"
            $CombineFiles += "## Pre-Loaded Module code ##`r`n`r`n"
            Get-childitem $PreloadFilePath | ForEach-Object {
                $CombineFiles += (Get-content $_ -Raw) + "`r`n`r`n"
            }
        }
        Write-Build White " Adding the private source files:"

        $CombineFiles += "## PRIVATE MODULE FUNCTIONS AND DATA ##`r`n`r`n"

        Get-childitem  (Join-Path $ScratchPath "$($Script:BuildEnv.PrivateFunctionSource)\*.ps1") | ForEach-Object {
            Write-Build White " $($_.Name)"
            $CombineFiles += (Get-content $_ -Raw) + "`r`n`r`n"
        }

        Write-Build White " Adding the public source files:"

        $CombineFiles += "## PUBLIC MODULE FUNCTIONS AND DATA ##`r`n`r`n"
        Get-childitem  (Join-Path $ScratchPath "$($Script:BuildEnv.PublicFunctionSource)\*.ps1") | ForEach-Object {
            Write-Build White " $($_.Name)"
            $CombineFiles += (Get-content $_ -Raw) + "`r`n`r`n"
        }

        Write-Build White " Finishing with Postload.ps1"
        $CombineFiles += "## Post-Load Module code ##`r`n`r`n"
        $PostLoadPath = (Join-Path $ScratchPath "$($Script:BuildEnv.OtherModuleSource)\PostLoad.ps1")
        if (Test-Path $PostLoadPath) {
            Get-childitem  $PostLoadPath | ForEach-Object {
                $CombineFiles += (Get-content $_ -Raw) + "`r`n`r`n"
            }
        }

        Set-Content -Path $Script:ReleaseModule  -Value $CombineFiles -Encoding $Script:BuildEnv.Encoding
    }
    else {
        Write-Build White " Option to combine PSM1 is NOT enabled, copying over file structure now..."
        Copy-Item -Path (Join-Path $ScratchPath $Script:BuildEnv.OtherModuleSource) -Recurse -Destination $StageReleasePath -Force
        Copy-Item -Path (Join-Path $ScratchPath $Script:BuildEnv.PrivateFunctionSource) -Recurse -Destination $StageReleasePath -Force
        Copy-Item -Path (Join-Path $ScratchPath $Script:BuildEnv.PublicFunctionSource) -Recurse -Destination $StageReleasePath -Force
        Copy-Item -Path (Join-Path $ScratchPath $Script:BuildEnv.ModuleToBuild) -Destination $StageReleasePath -Force
    }


    if (($Script:BuildEnv.AdditionalModulePaths).Count -gt 0) {
        Write-Build White " Copying over additional module paths now."
        $Script:BuildEnv.AdditionalModulePaths | ForEach-Object {
            Write-Build White " Copying $_"
            Copy-Item -Path $_ -Recurse -Destination $StageReleasePath -Force
        }
    }
}

# Synopsis: Removes script signatures before creating a combined PSM1 file
task RemoveScriptSignatures -Before CreateModulePSM1 {
    if ($Script:BuildEnv.OptionCombineFiles) {
        Write-Build White ' Remove script signatures from all files'
        Get-ChildItem -Path "$($ScratchPath)\$($Script:BuildEnv.BaseSourceFolder)" -Recurse -File | ForEach-Object {Remove-Signature -FilePath $_.FullName}
    }
}

# Synopsis: Warn about not empty git status if .git exists.
task GitStatus -If (Test-Path .git) {
    $status = exec { git status -s }
    if ($status) {
        Write-Warning " Git status: $($status -join ', ')"
    }
}

# Synopsis: Validate that sensitive strings are not found in your code
task SanitizeCode -if {$Script:BuildEnv.OptionSanitizeSensitiveTerms} {
    ForEach ($Term in $Script:BuildEnv.OptionSensitiveTerms) {
        Write-Build White " Checking Files for sensitive string: $Term"
        $TermsFound = Get-ChildItem -Path $ScratchPath -Recurse -File | Where-Object {$_.FullName -notlike "$($StageReleasePath)*"} | Select-String -Pattern $Term
        if ($TermsFound.Count -gt 0) {
            Write-Build White " Sensitive string found in the following files:"
            $TermsFound | ForEach-Object {
                Write-Build White " $($_)"
            }
            throw "Sensitive Terms found!"
        }
    }
}

# Synopsis: Replace comment based help with external help in all public functions for this project
task UpdateCBH -Before CreateModulePSM1 {
    $CBHPattern = "(?ms)(\<#.*\.SYNOPSIS.*?#>)"
    Get-ChildItem -Path "$($ScratchPath)\$($Script:BuildEnv.PublicFunctionSource)\*.ps1" -File | ForEach-Object {
        $FormattedOutFile = $_.FullName
        $FileName = $_.Name
        Write-Build White " Replacing CBH in file: $($FileName)"
        $FunctionName = $FileName -replace '.ps1', ''
        $NewExternalHelp = $ExternalHelp -replace '{{LINK}}', ($Script:BuildEnv.ModuleWebsite + "/tree/master/$($Script:BuildEnv.BaseReleaseFolder)/$($Script:BuildEnv.ModuleVersion)/docs/Functions/$($FunctionName).md")
        $UpdatedFile = (get-content  $FormattedOutFile -raw) -replace $CBHPattern, $NewExternalHelp
        $UpdatedFile | Out-File -FilePath $FormattedOutFile -force -Encoding $Script:BuildEnv.Encoding
    }
}

# Synopsis: Run PSScriptAnalyzer against the assembled module
task AnalyzeScript -After CreateModulePSM1 -if {$Script:BuildEnv.OptionAnalyzeCode} {
    Write-Build White ' Analyzing the project with ScriptAnalyzer.'
    $Analysis = Invoke-ScriptAnalyzer -Path $StageReleasePath
    $AnalysisErrors = @($Analysis | Where-Object {@('Information', 'Warning') -notcontains $_.Severity})

    if ($AnalysisErrors.Count -ne 0) {
        Write-Build White 'The following errors came up in the script analysis:'
        $AnalysisErrors
        Write-Build
        Write-Build White "Note that this was from the script analysis run against $StageReleasePath"
        Prompt-ForBuildBreak -CustomError $AnalysisErrors
    }
}

# Synopsis: Run PSScriptAnalyzer against the public source files.
task AnalyzePublic {
    Write-Build White ' Analyzing the public source files with ScriptAnalyzer.'
    $Analysis = Invoke-ScriptAnalyzer -Path (Join-Path $BuildRoot $Script:BuildEnv.PublicFunctionSource)
    $AnalysisErrors = @($Analysis | Where-Object {@('Information', 'Warning') -notcontains $_.Severity})

    if ($AnalysisErrors.Count -ne 0) {
        Write-Build White 'The following errors came up in the script analysis:'
        $AnalysisErrors
        Write-Build
        Write-Build White "Note that this was from the script analysis run against $($Script:BuildEnv.PublicFunctionSource)"
        #Prompt-ForBuildBreak -CustomError $AnalysisErrors
    }
}

# Synopsis: Build help files for module
task CreateHelp CreateMarkdownHelp, CreateExternalHelp, CreateUpdateableHelpCAB, CreateProjectHelp,AddAdditionalDocFiles, {
    Write-Build White ' Create help files'
}

# Synopsis: Build the markdown help files with PlatyPS
task CreateMarkdownHelp GetPublicFunctions, {
    # First copy over documentation
    Write-Build White ' Creating markdown documentation with PlatyPS'
    Copy-Item -Path "$($ScratchPath)\en-US" -Recurse -Destination $StageReleasePath -Force

    $OnlineModuleLocation = "$($Script:BuildEnv.ModuleWebsite)/$($Script:BuildEnv.BaseReleaseFolder)"
    $FwLink = "$($OnlineModuleLocation)/$($CurrentReleaseFolder)/docs/$($Script:BuildEnv.ModuleToBuild).md"
    $ModulePage = "$($StageReleasePath)\docs\$($Script:BuildEnv.ModuleToBuild).md"

    # Create the function .md files and the generic module page md as well for the distributable module
    $null = New-MarkdownHelp -module $Script:BuildEnv.ModuleToBuild -OutputFolder "$($StageReleasePath)\docs\" -Force -WithModulePage -Locale 'en-US' -FwLink $FwLink -HelpVersion $Script:BuildEnv.ModuleVersion -Encoding ([System.Text.Encoding]::($Script:BuildEnv.Encoding))

    # Replace each missing element we need for a proper generic module page .md file
    $ModulePageFileContent = Get-Content -raw $ModulePage
    $ModulePageFileContent = $ModulePageFileContent -replace '{{Manually Enter Description Here}}', $Script:Manifest.Description
    $Script:FunctionsToExport | Foreach-Object {
        Write-Build White " Updating definition for the following function: $($_)"
        $TextToReplace = "{{Manually Enter $($_) Description Here}}"
        $ReplacementText = (Get-Help -Detailed $_).Synopsis
        $ModulePageFileContent = $ModulePageFileContent -replace $TextToReplace, $ReplacementText
    }
    $ModulePageFileContent | Out-File $ModulePage -Force -Encoding $Script:BuildEnv.Encoding

    $MissingDocumentation = Select-String -Path "$($StageReleasePath)\docs\*.md" -Pattern "({{.*}})"

    if ($MissingDocumentation.Count -gt 0) {
        Write-Build Yellow ''
        Write-Build Yellow ' The documentation that got generated resulted in missing sections which should be filled out.'
        Write-Build Yellow ' Please review the following sections in your comment based help, fill out missing information and rerun this build:'
        Write-Build Yellow ' (Note: This can happen if the .EXTERNALHELP CBH is defined for a function before running this build.)'
        Write-Build White ''
        Write-Build Yellow "Path of files with issues: $($StageReleasePath)\docs\"
        Write-Build White ''
        $MissingDocumentation | Select-Object FileName, Matches | Format-Table -auto
        Write-Build  Yellow ''
        pause

        throw 'Missing documentation. Please review and rebuild.'
    }
}

# Synopsis: Build the markdown help files with PlatyPS
task CreateExternalHelp {
    Write-Build White ' Creating markdown help files'
    $PlatyPSVerbose = @{}
    if ($Script:BuildEnv.OptionRunPlatyPSVerbose) {
        $PlatyPSVerbose.Verbose = $true
    }
    $null = New-ExternalHelp "$($StageReleasePath)\docs" -OutputPath "$($StageReleasePath)\en-US\" -Force @PlatyPSVerbose
}

# Synopsis: Build the help file CAB with PlatyPS
task CreateUpdateableHelpCAB {
    Write-Build White " Creating updateable help cab file"
    $PlatyPSVerbose = @{}
    if ($Script:BuildEnv.OptionRunPlatyPSVerbose) {
        $PlatyPSVerbose.Verbose = $true
    }
    $LandingPage = "$($StageReleasePath)\docs\$($Script:BuildEnv.ModuleToBuild).md"
    $null = New-ExternalHelpCab -CabFilesFolder "$($StageReleasePath)\en-US\" -LandingPagePath $LandingPage -OutputFolder "$($StageReleasePath)\en-US\" @PlatyPSVerbose
}

# Synopsis: Build help files for module and ignore missing section errors
task TestCreateHelp Configure, CreateMarkdownHelp, CreateExternalHelp, CreateUpdateableHelpCAB, {
    Write-Build White ' Create help files'
}

# Synopsis: Create a new version release directory for our release and copy our contents to it
task PushVersionRelease {
    Write-Build White " Attempting to push a version release of the module"

    $ThisReleasePath = Join-Path $ReleasePath $Script:BuildEnv.ModuleVersion
    $ThisBuildReleasePath = ".\$($Script:BuildEnv.BaseReleaseFolder)\$($Script:BuildEnv.ModuleVersion)"
    $null = Remove-Item $ThisReleasePath -Force -Recurse -ErrorAction 0
    $null = New-Item $ThisReleasePath -ItemType:Directory -Force
    Copy-Item -Path "$($StageReleasePath)\*" -Destination $ThisReleasePath -Recurse
    Out-Zip $StageReleasePath "$ReleasePath\$($Script:BuildEnv.ModuleToBuild)-$Version.zip" -overwrite
}

# Synopsis: Create the current release directory and copy this build to it.
task PushCurrentRelease {
    Write-Build White " Attempting to push a current release of the module"

    $ThisBuildCurrentReleasePath = ".\$($Script:BuildEnv.BaseReleaseFolder)\$($CurrentReleaseFolder)"
    $MostRecentRelease = (Get-ChildItem $ReleasePath -Directory | Where-Object {$_.Name -like "*.*.*"} | Select-Object Name).name | ForEach-Object {[version]$_} | Sort-Object -Descending | Select-Object -First 1
    $ProcessCurrentRelease = $true
    if ($MostRecentRelease) {
        if ($MostRecentRelease -gt [version]$Script:BuildEnv.ModuleVersion) {
            $ProcessCurrentRelease = $false
        }
    }
    if ($ProcessCurrentRelease) {
        Write-Build White " Pushing a version release to $($ThisBuildCurrentReleasePath)"
        $null = Remove-Item $CurrentReleasePath -Force -Recurse -ErrorAction 0
        $null = New-Item $CurrentReleasePath -ItemType:Directory -Force
        Copy-Item -Path "$($StageReleasePath)\*" -Destination $CurrentReleasePath -Recurse -force
        Out-Zip $StageReleasePath "$ReleasePath\$($Script:BuildEnv.ModuleToBuild)-current.zip" -overwrite
    }
    else {
        Write-Warning ' Unable to push this version as a current release as it is not the most recent version in the release directory!'
    }
}

# Synopsis: Build the markdown help for the functions using PlatyPS for the core project docs.
task CreateProjectHelp {
    Write-Build White ' Creating markdown documentation with PlatyPS for the core project'
    $OnlineModuleLocation = "$($Script:BuildEnv.ModuleWebsite)/$($Script:BuildEnv.BaseReleaseFolder)"
    $FwLink = "$($OnlineModuleLocation)/docs/Functions/$($Script:BuildEnv.ModuleToBuild).md"

    # Create the function .md files for the core project documentation
    $null = New-MarkdownHelp -module $Script:BuildEnv.ModuleToBuild -OutputFolder "$($BuildRoot)\docs\Functions\" -Force -Locale 'en-US' -FwLink $FwLink -HelpVersion $Script:BuildEnv.ModuleVersion -Encoding ([System.Text.Encoding]::($Script:BuildEnv.Encoding)) #-OnlineVersionUrl "$($Script:BuildEnv.ModuleWebsite)/docs/Functions"
}

# Synopsis: Add additional doc files to the final project document folder
task AddAdditionalDocFiles {
    Write-Build White " Add additional doc files to the project document folder (.\docs) from .\build\docs\Additional\"
    Copy-Item -Path (Join-Path $BuildDocsPath 'Additional\*.md') -Destination (Join-Path $BuildRoot 'docs') -Force
}

# Synopsis: Build ReadTheDocs yml file
task CreateReadTheDocsYML -After AddAdditionalDocFiles -if {$Script:BuildEnv.OptionGenerateReadTheDocs} Configure, {
    Write-Build White ' Create ReadTheDocs definition file and saving to the root project site.'

    $ReadTheDocsPath = Join-Path $BuildDocsPath 'ReadTheDocs'
    $DocsReleasePath = Join-Path $Script:BuildEnv.BaseReleaseFolder $CurrentReleaseFolder
    $YMLFile = Join-Path $BuildRoot 'mkdocs.yml'
    $Pages = [ordered]@{}

    $RTDFolders = Get-ChildItem -Path $ReadTheDocsPath -Directory | Sort-Object -Property Name

    ForEach ($RTDFolder in $RTDFolders) {
        # First copy over to our project document root
        Copy-Item -Path $RTDFolder.FullName -Destination $ProjectDocsPath -Force -Recurse

        $RTDocs = @(Get-ChildItem -Path $RTDFolder.FullName -Filter '*.md' | Sort-Object Name)
        if ($RTDocs.Count -gt 1) {
            $NewSection = @()
            Foreach ($RTDDoc in $RTDocs) {
                $NewSection += @{$RTDDoc.Basename = "$($RTDFolder.Name)\$($RTDDoc.Name)"}
            }
            $Pages[$RTDFolder.Name] = $NewSection
        }
        else {
            $Pages[$RTDFolder.Name] = "$($RTDFolder.Name)\$($RTDocs.Name)"
        }
    }

    # Store all the functions for its own readthedocs section
    if ($Script:FunctionsToExport.Count -gt 1) {
        $Functions = @()
        $Script:FunctionsToExport | ForEach-Object {
            $Functions += @{$_ = "$DocsReleasePath/docs/$_.md"}
        }
        $Pages.Functions = $Functions
    }
    else {
        $Pages.Functions = $Script:FunctionsToExport
    }

    $RTD = @{
        site_name = "$($Script:BuildEnv.ModuleToBuild) Docs"
        repo_url = $Script:BuildEnv.ModuleWebsite
        site_author = $Script:BuildEnv.ModuleAuthor
        edit_uri = "edit/master/docs/"
        theme = "readthedocs"
        copyright = "$($Script:BuildEnv.ModuleToBuild) is licensed under the <a href='$($Script:BuildEnv.ModuleWebsite)/master/LICENSE.md'> license"
        Pages = $Pages
    }
    $RTD | ConvertTo-Yaml | Out-File -Encoding $Script:BuildEnv.Encoding -FilePath $YMLFile -Force
}

# Synopsis: Push with a version tag.
task GitPushRelease Version, {
    $changes = exec { git status --short }
    assert (-not $changes) "Please, commit changes."

    exec { git push }
    exec { git tag -a "v$($Script:BuildEnv.ModuleVersion)" -m "v$($Script:BuildEnv.ModuleVersion)" }
    exec { git push origin "v$($Script:BuildEnv.ModuleVersion)" }
}

# Synopsis: Push to github
task GithubPush Version, {
    exec { git add . }
    if ($ReleaseNotes -ne $null) {
        exec { git commit -m "$ReleaseNotes"}
    }
    else {
        exec { git commit -m "$($Script:BuildEnv.ModuleVersion)"}
    }
    exec { git push origin master }
    $changes = exec { git status --short }
    assert (-not $changes) "Please, commit changes."
}

# Synopsis: Push the project to PSScriptGallery
task PublishPSGallery LoadBuildTools, InstallModule, {
    Write-Build White ' Publishing recent module release to the PowerShell Gallery'

    if (Get-Module $Script:BuildEnv.ModuleToBuild) {
        # If the module is already loaded then unload it.
        Remove-Module $Script:BuildEnv.ModuleToBuild
    }

    # Try to import the module
    Import-Module -Name $Script:BuildEnv.ModuleToBuild

    Write-Build White " Uploading project to PSGallery: $($Script:BuildEnv.ModuleToBuild)"
    Upload-ProjectToPSGallery -Name $Script:BuildEnv.ModuleToBuild -NuGetApiKey $Script:BuildEnv.NuGetApiKey -Verbose
}

# Synopsis: Remove session artifacts like loaded modules and variables
task BuildSessionCleanup {
    Write-Build White ' Cleaning up the build session'

    # Clean up loaded modules if they are loaded
    $RequiredModules | Foreach-Object {
        Write-Build White " Removing $($_) module (if loaded)."
        Remove-Module $_  -Erroraction Ignore
    }
    Write-Build White " Removing $($Script:BuildEnv.ModuleToBuild) module (if loaded)."
    Remove-Module $Script:BuildEnv.ModuleToBuild -Erroraction Ignore

    # Dot source any post build cleanup scripts.
    Get-ChildItem $BuildToolPath/cleanup -Recurse -Filter "*.ps1" -File | Foreach {
        Write-Build White " Dot sourcing cleanup script file: $($_.Name)"
        . $_.FullName
    }
    if ($Script:BuildEnv.OptionTranscriptEnabled) {
        Stop-Transcript -WarningAction:Ignore
    }
}

# Synopsis: Install the current built module to the local machine
task InstallModule Version, {
    Write-Build White " Attempting to install the current module"
    $CurrentModulePath = Join-Path $Script:BuildEnv.BaseReleaseFolder $Script:BuildEnv.ModuleVersion
    assert (Test-Path $CurrentModulePath) 'The current version module has not been built yet!'

    $MyModulePath = "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\"
    $ModuleInstallPath = "$($MyModulePath)$($Script:BuildEnv.ModuleToBuild)"
    if (Test-Path $ModuleInstallPath) {
        Write-Build White " Removing installed module $($Script:BuildEnv.ModuleToBuild)"
        Remove-Item -Path $ModuleInstallPath -Confirm -Recurse
        assert (-not (Test-Path $ModuleInstallPath)) 'Module already installed and you opted not to remove it. Cancelling install operation!'
    }

    Write-Build White " Installing current module:"
    Write-Build White " Source - $($CurrentModulePath)"
    Write-Build White " Destination - $($ModuleInstallPath)"
    Copy-Item -Path $CurrentModulePath -Destination $ModuleInstallPath -Recurse
}

# Synopsis: Test import the current module
task TestInstalledModule Version, {
    Write-Build White " Test importing the current module version $($Script:BuildEnv.ModuleVersion)"

    $InstalledModules = @(Get-Module -ListAvailable $Script:BuildEnv.ModuleToBuild)
    assert ($InstalledModules.Count -gt 0) 'Unable to find that the module is installed!'
    if ($InstalledModules.Count -gt 1) {
        Write-Warning 'There are multiple installed modules found for this project (shown below). Be aware that this may skew the test results: '
    }
    Import-Module -Name $Script:BuildEnv.ModuleToBuild -MinimumVersion $Script:BuildEnv.ModuleVersion -Force
}

task InstallAndTestModule InstallModule, TestInstalledModule

# Synopsis: The default build
task . `
    Configure,
Clean,
PrepareStage,
GetPublicFunctions,
SanitizeCode,
CreateHelp,
CreateModulePSM1,
PushVersionRelease,
PushCurrentRelease,
CreateProjectHelp,
BuildSessionCleanup

# Synopsis: Instert Comment Based Help where it doesn't already exist (output to scratch directory)
task InsertMissingCBH `
    Configure,
Clean,
UpdateCBHtoScratch,
BuildSessionCleanup

# Synopsis: Test the code formatting module only
task TestCodeFormatting Configure, Clean, PrepareStage, GetPublicFunctions, FormatCode

# Synopsis: Build help files for module and ignore missing section errors
task TestCreateHelp Configure, CreateMarkdownHelp, CreateExternalHelp, CreateUpdateableHelpCAB