PSPublishModule.psm1

function Add-Artefact {
    [CmdletBinding()]
    param(
        [string] $ModuleName,
        [string] $ModuleVersion,
        [string] $ArtefactName,
        [alias('IncludeTagName')][nullable[bool]] $IncludeTag,
        [nullable[bool]] $LegacyName,
        [nullable[bool]] $CopyMainModule,
        [nullable[bool]] $CopyRequiredModules,
        [string] $ProjectPath,
        [string] $Destination,
        [string] $DestinationMainModule,
        [string] $DestinationRequiredModules,
        [nullable[bool]] $DestinationFilesRelative,
        [alias('DestinationDirectoriesRelative')][nullable[bool]] $DestinationFoldersRelative,
        [alias('FilesOutput')][System.Collections.IDictionary] $Files,
        [alias('DirectoryOutput')][System.Collections.IDictionary] $Folders,
        [array] $RequiredModules,
        # [string] $TagName,
        #[string] $FileName,
        [nullable[bool]] $ZipIt,
        [string] $DestinationZip,
        [bool] $ConvertToScript,
        [string] $ScriptName,
        [string] $PreScriptMerge,
        [string] $PostScriptMerge,
        [System.Collections.IDictionary] $Configuration,
        [string] $ID,
        [switch] $DoNotClear
    )

    $DestinationMainModule = Initialize-ReplacePath -ReplacementPath $DestinationMainModule -ModuleName $ModuleName -ModuleVersion $ModuleVersion -Configuration $Configuration
    $DestinationRequiredModules = Initialize-ReplacePath -ReplacementPath $DestinationRequiredModules -ModuleName $ModuleName -ModuleVersion $ModuleVersion -Configuration $Configuration
    $DestinationZip = Initialize-ReplacePath -ReplacementPath $DestinationZip -ModuleName $ModuleName -ModuleVersion $ModuleVersion -Configuration $Configuration
    $Destination = Initialize-ReplacePath -ReplacementPath $Destination -ModuleName $ModuleName -ModuleVersion $ModuleVersion -Configuration $Configuration

    $ResolvedDestination = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Destination)
    if (-not $DoNotClear) {
        if (Test-Path -LiteralPath $ResolvedDestination) {
            Write-TextWithTime -Text "Removing files/folders from $ResolvedDestination before copying artefacts" -SpacesBefore ' ' -PreAppend Minus {
                Remove-ItemAlternative -Path $ResolvedDestination -SkipFolder -Exclude '*.zip' -ErrorAction Stop
            } -ColorBefore Yellow -ColorTime Green -ColorError Red -Color Yellow
        }
    }
    if ($ConvertToScript) {
        Write-TextWithTime -Text "Converting merged release to script" -PreAppend Plus -SpacesBefore ' ' {
            $convertToScriptSplat = @{
                Enabled         = $true
                IncludeTagName  = $IncludeTag
                ModuleName      = $ModuleName
                Destination     = $DestinationMainModule
                PreScriptMerge  = $PreScriptMerge
                PostScriptMerge = $PostScriptMerge
                ScriptName      = $ScriptName
                Configuration   = $Configuration
                ModuleVersion   = $ModuleVersion
            }
            Remove-EmptyValue -Hashtable $convertToScriptSplat
            Copy-ArtefactToScript @convertToScriptSplat

            $copyRequiredModuleSplat = @{
                Enabled         = $CopyRequiredModules
                RequiredModules = $RequiredModules
                Destination     = $DestinationRequiredModules
            }
            Copy-ArtefactRequiredModule @copyRequiredModuleSplat

            $copyArtefactRequiredFoldersSplat = @{
                FoldersInput        = $Folders
                ProjectPath         = $ProjectPath
                Destination         = $Destination
                DestinationRelative = $DestinationFoldersRelative
            }
            Copy-ArtefactRequiredFolders @copyArtefactRequiredFoldersSplat

            $copyArtefactRequiredFilesSplat = @{
                FilesInput          = $Files
                ProjectPath         = $ProjectPath
                Destination         = $Destination
                DestinationRelative = $DestinationFilesRelative
            }
            Copy-ArtefactRequiredFiles @copyArtefactRequiredFilesSplat
        }
    }
    else {
        Write-TextWithTime -Text "Copying merged release to $ResolvedDestination" -PreAppend Addition -SpacesBefore ' ' {
            $copyMainModuleSplat = @{
                Enabled        = $true
                IncludeTagName = $IncludeTag
                ModuleName     = $ModuleName
                Destination    = $DestinationMainModule
            }
            Copy-ArtefactMainModule @copyMainModuleSplat

            $copyRequiredModuleSplat = @{
                Enabled         = $CopyRequiredModules
                RequiredModules = $RequiredModules
                Destination     = $DestinationRequiredModules
            }
            Copy-ArtefactRequiredModule @copyRequiredModuleSplat

            $copyArtefactRequiredFoldersSplat = @{
                FoldersInput        = $Folders
                ProjectPath         = $ProjectPath
                Destination         = $Destination
                DestinationRelative = $DestinationFoldersRelative
            }
            Copy-ArtefactRequiredFolders @copyArtefactRequiredFoldersSplat

            $copyArtefactRequiredFilesSplat = @{
                FilesInput          = $Files
                ProjectPath         = $ProjectPath
                Destination         = $Destination
                DestinationRelative = $DestinationFilesRelative
            }
            Copy-ArtefactRequiredFiles @copyArtefactRequiredFilesSplat
        }
    }
    if ($ZipIt -and $DestinationZip) {
        $ResolvedDestinationZip = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($DestinationZip)
        Write-TextWithTime -Text "Zipping merged release to $ResolvedDestinationZip" -PreAppend Information -SpacesBefore ' ' {
            $zipSplat = @{
                Source        = $ResolvedDestination
                Destination   = $ResolvedDestinationZip

                Configuration = $Configuration
                LegacyName    = if ($Configuration.Steps.BuildModule.Releases -is [bool]) {
                    $true 
                }
                else {
                    $false 
                }
                ModuleName    = $ModuleName
                ModuleVersion = $ModuleVersion
                IncludeTag    = $IncludeTag
                ArtefactName  = $ArtefactName
                ID            = $ID
            }
            Compress-Artefact @zipSplat
        }
        Write-TextWithTime -Text "Removing temporary files from $ResolvedDestination" -SpacesBefore ' ' -PreAppend Minus {
            Remove-ItemAlternative -Path $ResolvedDestination -SkipFolder -Exclude '*.zip' -ErrorAction Stop
        } -ColorBefore Yellow -ColorTime Green -ColorError Red -Color Yellow
    }
}
function Add-Directory {
    [CmdletBinding()]
    param(
        [string] $Directory
    )
    $exists = Test-Path -Path $Directory
    if ($exists -eq $false) {
        $null = New-Item -Path $Directory -ItemType Directory -Force
    }
}
function Compress-Artefact {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Configuration,
        [string] $Source,
        [string] $Destination,
        [string] $ModuleName,
        [string] $ModuleVersion,
        [nullable[bool]] $IncludeTag,
        [nullable[bool]] $LegacyName,
        [string] $ArtefactName,
        [string] $ID
    )

    if ($Configuration.CurrentSettings.PreRelease) {
        $ModuleVersionWithPreRelease = "$($ModuleVersion)-$($Configuration.CurrentSettings.PreRelease)"
        $TagModuleVersionWithPreRelease = "v$($ModuleVersionWithPreRelease)"
    }
    else {
        $ModuleVersionWithPreRelease = $ModuleVersion
        $TagModuleVersionWithPreRelease = "v$($ModuleVersion)"
    }
    if ($LegacyName) {

        $FileName = -join ("v$($ModuleVersion)", '.zip')
    }
    elseif ($ArtefactName) {
        $TagName = "v$($ModuleVersion)"
        $FileName = $ArtefactName
        $FileName = $FileName.Replace('{ModuleName}', $ModuleName)
        $FileName = $FileName.Replace('<ModuleName>', $ModuleName)
        $FileName = $FileName.Replace('{ModuleVersion}', $ModuleVersion)
        $FileName = $FileName.Replace('<ModuleVersion>', $ModuleVersion)
        $FileName = $FileName.Replace('{ModuleVersionWithPreRelease}', $ModuleVersionWithPreRelease)
        $FileName = $FileName.Replace('<ModuleVersionWithPreRelease>', $ModuleVersionWithPreRelease)
        $FileName = $FileName.Replace('{TagModuleVersionWithPreRelease}', $TagModuleVersionWithPreRelease)
        $FileName = $FileName.Replace('<TagModuleVersionWithPreRelease>', $TagModuleVersionWithPreRelease)
        $FileName = $FileName.Replace('{TagName}', $TagName)
        $FileName = $FileName.Replace('<TagName>', $TagName)

        $FileName = if ($FileName.EndsWith(".zip")) {
            $FileName 
        }
        else {
            -join ($FileName, '.zip') 
        }
    }
    else {
        if ($IncludeTag) {
            $TagName = "v$($ModuleVersion)"
        }
        else {
            $TagName = ''
        }
        if ($TagName) {
            $FileName = -join ($ModuleName, ".$TagName", '.zip')
        }
        else {
            $FileName = -join ($ModuleName, '.zip')
        }
    }
    $ZipPath = [System.IO.Path]::Combine($Destination, $FileName)
    $ZipPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ZipPath)

    $Configuration.CurrentSettings.ArtefactZipName = $FileName
    $Configuration.CurrentSettings.ArtefactZipPath = $ZipPath

    if ($ID) {

        $ZipPackage = [ordered] @{
            'Id'      = $ID
            'ZipName' = $FileName
            'ZipPath' = $ZipPath
        }
        $Configuration.CurrentSettings['Artefact'] += $ZipPackage
    }
    else {
        if (-not $Configuration.CurrentSettings['ArtefactDefault']) {
            $Configuration.CurrentSettings['ArtefactDefault'] = [ordered] @{
                'Id'      = 'Default'
                'ZipName' = $FileName
                'ZipPath' = $ZipPath
            }
        }
    }

    $Success = Write-TextWithTime -Text "Compressing final merged release $ZipPath" {
        $null = New-Item -ItemType Directory -Path $Destination -Force

        [Array] $DirectoryToCompress = Get-ChildItem -Path $Source -Directory -ErrorAction SilentlyContinue
        $FilesToCompressSource = [System.IO.Path]::Combine($Source, "*")
        [Array] $FilesToCompress = Get-ChildItem -Path $FilesToCompressSource -File -Exclude '*.zip' -ErrorAction SilentlyContinue
        if ($DirectoryToCompress.Count -gt 0 -and $FilesToCompress.Count -gt 0) {
            Compress-Archive -Path @(
                $DirectoryToCompress.FullName
                $FilesToCompress.FullName
            ) -DestinationPath $ZipPath -Force -ErrorAction Stop
        }
        elseif ($DirectoryToCompress.Count -gt 0) {
            Compress-Archive -Path $DirectoryToCompress.FullName -DestinationPath $ZipPath -Force -ErrorAction Stop
        }
        elseif ($FilesToCompress.Count -gt 0) {
            Compress-Archive -Path $FilesToCompress.FullName -DestinationPath $ZipPath -Force -ErrorAction Stop
        }
    } -PreAppend Addition -SpacesBefore ' ' -Color Yellow -ColorTime Green -ColorBefore Yellow -ColorError Red

    if ($Success -eq $false) {
        return $false
    }
}
function Convert-HashTableToNicelyFormattedString {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $hashTable
    )
    [string] $nicelyFormattedString = $hashTable.Keys | ForEach-Object `
    {
        $key = $_
        $value = $hashTable.$key
        " $key = $value$NewLine"
    }
    return $nicelyFormattedString
}

function Convert-RequiredModules {
    <#
    .SYNOPSIS
    Converts the RequiredModules section of the manifest to the correct format
 
    .DESCRIPTION
    Converts the RequiredModules section of the manifest to the correct format
    Fixes the ModuleVersion and Guid if set to 'Latest' or 'Auto'
 
    .PARAMETER Configuration
    The configuration object of the module
 
    .EXAMPLE
    Convert-RequiredModules -Configuration $Configuration
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Configuration
    )
    $Manifest = $Configuration.Information.Manifest

    $Failures = $false
    if ($Manifest.Contains('RequiredModules')) {
        foreach ($SubModule in $Manifest.RequiredModules) {
            if ($SubModule -is [string]) {
            }
            else {
                [Array] $AvailableModule = Get-Module -ListAvailable $SubModule.ModuleName -Verbose:$false
                if ($SubModule.ModuleVersion -in 'Latest', 'Auto') {
                    if ($AvailableModule) {
                        $SubModule.ModuleVersion = $AvailableModule[0].Version.ToString()
                    }
                    else {
                        Write-Text -Text "[-] Module $($SubModule.ModuleName) is not available (Version), but defined as required with last version. Terminating." -Color Red
                        $Failures = $true
                    }
                }
                if ($SubModule.Guid -in 'Latest', 'Auto') {
                    if ($AvailableModule) {
                        $SubModule.Guid = $AvailableModule[0].Guid.ToString()
                    }
                    else {
                        Write-Text -Text "[-] Module $($SubModule.ModuleName) is not available (GUID), but defined as required with last version. Terminating." -Color Red
                        $Failures = $true
                    }
                }
            }
        }
    }
    if ($Failures -eq $true) {
        $false
    }
}
function Copy-ArtefactMainModule {
    [CmdletBinding()]
    param(
        [switch] $Enabled,
        [nullable[bool]] $IncludeTagName,
        [string] $ModuleName,
        [string] $Destination
    )
    if (-not $Enabled) {
        return
    }
    if ($IncludeTagName) {
        $NameOfDestination = [io.path]::Combine($Destination, $ModuleName, $TagName)
    }
    else {
        $NameOfDestination = [io.path]::Combine($Destination, $ModuleName)
    }
    $ResolvedDestination = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($NameOfDestination)
    Write-TextWithTime -PreAppend Addition -Text "Copying main module to $ResolvedDestination" -Color Yellow {
        if (Test-Path -Path $NameOfDestination) {
            Remove-ItemAlternative -LiteralPath $NameOfDestination -ErrorAction Stop
        }
        $null = New-Item -ItemType Directory -Path $Destination -Force

        if ($DestinationPaths.Desktop) {
            Copy-Item -LiteralPath $DestinationPaths.Desktop -Recurse -Destination $ResolvedDestination -Force
        }
        elseif ($DestinationPaths.Core) {
            Copy-Item -LiteralPath $DestinationPaths.Core -Recurse -Destination $ResolvedDestination -Force
        }
    } -SpacesBefore ' '
}
function Copy-ArtefactRequiredFiles {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $FilesInput,
        [string] $ProjectPath,
        [string] $Destination,
        [nullable[bool]] $DestinationRelative
    )

    foreach ($File in $FilesInput.Keys) {
        if ($FilesInput[$File] -is [string]) {
            $FullFilePath = [System.IO.Path]::Combine($ProjectPath, $File)
            if (Test-Path -Path $FullFilePath) {
                if ($DestinationRelative) {
                    $DestinationPath = [System.IO.Path]::Combine($Destination, $FilesInput[$File])
                }
                else {
                    $DestinationPath = $FilesInput[$File]
                }
                $ResolvedDestination = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($DestinationPath)
                Write-TextWithTime -Text "Copying file $FullFilePath to $ResolvedDestination" {
                    $DirectoryPath = [Io.Path]::GetDirectoryName($ResolvedDestination)
                    $null = New-Item -ItemType Directory -Force -ErrorAction Stop -Path $DirectoryPath
                    Copy-Item -LiteralPath $FullFilePath -Destination $ResolvedDestination -Force -ErrorAction Stop
                } -PreAppend Addition -SpacesBefore ' ' -Color Yellow
            }
            else {
                Write-TextWithTime -Text "File $FullFilePath does not exist" -PreAppend Plus -SpacesBefore ' ' -Color Red -ColorTime Red -ColorBefore Red
                return $false
            }
        }
        elseif ($FilesInput[$File] -is [System.Collections.IDictionary]) {
            if ($FilesInput[$File].Enabled -eq $true) {
                if ($FilesInput[$File].Source) {
                    $FullFilePath = [System.IO.Path]::Combine($ProjectPath, $FilesInput[$File].Source)
                    if (Test-Path -Path $FullFilePath) {
                        if ($FilesInput[$File].DestinationRelative) {
                            $DestinationPath = [System.IO.Path]::Combine($Destination, $FilesInput[$File].Destination)
                        }
                        else {
                            $DestinationPath = $FilesInput[$File].Destination
                        }
                        $ResolvedDestination = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($DestinationPath)
                        Write-TextWithTime -Text "Copying file $FullFilePath to $ResolvedDestination" {
                            $DirectoryPath = [Io.Path]::GetDirectoryName($ResolvedDestination)
                            $null = New-Item -ItemType Directory -Force -ErrorAction Stop -Path $DirectoryPath
                            Copy-Item -LiteralPath $FullFilePath -Destination $ResolvedDestination -Force -ErrorAction Stop
                        } -PreAppend Addition -SpacesBefore ' ' -Color Yellow
                    }
                    else {
                        Write-TextWithTime -Text "File $FullFilePath does not exist" -PreAppend Plus -SpacesBefore ' ' -Color Red -ColorTime Red -ColorBefore Red
                        return $false
                    }
                }
            }
        }
    }
}
function Copy-ArtefactRequiredFolders {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $FoldersInput,
        [string] $ProjectPath,
        [string] $Destination,
        [nullable[bool]] $DestinationRelative
    )
}
function Copy-ArtefactRequiredModule {
    [CmdletBinding()]
    param(
        [switch] $Enabled,
        [Array] $RequiredModules,
        [string] $Destination
    )
    if (-not $Enabled) {
        return
    }
    if (-not (Test-Path -LiteralPath $Destination)) {
        New-Item -ItemType Directory -Path $Destination -Force
    }
    foreach ($Module in $RequiredModules) {
        if ($Module.ModuleName) {
            Write-TextWithTime -PreAppend Addition -Text "Copying required module $($Module.ModuleName)" -Color Yellow {
                $ModulesFound = Get-Module -ListAvailable -Name $Module.ModuleName
                if ($ModulesFound.Count -gt 0) {
                    $PathToPSD1 = if ($Module.ModuleVersion -eq 'Latest') {
                        $ModulesFound[0].Path
                    }
                    else {
                        $FoundModule = foreach ($M in $ModulesFound) {
                            if ($M.Version -eq $Module.ModuleVersion) {
                                $M.Path
                                break
                            }
                        }
                        if (-not $FoundModule) {

                            $ModulesFound[0].Path
                        }
                        else {
                            $FoundModule
                        }
                    }
                    $FolderToCopy = [System.IO.Path]::GetDirectoryName($PathToPSD1)
                    $ItemInformation = Get-Item -LiteralPath $FolderToCopy

                    if ($ItemInformation.Name -ne $Module.ModuleName) {
                        $NewPath = [io.path]::Combine($Destination, $Module.ModuleName)
                        if (Test-Path -LiteralPath $NewPath) {
                            Remove-Item -LiteralPath $NewPath -Recurse -Force -ErrorAction Stop
                        }
                        Copy-Item -LiteralPath $FolderToCopy -Destination $NewPath -Recurse -Force -ErrorAction Stop
                    }
                    else {
                        Copy-Item -LiteralPath $FolderToCopy -Destination $Destination -Recurse -Force
                    }
                }
            } -SpacesBefore ' '
        }
    }
}
function Copy-ArtefactToScript {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Configuration,
        [string] $ModuleVersion,
        [switch] $Enabled,
        [nullable[bool]] $IncludeTagName,
        [string] $ModuleName,
        [string] $Destination,
        [string] $PreScriptMerge,
        [string] $PostScriptMerge,
        [string] $ScriptName
    )
    if (-not $Enabled) {
        return
    }

    if ($PSVersionTable.PSVersion.Major -gt 5) {
        $Encoding = 'UTF8BOM'
    }
    else {
        $Encoding = 'UTF8'
    }

    if ($IncludeTagName) {
        $NameOfDestination = [io.path]::Combine($Destination, $TagName)
    }
    else {
        $NameOfDestination = [io.path]::Combine($Destination)
    }
    $ResolvedDestination = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($NameOfDestination)
    Write-TextWithTime -PreAppend Addition -Text "Copying main module to $ResolvedDestination" -Color Yellow {
        if (Test-Path -Path $NameOfDestination) {
            Remove-ItemAlternative -LiteralPath $NameOfDestination -ErrorAction Stop
        }
        $null = New-Item -ItemType Directory -Path $Destination -Force

        if ($DestinationPaths.Desktop) {
            $DestinationToUse = [System.IO.Path]::Combine($DestinationPaths.Desktop, "*")
            Copy-Item -Path $DestinationToUse -Recurse -Destination "$ResolvedDestination" -Force
        }
        elseif ($DestinationPaths.Core) {
            $DestinationToUse = [System.IO.Path]::Combine($DestinationPaths.Core, "*")
            Copy-Item -Path $DestinationToUse -Recurse -Destination "$ResolvedDestination" -Force
        }
    } -SpacesBefore ' '
    Write-TextWithTime -PreAppend Addition -Text "Cleaning up main module" -Color Yellow {
        $PSD1 = [io.path]::Combine($ResolvedDestination, "$ModuleName.psd1")
        Remove-Item -LiteralPath $PSD1 -Force -ErrorAction Stop
        $PSM1 = [io.path]::Combine($ResolvedDestination, "$ModuleName.psm1")
        if ($ScriptName) {

            $TagName = "v$($ModuleVersion)"
            if ($Configuration.CurrentSettings.PreRelease) {
                $ModuleVersionWithPreRelease = "$($ModuleVersion)-$($Configuration.CurrentSettings.PreRelease)"
                $TagModuleVersionWithPreRelease = "v$($ModuleVersionWithPreRelease)"
            }
            else {
                $ModuleVersionWithPreRelease = $ModuleVersion
                $TagModuleVersionWithPreRelease = "v$($ModuleVersion)"
            }

            $ScriptName = $ScriptName.Replace('{ModuleName}', $ModuleName)
            $ScriptName = $ScriptName.Replace('<ModuleName>', $ModuleName)
            $ScriptName = $ScriptName.Replace('{ModuleVersion}', $ModuleVersion)
            $ScriptName = $ScriptName.Replace('<ModuleVersion>', $ModuleVersion)
            $ScriptName = $ScriptName.Replace('{ModuleVersionWithPreRelease}', $ModuleVersionWithPreRelease)
            $ScriptName = $ScriptName.Replace('<ModuleVersionWithPreRelease>', $ModuleVersionWithPreRelease)
            $ScriptName = $ScriptName.Replace('{TagModuleVersionWithPreRelease}', $TagModuleVersionWithPreRelease)
            $ScriptName = $ScriptName.Replace('<TagModuleVersionWithPreRelease>', $TagModuleVersionWithPreRelease)
            $ScriptName = $ScriptName.Replace('{TagName}', $TagName)
            $ScriptName = $ScriptName.Replace('<TagName>', $TagName)

            if ($ScriptName.EndsWith(".ps1")) {
                $PS1 = [io.path]::Combine($ResolvedDestination, "$ScriptName")
                Rename-Item -LiteralPath $PSM1 -NewName "$ScriptName" -Force -ErrorAction Stop
            }
            else {
                $PS1 = [io.path]::Combine($ResolvedDestination, "$ScriptName.ps1")
                Rename-Item -LiteralPath $PSM1 -NewName "$ScriptName.ps1" -Force -ErrorAction Stop
            }
        }
        else {
            $PS1 = [io.path]::Combine($ResolvedDestination, "$ModuleName.ps1")

            Rename-Item -LiteralPath $PSM1 -NewName "$ModuleName.ps1" -Force -ErrorAction Stop
        }
        $Content = Get-Content -LiteralPath $PS1 -ErrorAction Stop -Encoding UTF8

        $index = ($Content | Select-String -Pattern "# Export functions and aliases as required" -SimpleMatch | Select-Object -Last 1).LineNumber

        $Content = $Content[0..($index - 2)]

        if ($PreScriptMerge) {
            $Content = @(
                $PreScriptMerge.Trim()
                $Content
            )
        }
        if ($PostScriptMerge) {
            $Content = @(
                $Content
                $PostScriptMerge.Trim()
            )
        }

        Set-Content -LiteralPath $PS1 -Value $Content -Force -ErrorAction Stop -Encoding $Encoding
    } -SpacesBefore ' '
}
function Copy-DictionaryManual {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Dictionary
    )

    $clone = @{}
    foreach ($Key in $Dictionary.Keys) {
        $value = $Dictionary.$Key

        $clonedValue = switch ($Dictionary.$Key) {
            { $null -eq $_ } {
                $null
                continue
            }
            { $_ -is [System.Collections.IDictionary] } {
                Copy-DictionaryManual -Dictionary $_
                continue
            }
            {
                $type = $_.GetType()
                $type.IsPrimitive -or $type.IsValueType -or $_ -is [string]
            } {
                $_
                continue
            }
            default {
                $_ | Select-Object -Property *
            }
        }

        if ($value -is [System.Collections.IList]) {
            $clone[$Key] = @($clonedValue)
        }
        else {
            $clone[$Key] = $clonedValue
        }
    }

    $clone
}
function Copy-InternalDictionary {
    [cmdletbinding()]
    param(
        [System.Collections.IDictionary] $Dictionary
    )

    $ms = [System.IO.MemoryStream]::new()
    $bf = [System.Runtime.Serialization.Formatters.Binary.BinaryFormatter]::new()
    $bf.Serialize($ms, $Dictionary)
    $ms.Position = 0
    $clone = $bf.Deserialize($ms)
    $ms.Close()
    $clone
}
function Copy-InternalFiles {
    [CmdletBinding()]
    param(
        [string[]] $LinkFiles,
        [string] $FullModulePath,
        [string] $FullProjectPath,
        [switch] $Delete
    )

    foreach ($File in $LinkFiles) {
        [string] $Path = [System.IO.Path]::Combine($FullModulePath, $File)
        [string] $Path2 = [System.IO.Path]::Combine($FullProjectPath, $File)

        if ($Delete) {
            if (Test-ReparsePoint -path $Path) {

                Remove-Item $Path -Confirm:$false
            }
        }

        Copy-Item -Path $Path2 -Destination $Path -Force -Recurse -Confirm:$false
    }
}
function Export-PSData {
    <#
    .Synopsis
        Exports property bags into a data file
    .Description
        Exports property bags and the first level of any other object into a ps data file (.psd1)
    .Link
        https://github.com/StartAutomating/Pipeworks
        Import-PSData
    .Example
        Get-Web -Url http://www.youtube.com/watch?v=xPRC3EDR_GU -AsMicrodata -ItemType http://schema.org/VideoObject |
            Export-PSData .\PipeworksQuickstart.video.psd1
    #>

    [OutputType([IO.FileInfo])]
    [cmdletbinding()]
    param(
        # The data that will be exported
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)][PSObject[]]$InputObject,
        # The path to the data file
        [Parameter(Mandatory = $true, Position = 0)][string] $DataFile,
        [switch] $Sort
    )
    begin {
        $AllObjects = [System.Collections.Generic.List[object]]::new()
    }
    process {
        $AllObjects.AddRange($InputObject)
    }
    end {
        if ($PSVersionTable.PSVersion.Major -gt 5) {
            $Encoding = 'UTF8BOM'
        }
        else {
            $Encoding = 'UTF8'
        }

        $Text = $AllObjects | Write-PowerShellHashtable -Sort:$Sort.IsPresent
        $Text | Out-File -FilePath $DataFile -Encoding $Encoding
    }
}
function Find-NetFramework {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER RequireVersion
    Parameter description
 
    .EXAMPLE
    Find-NetFramework -RequireVersion 4.8
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [string] $RequireVersion
    )
    if ($RequireVersion) {
        $Framework = [ordered] @{
            '4.5'   = 378389
            '4.5.1'    = 378675
            '4.5.2'    = 379893
            '4.6'   = 393295
            '4.6.1'    = 394254
            '4.6.2'    = 394802
            '4.7'   = 460798
            '4.7.1'    = 461308
            '4.7.2'    = 461808
            '4.8'   = 528040
        }
        $DetectVersion = $Framework[$RequireVersion]

        "if (`$PSVersionTable.PSEdition -eq 'Desktop' -and (Get-ItemProperty `"HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full`").Release -lt $DetectVersion) { Write-Warning `"This module requires .NET Framework $RequireVersion or later.`"; return } "
    }
}
function Find-RequiredModules {
    [cmdletbinding()]
    param(
        [string] $Name
    )
    $Module = Get-Module -ListAvailable $Name -ErrorAction SilentlyContinue -Verbose:$false
    $AllModules = if ($Module) {
        [Array] $RequiredModules = $Module.RequiredModules.Name
        if ($null -ne $RequiredModules) {
            $null
        }
        $RequiredModules
        foreach ($_ in $RequiredModules) {
            Find-RequiredModules -Name $_
        }
    }

    [Array] $ListModules = $AllModules | Where-Object { $null -ne $_ }
    if ($null -ne $ListModules) {
        [array]::Reverse($ListModules)
    }
    $CleanedModules = [System.Collections.Generic.List[string]]::new()
    foreach ($_ in $ListModules) {
        if ($CleanedModules -notcontains $_) {
            $CleanedModules.Add($_)
        }
    }
    $CleanedModules
}
function Format-Code {
    [cmdletbinding()]
    param(
        [string] $FilePath,
        [System.Collections.IDictionary] $FormatCode
    )

    if ($PSVersionTable.PSVersion.Major -gt 5) {
        $Encoding = 'UTF8BOM'
    }
    else {
        $Encoding = 'UTF8'
    }

    if ($FormatCode.Enabled) {
        if ($FormatCode.RemoveComments -or $FormatCode.RemoveCommentsInParamBlock -or $FormatCode.RemoveCommentsBeforeParamBlock) {
            $Output = Write-TextWithTime -Text "[+] Removing Comments - $FilePath" {
                $removeCommentsSplat = @{
                    SourceFilePath                 = $FilePath
                    RemoveCommentsInParamBlock     = $FormatCode.RemoveCommentsInParamBlock
                    RemoveCommentsBeforeParamBlock = $FormatCode.RemoveCommentsBeforeParamBlock
                    RemoveAllEmptyLines            = $FormatCode.RemoveAllEmptyLines
                    RemoveEmptyLines               = $FormatCode.RemoveEmptyLines
                }
                Remove-Comments @removeCommentsSplat
            }
        }
        elseif ($FormatCode.RemoveAllEmptyLines -or $FormatCode.RemoveEmptyLines) {
            $Output = Write-TextWithTime -Text "[+] Removing Empty Lines - $FilePath" {
                $removeEmptyLinesSplat = @{
                    SourceFilePath      = $FilePath
                    RemoveAllEmptyLines = $FormatCode.RemoveAllEmptyLines
                    RemoveEmptyLines    = $FormatCode.RemoveEmptyLines
                }
                Remove-EmptyLines @removeEmptyLinesSplat
            }
        }
        else {
            $Output = Write-TextWithTime -Text "Reading file content - $FilePath" {
                Get-Content -LiteralPath $FilePath -Raw -Encoding UTF8
            } -PreAppend Plus -SpacesBefore ' '
        }
        if ($null -eq $FormatCode.FormatterSettings) {
            $FormatCode.FormatterSettings = $Script:FormatterSettings
        }
        $Data = Write-TextWithTime -Text "Formatting file - $FilePath" {
            try {
                Invoke-Formatter -ScriptDefinition $Output -Settings $FormatCode.FormatterSettings 
            }
            catch {
                $ErrorMessage = $_.Exception.Message

                Write-Host 
                Write-Text " [-] Format-Code - Formatting on file $FilePath failed." -Color Red
                Write-Text " [-] Format-Code - Error: $ErrorMessage" -Color Red
                Write-Text " [-] Format-Code - This is most likely related to a bug in PSScriptAnalyzer running inside VSCode. Please try running outside of VSCode when using formatting." -Color Red
                return $false
            }
        } -PreAppend Plus -SpacesBefore ' '
        if ($Data -eq $false) {
            return $false
        }
        Write-TextWithTime -Text "Saving file - $FilePath" {

            $Final = foreach ($O in $Data) {
                if ($O.Trim() -ne '') {
                    $O.Trim()
                }
            }
            try {
                $Final | Out-File -LiteralPath $FilePath -NoNewline -Encoding $Encoding -ErrorAction Stop
            }
            catch {
                $ErrorMessage = $_.Exception.Message

                Write-Host 
                Write-Text "[-] Format-Code - Resaving file $FilePath failed. Error: $ErrorMessage" -Color Red
                return $false
            }
        } -PreAppend Plus -SpacesBefore ' '
    }
}

function Format-UsingNamespace {
    [CmdletBinding()]
    param(
        [string] $FilePath,
        [string] $FilePathSave,
        [string] $FilePathUsing
    )

    if ($PSVersionTable.PSVersion.Major -gt 5) {
        $Encoding = 'UTF8BOM'
    }
    else {
        $Encoding = 'UTF8'
    }

    if ($FilePathSave -eq '') {
        $FilePathSave = $FilePath
    }
    if ($FilePath -ne '' -and (Test-Path -Path $FilePath) -and (Get-Item -LiteralPath $FilePath).Length -gt 0kb) {
        $FileStream = New-Object -TypeName IO.FileStream -ArgumentList ($FilePath), ([System.IO.FileMode]::Open), ([System.IO.FileAccess]::Read), ([System.IO.FileShare]::ReadWrite);
        $ReadFile = New-Object -TypeName System.IO.StreamReader -ArgumentList ($FileStream, [System.Text.Encoding]::UTF8, $true);

        $UsingNamespaces = [System.Collections.Generic.List[string]]::new()

        $Content = while (!$ReadFile.EndOfStream) {
            $Line = $ReadFile.ReadLine()
            if ($Line -like 'using namespace*') {
                $UsingNamespaces.Add($Line)
            }
            else {
                $Line
            }
        }
        $ReadFile.Close()

        $null = New-Item -Path $FilePathSave -ItemType file -Force
        if ($UsingNamespaces) {

            $null = New-Item -Path $FilePathUsing -ItemType file -Force
            $UsingNamespaces = $UsingNamespaces.Trim() | Sort-Object -Unique
            $UsingNamespaces | Out-File -Append -LiteralPath $FilePathUsing -Encoding $Encoding

            $Content | Out-File -Append -LiteralPath $FilePathSave -Encoding $Encoding
            return $true
        }
        else {
            $Content | Out-File -Append -LiteralPath $FilePathSave -Encoding $Encoding
            return $False
        }
    }
}
function Get-AstTokens {
    [cmdletBinding()]
    param(
        [System.Management.Automation.Language.Token[]] $ASTTokens,
        [System.Collections.Generic.List[Object]] $Commands
    )
    foreach ($_ in $astTokens) {
        if ($_.TokenFlags -eq 'Command' -and $_.Kind -eq 'Generic') {
            if ($_.Value -notin $Commands) {
                $Commands.Add($_)
            }
        }
        else {
            if ($_.NestedTokens) {
                Get-AstTokens -ASTTokens $_.NestedTokens -Commands $Commands
            }
        }
    }
}
function Get-Encoding {
    [cmdletBinding()]
    param    (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)][Alias('FullName')][string] $Path
    )
    process {
        $bom = New-Object -TypeName System.Byte[](4)

        $file = New-Object System.IO.FileStream($Path, 'Open', 'Read')

        $null = $file.Read($bom, 0, 4)
        $file.Close()
        $file.Dispose()

        $enc = [Text.Encoding]::ASCII
        if ($bom[0] -eq 0x2b -and $bom[1] -eq 0x2f -and $bom[2] -eq 0x76) {
            $enc = [Text.Encoding]::UTF7 
        }
        if ($bom[0] -eq 0xff -and $bom[1] -eq 0xfe) {
            $enc = [Text.Encoding]::Unicode 
        }
        if ($bom[0] -eq 0xfe -and $bom[1] -eq 0xff) {
            $enc = [Text.Encoding]::BigEndianUnicode 
        }
        if ($bom[0] -eq 0x00 -and $bom[1] -eq 0x00 -and $bom[2] -eq 0xfe -and $bom[3] -eq 0xff) {
            $enc = [Text.Encoding]::UTF32 
        }
        if ($bom[0] -eq 0xef -and $bom[1] -eq 0xbb -and $bom[2] -eq 0xbf) {
            $enc = [Text.Encoding]::UTF8 
        }

        [PSCustomObject]@{
            Encoding = $enc
            Path     = $Path
        }
    }
}
function Get-FilteredScriptCommands {
    [CmdletBinding()]
    param(
        [Array] $Commands,
        [switch] $NotCmdlet,
        [switch] $NotUnknown,
        [switch] $NotApplication,
        [string[]] $Functions,
        [string] $FilePath,
        [string[]] $ApprovedModules
    )
    if ($Functions.Count -eq 0) {
        $Functions = Get-FunctionNames -Path $FilePath
    }
    $Commands = $Commands | Where-Object { $_ -notin $Functions }
    $Commands = $Commands | Sort-Object -Unique
    $Scan = foreach ($Command in $Commands) {
        try {
            $IsAlias = $false
            $Data = Get-Command -Name $Command -ErrorAction Stop
            if ($Data.Source -eq 'PSPublishModule') {

                if ($Data.Source -notin $ApprovedModules) {
                    throw
                }
            }
            if ($Data.CommandType -eq 'Alias') {
                $Data = Get-Command -Name $Data.Definition
                $IsAlias = $true
            }
            [PSCustomObject] @{
                Name        = $Data.Name
                Source      = $Data.Source
                CommandType = $Data.CommandType
                IsAlias     = $IsAlias
                IsPrivate   = $false
                Error       = ''
                ScriptBlock = $Data.ScriptBlock
            }
        }
        catch {
            $CurrentOutput = [PSCustomObject] @{
                Name        = $Command
                Source      = ''
                CommandType = ''
                IsAlias     = $IsAlias
                IsPrivate   = $false
                Error       = $_.Exception.Message
                ScriptBlock = ''
            }

            foreach ($ApprovedModule in $ApprovedModules) {
                try {
                    $ImportModuleWithPrivateCommands = Import-Module -PassThru -Name $ApprovedModule -ErrorAction Stop -Verbose:$false
                    $Data = & $ImportModuleWithPrivateCommands { param($command); Get-Command $command -Verbose:$false -ErrorAction Stop } $command
                    $CurrentOutput = [PSCustomObject] @{
                        Name        = $Data.Name
                        Source      = $Data.Source
                        CommandType = $Data.CommandType
                        IsAlias     = $IsAlias
                        IsPrivate   = $true
                        Error       = ''
                        ScriptBlock = $Data.ScriptBlock
                    }
                    break
                }
                catch {
                    $CurrentOutput = [PSCustomObject] @{
                        Name        = $Command
                        Source      = ''
                        CommandType = ''
                        IsAlias     = $IsAlias
                        IsPrivate   = $false
                        Error       = $_.Exception.Message
                        ScriptBlock = ''
                    }
                }
            }
            $CurrentOutput
        }
    }
    $Filtered = foreach ($Command in $Scan) {
        if ($Command.Source -eq 'Microsoft.PowerShell.Core') {

            continue
        }
        if ($NotCmdlet -and $NotUnknown -and $NotApplication) {
            if ($Command.CommandType -ne 'Cmdlet' -and $Command.Source -ne '' -and $Command.CommandType -ne 'Application') {
                $Command
            }
        }
        elseif ($NotCmdlet -and $NotUnknown) {
            if ($Command.CommandType -ne 'Cmdlet' -and $Command.Source -ne '') {
                $Command
            }
        }
        elseif ($NotCmdlet) {
            if ($Command.CommandType -ne 'Cmdlet') {
                $Command
            }
        }
        elseif ($NotUnknown) {
            if ($Command.Source -ne '') {
                $Command
            }
        }
        elseif ($NotApplication) {
            if ($Command.CommandType -ne 'Application') {
                $Command
            }
        }
        else {
            $Command
        }
    }
    $Filtered
}

Function Get-FunctionAliases {
    [cmdletbinding()]
    param (
        [Alias('PSPath', 'FullName')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)][string[]]$Path,
        [string] $Content,
        [switch] $RecurseFunctionNames,
        [switch] $AsHashtable
    )
    process {
        if ($Content) {
            $ProcessData = $Content
            $Code = $true
        }
        else {
            $ProcessData = $Path
            $Code = $false
        }
        foreach ($File in $ProcessData) {
            $Ast = $null
            if ($Code) {
                $FileAst = [System.Management.Automation.Language.Parser]::ParseInput($File, [ref]$null, [ref]$null)
            }
            else {
                $FileAst = [System.Management.Automation.Language.Parser]::ParseFile($File , [ref]$null, [ref]$null)
            }

            $ListOfFuncionsAst = $FileAst.FindAll( {
                    param ($ast)
                    $ast -is [System.Management.Automation.Language.FunctionDefinitionAst]
                }, $RecurseFunctionNames
            )

            if ($AsHashtable) {

                $OutputList = [ordered] @{}
                foreach ($Function in $ListOfFuncionsAst) {
                    $AliasDefinitions = $Function.FindAll( {
                            param ( $ast )
                            $ast -is [System.Management.Automation.Language.AttributeAst] -and
                            $ast.TypeName.Name -eq 'Alias' -and
                            $ast.Parent -is [System.Management.Automation.Language.ParamBlockAst]
                        }, $true)

                    $AliasTarget = @(
                        $AliasDefinitions.PositionalArguments.Value
                        foreach ($_ in  $AliasDefinitions.Parent.CommandElements) {
                            if ($_.StringConstantType -eq 'BareWord' -and $null -ne $_.Value -and $_.Value -notin ('New-Alias', 'Set-Alias', $Function.Name)) {
                                $_.Value
                            }
                        }
                    )
                    $OutputList[$Function.Name] = $AliasTarget
                }
                $OutputList
            }
            else {

                $Ast = $Null
                $AliasDefinitions = $FileAst.FindAll( {
                        param ( $ast )
                        $ast -is [System.Management.Automation.Language.AttributeAst] -and
                        $ast.TypeName.Name -eq 'Alias' -and
                        $ast.Parent -is [System.Management.Automation.Language.ParamBlockAst]
                    }, $true)

                $AliasTarget = @(
                    $AliasDefinitions.PositionalArguments.Value
                    foreach ($_ in  $AliasDefinitions.Parent.CommandElements) {
                        if ($_.StringConstantType -eq 'BareWord' -and $null -ne $_.Value -and $_.Value -notin ('New-Alias', 'Set-Alias', $FunctionName)) {
                            $_.Value
                        }
                    }
                )
                [PsCustomObject]@{
                    Name  = $ListOfFuncionsAst.Name
                    Alias = $AliasTarget
                }
            }
        }
    }
}
function Get-FunctionAliasesFromFolder {
    [cmdletbinding()]
    param(
        [string] $FullProjectPath,
        [string[]] $Folder,
        [Array] $Files
    )
    $FilesPS1 = foreach ($File in $Files) {
        $Path = [io.path]::Combine("*", 'Public', '*')
        if ($file.FullName -like $Path) {
            if ($File.Extension -eq '.ps1' -or $File.Extension -eq '*.psm1') {
                $File
            }
        }
    }
    [Array] $Content = foreach ($File in $FilesPS1) {
        ''
        Get-Content -LiteralPath $File.FullName -Raw -Encoding UTF8
    }
    $Code = $Content -join [System.Environment]::NewLine

    $AliasesToExport = Get-FunctionAliases -Content $Code -AsHashtable
    $AliasesToExport
}
function Get-FunctionNames {
    [cmdletbinding()]
    param(
        [string] $Path,
        [switch] $Recurse
    )
    if ($Path -ne '' -and (Test-Path -LiteralPath $Path)) {
        $FilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
        [System.Management.Automation.Language.Parser]::ParseFile(($FilePath),
            [ref]$null,
            [ref]$null).FindAll(
            { param($c)$c -is [Management.Automation.Language.FunctionDefinitionAst] }, $Recurse).Name
    }
}
function Get-GitLog {
    # Source https://gist.github.com/thedavecarroll/3245449f5ff893e51907f7aa13e33ebe
    # Author: thedavecarroll/Get-GitLog.ps1
    [CmdLetBinding(DefaultParameterSetName = 'Default')]
    param (

        [Parameter(ParameterSetName = 'Default', Mandatory)]
        [Parameter(ParameterSetName = 'SourceTarget', Mandatory)]
        [ValidateScript({ Resolve-Path -Path $_ | Test-Path })]
        [string]$GitFolder,

        [Parameter(ParameterSetName = 'SourceTarget', Mandatory)]
        [string]$StartCommitId,
        [Parameter(ParameterSetName = 'SourceTarget')]
        [string]$EndCommitId = 'HEAD'
    )

    Push-Location
    try {
        Set-Location -Path $GitFolder
        $GitCommand = Get-Command -Name git -ErrorAction Stop
    }
    catch {
        $PSCmdlet.ThrowTerminatingError($_)
    }

    if ($StartCommitId) {
        $GitLogCommand = '"{0}" log --oneline --format="%H`t%h`t%ai`t%an`t%ae`t%ci`t%cn`t%ce`t%s`t%f" {1}...{2} 2>&1' -f $GitCommand.Source, $StartCommitId, $EndCommitId
    }
    else {
        $GitLogCommand = '"{0}" log --oneline --format="%H`t%h`t%ai`t%an`t%ae`t%ci`t%cn`t%ce`t%s`t%f" 2>&1' -f $GitCommand.Source
    }

    Write-Verbose -Message $GitLogCommand
    $GitLog = Invoke-Expression -Command "& $GitLogCommand" -ErrorAction SilentlyContinue
    Pop-Location

    if ($GitLog[0] -notmatch 'fatal:') {
        $GitLog | ConvertFrom-Csv -Delimiter "`t" -Header 'CommitId', 'ShortCommitId', 'AuthorDate', 'AuthorName', 'AuthorEmail', 'CommitterDate', 'CommitterName', 'ComitterEmail', 'CommitMessage', 'SafeCommitMessage'
    }
    else {
        if ($GitLog[0] -like "fatal: ambiguous argument '*...*'*") {
            Write-Warning -Message 'Unknown revision. Please check the values for StartCommitId or EndCommitId; omit the parameters to retrieve the entire log.'
        }
        else {
            Write-Error -Category InvalidArgument -Message ($GitLog -join "`n")
        }
    }
}
function Get-RecursiveCommands {
    [CmdletBinding()]
    param(
        [Array] $Commands
    )
    $Another = foreach ($Command in $Commands) {
        if ($Command.ScriptBlock) {
            Get-ScriptCommands -Code $Command.ScriptBlock -CommandsOnly
        }
    }

    $filter = Get-FilteredScriptCommands -Commands $Another -NotUnknown -NotCmdlet
    [Array] $ProcessedCommands = foreach ($_ in $Filter) {
        if ($_.Name -notin $ListCommands.Name) {
            $ListCommands.Add($_)
            $_
        }
    }
    if ($ProcessedCommands.Count -gt 0) {
        Get-RecursiveCommands -Commands $ProcessedCommands
    }
}
function Get-RequiredModule {
    [cmdletbinding()]
    param(
        [string] $Path,
        [string] $Name
    )
    $LiteralPath = [System.IO.Path]::Combine($Path, $Name)
    $PrimaryModule = Get-ChildItem -LiteralPath $LiteralPath -Filter '*.psd1' -Recurse -ErrorAction SilentlyContinue -Depth 1
    if ($PrimaryModule) {
        $Module = Get-Module -ListAvailable $PrimaryModule.FullName -ErrorAction SilentlyContinue -Verbose:$false
        if ($Module) {
            [Array] $RequiredModules = $Module.RequiredModules.Name
            if ($null -ne $RequiredModules) {
                $null
            }
            $RequiredModules
            foreach ($_ in $RequiredModules) {
                Get-RequiredModule -Path $Path -Name $_
            }
        }
    }
    else {
        Write-Warning "Initialize-ModulePortable - Modules to load not found in $Path"
    }
}
function Get-RestMethodExceptionDetailsOrNull {
    [CmdletBinding()]
    param(
        [Exception] $restMethodException
    )
    try {
        $responseDetails = @{
            ResponseUri       = $exception.Response.ResponseUri
            StatusCode        = $exception.Response.StatusCode
            StatusDescription = $exception.Response.StatusDescription
            ErrorMessage      = $exception.Message
        }
        [string] $responseDetailsAsNicelyFormattedString = Convert-HashTableToNicelyFormattedString $responseDetails

        [string] $errorInfo = "Request Details:" + $NewLine + $requestDetailsAsNicelyFormattedString
        $errorInfo += $NewLine
        $errorInfo += "Response Details:" + $NewLine + $responseDetailsAsNicelyFormattedString
        return $errorInfo
    }
    catch {
        return $null
    }
}

function Get-ScriptCommands {
    [CmdletBinding()]
    param(
        [string] $FilePath,
        [alias('ScriptBlock')][scriptblock] $Code,
        [switch] $CommandsOnly
    )
    $astTokens = $null
    $astErr = $null

    if ($FilePath) {
        $null = [System.Management.Automation.Language.Parser]::ParseFile($FilePath, [ref]$astTokens, [ref]$astErr)
    }
    else {
        $null = [System.Management.Automation.Language.Parser]::ParseInput($Code, [ref]$astTokens, [ref]$astErr)
    }
    $Commands = [System.Collections.Generic.List[Object]]::new()
    Get-AstTokens -ASTTokens $astTokens -Commands $Commands
    if ($CommandsOnly) {
        $Commands.Value | Sort-Object -Unique
    }
    else {
        $Commands
    }
}
function Get-ScriptsContentAndTryReplace {
    <#
    .SYNOPSIS
    Gets script content and replaces $PSScriptRoot\..\..\ with $PSScriptRoot\
 
    .DESCRIPTION
    Gets script content and replaces $PSScriptRoot\..\..\ with $PSScriptRoot\
 
    .PARAMETER Files
    Parameter description
 
    .PARAMETER OutputPath
    Parameter description
 
    .EXAMPLE
    Get-ScriptsContentAndTryReplace -Files 'C:\Support\GitHub\PSWriteHTML\Private\Get-HTMLLogos.ps1' -OutputPath "C:\Support\GitHub\PSWriteHTML\Private\Get-HTMLLogos1.ps1"
 
    .NOTES
    Often in code people would use relative paths to get to the root of the module.
    This is all great but the path changes during merge.
    So we fix this by replacing $PSScriptRoot\..\..\ with $PSScriptRoot\
    While in best case they should always use $MyInvocation.MyCommand.Module.ModuleBase
    It's not always possible. So this is a workaround.
    Very bad workaround, but it works, but may have unintended consequences.
 
    $Content = @(
        '$PSScriptRoot\..\..\Build\Manage-PSWriteHTML.ps1'
        '$PSScriptRoot\..\Build\Manage-PSWriteHTML.ps1'
        '$PSScriptRoot\Build\Manage-PSWriteHTML.ps1'
        "[IO.Path]::Combine(`$PSScriptRoot, '..', 'Images')"
        "[IO.Path]::Combine(`$PSScriptRoot,'..','Images')"
    )
    $Content = $Content -replace [regex]::Escape('$PSScriptRoot\..\..\'), '$PSScriptRoot\' -replace [regex]::Escape('$PSScriptRoot\..\'), '$PSScriptRoot\'
    $Content = $Content -replace [regex]::Escape("`$PSScriptRoot, '..',"), '$PSScriptRoot,' -replace [regex]::Escape("`$PSScriptRoot,'..',"), '$PSScriptRoot,'
    $Content
 
    #>

    [cmdletbinding()]
    param(
        [string[]] $Files,
        [string] $OutputPath,
        [switch] $DoNotAttemptToFixRelativePaths
    )

    if ($PSVersionTable.PSVersion.Major -gt 5) {
        $Encoding = 'UTF8BOM'
    }
    else {
        $Encoding = 'UTF8'
    }

    if ($DoNotAttemptToFixRelativePaths) {
        Write-TextWithTime -Text "Without expanding variables (`$PSScriptRoot\..\.. etc.)" {
            foreach ($FilePath in $Files) {
                $Content = Get-Content -Path $FilePath -Raw -Encoding utf8
                if ($Content.Count -gt 0) {
                    try {
                        $Content | Out-File -Append -LiteralPath $OutputPath -Encoding $Encoding
                    }
                    catch {
                        $ErrorMessage = $_.Exception.Message
                        Write-Text "[-] Get-ScriptsContentAndTryReplace - Merge on file $FilePath failed. Error: $ErrorMessage" -Color Red
                        return $false
                    }
                }
            }
        } -PreAppend Plus -Color Green -SpacesBefore " " -ColorTime Green
    }
    else {
        Write-TextWithTime -Text "Replacing expandable variables (`$PSScriptRoot\..\.. etc.)" {
            foreach ($FilePath in $Files) {
                $Content = Get-Content -Path $FilePath -Raw -Encoding utf8
                if ($Content.Count -gt 0) {

                    if (-not $FilePath.EndsWith('Get-ScriptsContentAndTryReplace.ps1')) {
                        $Content = $Content -replace [regex]::Escape('$PSScriptRoot\..\..\'), '$PSScriptRoot\'
                        $Content = $Content -replace [regex]::Escape('$PSScriptRoot\..\'), '$PSScriptRoot\'
                        $Content = $Content -replace [regex]::Escape("`$PSScriptRoot, '..',"), '$PSScriptRoot,'
                        $Content = $Content -replace [regex]::Escape("`$PSScriptRoot,'..',"), '$PSScriptRoot,'
                    }
                }
                try {
                    $Content | Out-File -Append -LiteralPath $OutputPath -Encoding $Encoding
                }
                catch {
                    $ErrorMessage = $_.Exception.Message
                    Write-Text "[-] Get-ScriptsContentAndTryReplace - Merge on file $FilePath failed. Error: $ErrorMessage" -Color Red
                    return $false
                }
            }
        } -PreAppend Information -Color Magenta -SpacesBefore " " -ColorBefore Magenta -ColorTime Magenta
    }
}

function Import-ValidCertificate {
    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")]
    param(
        [parameter(Mandatory, ParameterSetName = 'FilePath')][string] $FilePath,
        [parameter(Mandatory, ParameterSetName = 'Base64')][string] $CertificateAsBase64,
        [parameter(Mandatory)][string] $PfxPassword
    )
    if ($FilePath -and (Test-Path -LiteralPath $FilePath)) {
        $TemporaryFile = $FilePath
    }
    elseif ($CertificateAsBase64) {
        $TemporaryFile = [io.path]::GetTempFileName()
        if ($PSVersionTable.PSEdition -eq 'Core') {
            Set-Content -AsByteStream -Value $([System.Convert]::FromBase64String($CertificateAsBase64)) -Path $TemporaryFile -ErrorAction Stop
        }
        else {
            Set-Content -Value $([System.Convert]::FromBase64String($CertificateAsBase64)) -Path $TemporaryFile -Encoding Byte -ErrorAction Stop
        }
    }
    else {
        return $false
    }
}
function Initialize-InternalTests {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Configuration,
        [string] $Type
    )

    if ($Configuration.Options.$Type.TestsPath -and (Test-Path -LiteralPath $Configuration.Options.$Type.TestsPath)) {
        Write-TextWithTime -PreAppend Plus -Text "Running tests ($Type)" {
            $TestsResult = Invoke-Pester -Script $Configuration.Options.$Type.TestsPath -Verbose -PassThru
            Write-Host
            if (-not $TestsResult) {
                if ($Configuration.Options.$Type.Force) {
                    Write-Text "[e] Tests ($Type) failed, but Force was used to skip failed tests. Continuing" -Color Red
                }
                else {
                    Write-Text "[e] Tests ($Type) failed. Terminating." -Color Red
                    return $false
                }
            }
            elseif ($TestsResult.FailedCount -gt 0) {
                if ($Configuration.Options.$Type.Force) {
                    Write-Text "[e] Tests ($Type) failed, but Force was used to skip failed tests. Continuing" -Color Red
                }
                else {
                    Write-Text "[e] Tests ($Type) failed (failedCount $($TestsResult.FailedCount)). Terminating." -Color Red
                    return $false
                }
            }
        } -Color Blue
    }
    else {
        Write-Text "[e] Tests ($Type) are enabled, but the path to tests doesn't exits. Terminating." -Color Red
        return $false
    }
}
function Initialize-ReplacePath {
    [CmdletBinding()]
    param(
        [string] $ReplacementPath,
        [string] $ModuleName,
        [string] $ModuleVersion,
        [System.Collections.IDictionary] $Configuration
    )

    $TagName = "v$($ModuleVersion)"
    if ($Configuration.CurrentSettings.PreRelease) {
        $ModuleVersionWithPreRelease = "$($ModuleVersion)-$($Configuration.CurrentSettings.PreRelease)"
        $TagModuleVersionWithPreRelease = "v$($ModuleVersionWithPreRelease)"
    }
    else {
        $ModuleVersionWithPreRelease = $ModuleVersion
        $TagModuleVersionWithPreRelease = "v$($ModuleVersion)"
    }

    $ReplacementPath = $ReplacementPath.Replace('<TagName>', $TagName)
    $ReplacementPath = $ReplacementPath.Replace('{TagName}', $TagName)
    $ReplacementPath = $ReplacementPath.Replace('<ModuleVersion>', $ModuleVersion)
    $ReplacementPath = $ReplacementPath.Replace('{ModuleVersion}', $ModuleVersion)
    $ReplacementPath = $ReplacementPath.Replace('<ModuleVersionWithPreRelease>', $ModuleVersionWithPreRelease)
    $ReplacementPath = $ReplacementPath.Replace('{ModuleVersionWithPreRelease}', $ModuleVersionWithPreRelease)
    $ReplacementPath = $ReplacementPath.Replace('<TagModuleVersionWithPreRelease>', $TagModuleVersionWithPreRelease)
    $ReplacementPath = $ReplacementPath.Replace('{TagModuleVersionWithPreRelease}', $TagModuleVersionWithPreRelease)
    $ReplacementPath = $ReplacementPath.Replace('<ModuleName>', $ModuleName)
    $ReplacementPath = $ReplacementPath.Replace('{ModuleName}', $ModuleName)
    $ReplacementPath
}
function Invoke-RestMethodAndThrowDescriptiveErrorOnFailure {
    [cmdletbinding()]
    param(
        [System.Collections.IDictionary] $requestParametersHashTable
    )
    $requestDetailsAsNicelyFormattedString = Convert-HashTableToNicelyFormattedString $requestParametersHashTable
    Write-Verbose "Making web request with the following parameters:$NewLine$requestDetailsAsNicelyFormattedString"

    try {
        $webRequestResult = Invoke-RestMethod @requestParametersHashTable
    }
    catch {
        [Exception] $exception = $_.Exception

        [string] $errorMessage = Get-RestMethodExceptionDetailsOrNull -restMethodException $exception
        if ([string]::IsNullOrWhiteSpace($errorMessage)) {
            $errorMessage = $exception.ToString()
        }

        throw "An unexpected error occurred while making web request:$NewLine$errorMessage"
    }

    Write-Verbose "Web request returned the following result:$NewLine$webRequestResult"
    return $webRequestResult
}

function Merge-Module {
    [CmdletBinding()]
    param (
        [string] $ModuleName,
        [string] $ModulePathSource,
        [string] $ModulePathTarget,
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [ValidateSet("ASC", "DESC", "NONE", '')]
        [string] $Sort = 'NONE',
        [string[]] $FunctionsToExport,
        [string[]] $AliasesToExport,
        [System.Collections.IDictionary] $AliasesAndFunctions,
        [Array] $LibrariesStandard,
        [Array] $LibrariesCore,
        [Array] $LibrariesDefault,
        [System.Collections.IDictionary] $FormatCodePSM1,
        [System.Collections.IDictionary] $FormatCodePSD1,
        [System.Collections.IDictionary] $Configuration,
        [string[]] $DirectoriesWithPS1,
        [string[]] $ClassesPS1,
        [System.Collections.IDictionary] $IncludeAsArray

    )

    if ($PSVersionTable.PSVersion.Major -gt 5) {
        $Encoding = 'UTF8BOM'
    }
    else {
        $Encoding = 'UTF8'
    }

    $TimeToExecute = [System.Diagnostics.Stopwatch]::StartNew()
    Write-Text "[+] Merging files into PSM1" -Color Blue

    $PSM1FilePath = [System.IO.Path]::Combine($ModulePathTarget, "$ModuleName.psm1")
    $PSD1FilePath = [System.IO.Path]::Combine($ModulePathTarget, "$ModuleName.psd1")

    [Array] $ArrayIncludes = foreach ($VariableName in $IncludeAsArray.Keys) {
        $FilePathVariables = [System.IO.Path]::Combine($ModulePathSource, $IncludeAsArray[$VariableName], "*.ps1")

        [Array] $FilesInternal = if ($PSEdition -eq 'Core') {
            Get-ChildItem -Path $FilePathVariables -ErrorAction SilentlyContinue -Recurse -FollowSymlink
        }
        else {
            Get-ChildItem -Path $FilePathVariables -ErrorAction SilentlyContinue -Recurse
        }
        "$VariableName = @("
        foreach ($Internal in $FilesInternal) {
            Get-Content -Path $Internal.FullName -Raw -Encoding utf8
        }
        ")"
    }

    if ($Configuration.Steps.BuildModule.ClassesDotSource) {
        [Array] $ListDirectoriesPS1 = foreach ($Dir in $DirectoriesWithPS1) {
            if ($Dir -ne $ClassesPS1) {
                $Dir
            }
        }
    }
    else {
        [Array] $ListDirectoriesPS1 = $DirectoriesWithPS1
    }

    [Array] $ScriptFunctions = foreach ($Directory in $ListDirectoriesPS1) {
        $PathToFiles = [System.IO.Path]::Combine($ModulePathSource, $Directory, "*.ps1")
        if ($PSEdition -eq 'Core') {
            Get-ChildItem -Path $PathToFiles -ErrorAction SilentlyContinue -Recurse -FollowSymlink
        }
        else {
            Get-ChildItem -Path $PathToFiles -ErrorAction SilentlyContinue -Recurse
        }
    }
    [Array] $ClassesFunctions = foreach ($Directory in $ClassesPS1) {
        $PathToFiles = [System.IO.Path]::Combine($ModulePathSource, $Directory, "*.ps1")
        if ($PSEdition -eq 'Core') {
            Get-ChildItem -Path $PathToFiles -ErrorAction SilentlyContinue -Recurse -FollowSymlink
        }
        else {
            Get-ChildItem -Path $PathToFiles -ErrorAction SilentlyContinue -Recurse
        }
    }
    if ($Sort -eq 'ASC') {
        $ScriptFunctions = $ScriptFunctions | Sort-Object -Property Name
        $ClassesFunctions = $ClassesFunctions | Sort-Object -Property Name
    }
    elseif ($Sort -eq 'DESC') {
        $ScriptFunctions = $ScriptFunctions | Sort-Object -Descending -Property Name
        $ClassesFunctions = $ClassesFunctions | Sort-Object -Descending -Property Name
    }

    if ($ArrayIncludes.Count -gt 0) {
        $ArrayIncludes | Out-File -Append -LiteralPath $PSM1FilePath -Encoding $Encoding
    }
    $Success = Get-ScriptsContentAndTryReplace -Files $ScriptFunctions -OutputPath $PSM1FilePath -DoNotAttemptToFixRelativePaths:$Configuration.Steps.BuildModule.DoNotAttemptToFixRelativePaths
    if ($Success -eq $false) {
        return $false
    }

    $FilePathUsing = [System.IO.Path]::Combine($ModulePathTarget, "$ModuleName.Usings.ps1")

    $UsingInPlace = Format-UsingNamespace -FilePath $PSM1FilePath -FilePathUsing $FilePathUsing
    if ($UsingInPlace) {
        $Success = Format-Code -FilePath $FilePathUsing -FormatCode $FormatCodePSM1
        if ($Success -eq $false) {
            return $false
        }
        $Configuration.UsingInPlace = "$ModuleName.Usings.ps1"
    }

    $TimeToExecute.Stop()
    Write-Text "[+] Merging files into PSM1 [Time: $($($TimeToExecute.Elapsed).Tostring())]" -Color Blue

    $TimeToExecute = [System.Diagnostics.Stopwatch]::StartNew()
    Write-Text "[+] Detecting required modules" -Color Blue

    $RequiredModules = @(
        if ($Configuration.Information.Manifest.RequiredModules.Count -gt 0) {
            if ($Configuration.Information.Manifest.RequiredModules[0] -is [System.Collections.IDictionary]) {
                $Configuration.Information.Manifest.RequiredModules.ModuleName
            }
            else {
                $Configuration.Information.Manifest.RequiredModules
            }
        }
        if ($Configuration.Information.Manifest.ExternalModuleDependencies.Count -gt 0) {
            $Configuration.Information.Manifest.ExternalModuleDependencies
        }
    )
    [Array] $ApprovedModules = $Configuration.Options.Merge.Integrate.ApprovedModules | Sort-Object -Unique

    $ModulesThatWillMissBecauseOfIntegrating = [System.Collections.Generic.List[string]]::new()
    [Array] $DependantRequiredModules = foreach ($Module in $RequiredModules) {
        [Array] $TemporaryDependant = Find-RequiredModules -Name $Module
        if ($TemporaryDependant.Count -gt 0) {
            if ($Module -in $ApprovedModules) {

                foreach ($ModulesTemp in $TemporaryDependant) {
                    $ModulesThatWillMissBecauseOfIntegrating.Add($ModulesTemp)
                }
            }
            else {
                $TemporaryDependant
            }
        }
    }
    $DependantRequiredModules = $DependantRequiredModules | Sort-Object -Unique

    $TimeToExecute.Stop()
    Write-Text "[+] Detecting required modules [Time: $($($TimeToExecute.Elapsed).Tostring())]" -Color Blue

    $TimeToExecute = [System.Diagnostics.Stopwatch]::StartNew()
    Write-Text "[+] Searching for missing functions" -Color Blue

    $MissingFunctions = Get-MissingFunctions -FilePath $PSM1FilePath -SummaryWithCommands -ApprovedModules $ApprovedModules

    $TimeToExecute.Stop()
    Write-Text "[+] Searching for missing functions [Time: $($($TimeToExecute.Elapsed).Tostring())]" -Color Blue

    $TimeToExecute = [System.Diagnostics.Stopwatch]::StartNew()
    Write-Text "[+] Detecting commands used" -Color Blue

    [Array] $ApplicationsCheck = $MissingFunctions.Summary | Where-Object { $_.CommandType -eq 'Application' } | Sort-Object -Unique -Property 'Source'
    [Array] $ModulesToCheck = $MissingFunctions.Summary | Where-Object { $_.CommandType -ne 'Application' -and $_.CommandType -ne '' } | Sort-Object -Unique -Property 'Source'
    [Array] $CommandsWithoutModule = $MissingFunctions.Summary | Where-Object { $_.CommandType -eq '' } 

    if ($ApplicationsCheck.Source) {
        Write-Text "[i] Applications used by this module. Make sure those are present on destination system. " -Color Yellow
        foreach ($Application in $ApplicationsCheck.Source) {
            Write-Text " [>] Application $Application " -Color Yellow
        }
    }
    $TimeToExecute.Stop()
    Write-Text "[+] Detecting commands used [Time: $($($TimeToExecute.Elapsed).Tostring())]" -Color Blue

    Write-TextWithTime -Text "Pre-Verification of approved modules" {
        foreach ($ApprovedModule in $ApprovedModules) {
            $ApprovedModuleStatus = Get-Module -Name $ApprovedModule -ListAvailable
            if ($ApprovedModuleStatus) {
                Write-Text " [>] Approved module $ApprovedModule exists - can be used for merging." -Color Green
            }
            else {
                Write-Text " [>] Approved module $ApprovedModule doesn't exists. Potentially issue with merging." -Color Red
            }
        }
    } -PreAppend Plus

    $TerminateEarly = $false
    $Success = Write-TextWithTime -Text "Analyze required, approved modules" {
        foreach ($Module in $ModulesToCheck.Source | Sort-Object) {
            if ($Module -in $RequiredModules -and $Module -in $ApprovedModules) {
                Write-Text " [+] Module $Module is in required modules with ability to merge." -Color DarkYellow
                $MyFunctions = ($MissingFunctions.Summary | Where-Object { $_.Source -eq $Module })
                foreach ($F in $MyFunctions) {
                    if ($F.IsPrivate) {
                        Write-Text " [>] Command used $($F.Name) (Command Type: $($F.CommandType) / IsAlias: $($F.IsAlias)) / IsPrivate: $($F.IsPrivate))" -Color Magenta
                    }
                    else {
                        Write-Text " [>] Command used $($F.Name) (Command Type: $($F.CommandType) / IsAlias: $($F.IsAlias)) / IsPrivate: $($F.IsPrivate))" -Color DarkYellow
                    }
                }
            }
            elseif ($Module -in $DependantRequiredModules -and $Module -in $ApprovedModules) {
                Write-Text " [+] Module $Module is in dependant required module within required modules with ability to merge." -Color DarkYellow
                $MyFunctions = ($MissingFunctions.Summary | Where-Object { $_.Source -eq $Module })
                foreach ($F in $MyFunctions) {
                    Write-Text " [>] Command used $($F.Name) (Command Type: $($F.CommandType) / IsAlias: $($F.IsAlias)) / IsAlias: $($F.IsPrivate))" -Color DarkYellow
                }
            }
            elseif ($Module -in $DependantRequiredModules) {
                Write-Text " [+] Module $Module is in dependant required module within required modules." -Color DarkGray
                $MyFunctions = ($MissingFunctions.Summary | Where-Object { $_.Source -eq $Module })
                foreach ($F in $MyFunctions) {
                    Write-Text " [>] Command used $($F.Name) (Command Type: $($F.CommandType) / IsAlias: $($F.IsAlias)) / IsAlias: $($F.IsPrivate))" -Color DarkGray
                }
            }
            elseif ($Module -in $RequiredModules) {
                Write-Text " [+] Module $Module is in required modules." -Color Green
                $MyFunctions = ($MissingFunctions.Summary | Where-Object { $_.Source -eq $Module })
                foreach ($F in $MyFunctions) {
                    Write-Text " [>] Command used $($F.Name) (Command Type: $($F.CommandType) / IsAlias: $($F.IsAlias)) / IsAlias: $($F.IsPrivate))" -Color Green
                }
            }
            elseif ($Module -notin $RequiredModules -and $Module -in $ApprovedModules) {
                Write-Text " [+] Module $Module is missing in required module, but it's in approved modules." -Color Magenta
                $MyFunctions = ($MissingFunctions.Summary | Where-Object { $_.Source -eq $Module })
                foreach ($F in $MyFunctions) {
                    Write-Text " [>] Command used $($F.Name) (Command Type: $($F.CommandType) / IsAlias: $($F.IsAlias)) / IsAlias: $($F.IsPrivate))" -Color Magenta
                }
            }
            else {
                [Array] $MyFunctions = ($MissingFunctions.Summary | Where-Object { $_.Source -eq $Module })
                if ($Configuration.Options.Merge.ModuleSkip.Force -eq $true) {
                    Write-Text " [-] Module $Module is missing in required modules. Non-critical issue as per configuration (force used)." -Color Gray
                    foreach ($F in $MyFunctions) {
                        Write-Text " [>] Command affected $($F.Name) (Command Type: $($F.CommandType) / IsAlias: $($F.IsAlias)) / IsAlias: $($F.IsPrivate)). Ignored by configuration." -Color Gray
                    }
                }
                else {
                    if ($Module -in $Configuration.Options.Merge.ModuleSkip.IgnoreModuleName) {
                        Write-Text " [-] Module $Module is missing in required modules. Non-critical issue as per configuration (skipped module)." -Color Gray
                        foreach ($F in $MyFunctions) {
                            Write-Text " [>] Command affected $($F.Name) (Command Type: $($F.CommandType) / IsAlias: $($F.IsAlias)) / IsAlias: $($F.IsPrivate)). Ignored by configuration." -Color Gray
                        }
                    }
                    else {
                        $FoundProblem = $false
                        foreach ($F in $MyFunctions) {
                            if ($F.Name -notin $Configuration.Options.Merge.ModuleSkip.IgnoreFunctionName) {
                                $FoundProblem = $true
                            }
                        }
                        if (-not $FoundProblem) {
                            Write-Text " [-] Module $Module is missing in required modules. Non-critical issue as per configuration (skipped functions)." -Color Gray
                            foreach ($F in $MyFunctions) {
                                if ($F.Name -in $Configuration.Options.Merge.ModuleSkip.IgnoreFunctionName) {
                                    Write-Text " [>] Command affected $($F.Name) (Command Type: $($F.CommandType) / IsAlias: $($F.IsAlias)) / IsAlias: $($F.IsPrivate)). Ignored by configuration." -Color Gray
                                }
                                else {
                                    Write-Text " [>] Command affected $($F.Name) (Command Type: $($F.CommandType) / IsAlias: $($F.IsAlias)) / IsAlias: $($F.IsPrivate))" -Color Red
                                }
                            }
                        }
                        else {
                            $TerminateEarly = $true
                            Write-Text " [-] Module $Module is missing in required modules. Potential issue. Fix configuration required." -Color Red
                            foreach ($F in $MyFunctions) {
                                if ($F.Name -in $Configuration.Options.Merge.ModuleSkip.IgnoreFunctionName) {
                                    Write-Text " [>] Command affected $($F.Name) (Command Type: $($F.CommandType) / IsAlias: $($F.IsAlias)) / IsAlias: $($F.IsPrivate)). Ignored by configuration." -Color Gray
                                }
                                else {
                                    Write-Text " [>] Command affected $($F.Name) (Command Type: $($F.CommandType) / IsAlias: $($F.IsAlias)) / IsAlias: $($F.IsPrivate))" -Color Red
                                }
                            }
                        }
                    }
                }
            }
        }

        if ($CommandsWithoutModule.Count -gt 0) {
            $FoundProblem = $false
            foreach ($F in $CommandsWithoutModule) {
                if ($F.Name -notin $Configuration.Options.Merge.ModuleSkip.IgnoreFunctionName) {
                    $FoundProblem = $true
                }
            }
            if ($FoundProblem) {
                Write-Text " [-] Some commands couldn't be resolved to functions (private function maybe?). Potential issue." -Color Red
                foreach ($F in $CommandsWithoutModule) {
                    if ($F.Name -notin $Configuration.Options.Merge.ModuleSkip.IgnoreFunctionName) {
                        $TerminateEarly = $true
                        Write-Text " [>] Command affected $($F.Name) (Command Type: Unknown / IsAlias: $($F.IsAlias))" -Color Red
                    }
                    else {
                        Write-Text " [>] Command affected $($F.Name) (Command Type: Unknown / IsAlias: $($F.IsAlias)). Ignored by configuration." -Color Gray
                    }
                }
            }
            else {
                Write-Text " [-] Some commands couldn't be resolved to functions (private function maybe?). Non-critical issue as per configuration (skipped functions)." -Color Gray
                foreach ($F in $CommandsWithoutModule) {
                    if ($F.Name -in $Configuration.Options.Merge.ModuleSkip.IgnoreFunctionName) {
                        Write-Text " [>] Command affected $($F.Name) (Command Type: Unknown / IsAlias: $($F.IsAlias)). Ignored by configuration." -Color Gray
                    }
                    else {

                        Write-Text " [>] Command affected $($F.Name) (Command Type: Unknown / IsAlias: $($F.IsAlias))" -Color Red
                    }
                }
            }
        }

        if ($TerminateEarly) {
            Write-Text " [-] Some commands are missing in required modules. Fix this issue or use New-ConfigurationModuleSkip to skip verification." -Color Red
            return $false
        }
    } -PreAppend Plus

    if ($Success -eq $false) {
        return $false
    }

    if ($Configuration.Steps.BuildModule.MergeMissing -eq $true) {
        if (Test-Path -LiteralPath $PSM1FilePath) {
            $TimeToExecute = [System.Diagnostics.Stopwatch]::StartNew()
            Write-Text "[+] Merge mergable commands" -Color Blue

            $PSM1Content = Get-Content -LiteralPath $PSM1FilePath -Raw -Encoding $Encoding
            $IntegrateContent = @(
                $MissingFunctions.Functions
                $PSM1Content
            )
            $IntegrateContent | Set-Content -LiteralPath $PSM1FilePath -Encoding $Encoding

            $NewRequiredModules = foreach ($_ in $Configuration.Information.Manifest.RequiredModules) {
                if ($_ -is [System.Collections.IDictionary]) {
                    if ($_.ModuleName -notin $ApprovedModules) {
                        $_
                    }
                }
                else {
                    if ($_ -notin $ApprovedModules) {
                        $_
                    }
                }
            }
            $Configuration.Information.Manifest.RequiredModules = $NewRequiredModules
            $TimeToExecute.Stop()
            Write-Text "[+] Merge mergable commands [Time: $($($TimeToExecute.Elapsed).Tostring())]" -Color Blue
        }
    }

    $TimeToExecuteSign = [System.Diagnostics.Stopwatch]::StartNew()
    Write-Text "[+] Finalizing PSM1/PSD1" -Color Blue

    if ($null -eq $Configuration.Steps.BuildModule.DebugDLL) {
        $Configuration.Steps.BuildModule.DebugDLL = $false
    }
    $LibraryContent = @(
        if ($LibrariesStandard.Count -gt 0) {
            foreach ($File in $LibrariesStandard) {
                $Extension = $File.Substring($File.Length - 4, 4)
                if ($Extension -eq '.dll') {
                    $Output = New-DLLCodeOutput -DebugDLL $Configuration.Steps.BuildModule.DebugDLL -File $File
                    $Output
                }
            }
        }
        elseif ($LibrariesCore.Count -gt 0 -and $LibrariesDefault.Count -gt 0) {
            'if ($PSEdition -eq ''Core'') {'
            if ($LibrariesCore.Count -gt 0) {
                foreach ($File in $LibrariesCore) {
                    $Extension = $File.Substring($File.Length - 4, 4)
                    if ($Extension -eq '.dll') {
                        $Output = New-DLLCodeOutput -DebugDLL $Configuration.Steps.BuildModule.DebugDLL -File $File
                        $Output
                    }
                }
            }
            '} else {'
            if ($LibrariesDefault.Count -gt 0) {
                foreach ($File in $LibrariesDefault) {
                    $Extension = $File.Substring($File.Length - 4, 4)
                    if ($Extension -eq '.dll') {
                        $Output = New-DLLCodeOutput -DebugDLL $Configuration.Steps.BuildModule.DebugDLL -File $File
                        $Output
                    }
                }
            }
            '}'
        }
    )

    if ($LibraryContent.Count -gt 0) {
        if ($Configuration.Steps.BuildModule.LibrarySeparateFile -eq $true) {
            $LibariesPath = [System.IO.Path]::Combine($ModulePathTarget, "$ModuleName.Libraries.ps1")
            $ScriptsToProcessLibrary = "$ModuleName.Libraries.ps1"
        }
        if ($Configuration.Steps.BuildModule.LibraryDotSource -eq $true) {
            $LibariesPath = [System.IO.Path]::Combine($ModulePathTarget, "$ModuleName.Libraries.ps1")
            $DotSourcePath = ". `$PSScriptRoot\$ModuleName.Libraries.ps1"
        }
        if ($LibariesPath) {
            $LibraryContent | Out-File -Append -LiteralPath $LibariesPath -Encoding $Encoding
        }
    }

    if ($ClassesFunctions.Count -gt 0) {
        $ClassesPath = [System.IO.Path]::Combine($ModulePathTarget, "$ModuleName.Classes.ps1")
        $DotSourceClassPath = ". `$PSScriptRoot\$ModuleName.Classes.ps1"
        $Success = Get-ScriptsContentAndTryReplace -Files $ClassesFunctions -OutputPath $ClassesPath -DoNotAttemptToFixRelativePaths:$Configuration.Steps.BuildModule.DoNotAttemptToFixRelativePaths
        if ($Success -eq $false) {
            return $false
        }
    }

    if ($LibariesPath -gt 0 -or $ClassesPath -gt 0 -or $Configuration.Steps.BuildModule.ResolveBinaryConflicts) {
        $PSM1Content = Get-Content -LiteralPath $PSM1FilePath -Raw -Encoding UTF8
        $IntegrateContent = @(

            if ($Configuration.Steps.BuildModule.ResolveBinaryConflicts -is [System.Collections.IDictionary]) {
                New-DLLResolveConflict -ProjectName $Configuration.Steps.BuildModule.ResolveBinaryConflicts.ProjectName
            }
            elseif ($Configuration.Steps.BuildModule.ResolveBinaryConflicts -eq $true) {
                New-DLLResolveConflict
            }
            if ($LibraryContent.Count -gt 0) {
                if ($DotSourcePath) {
                    "# Dot source all libraries by loading external file"
                    $DotSourcePath
                    ""
                }
                if (-not $LibariesPath) {
                    "# Load all types"
                    $LibraryContent
                    ""
                }
            }
            if ($ClassesPath) {
                "# Dot source all classes by loading external file"
                $DotSourceClassPath
                ""
            }
            $PSM1Content
        )
        $IntegrateContent | Set-Content -LiteralPath $PSM1FilePath -Encoding $Encoding
    }

    if ($Configuration.Information.Manifest.DotNetFrameworkVersion) {
        Find-NetFramework -RequireVersion $Configuration.Information.Manifest.DotNetFrameworkVersion | Out-File -Append -LiteralPath $PSM1FilePath -Encoding $Encoding
    }

    $Success = New-PSMFile -Path $PSM1FilePath `
        -FunctionNames $FunctionsToExport `
        -FunctionAliaes $AliasesToExport `
        -AliasesAndFunctions $AliasesAndFunctions `
        -LibrariesStandard $LibrariesStandard `
        -LibrariesCore $LibrariesCore `
        -LibrariesDefault $LibrariesDefault `
        -ModuleName $ModuleName `
        -UsingNamespaces:$UsingInPlace `
        -LibariesPath $LibariesPath `
        -InternalModuleDependencies $Configuration.Information.Manifest.InternalModuleDependencies `
        -CommandModuleDependencies $Configuration.Information.Manifest.CommandModuleDependencies
    if ($Success -eq $false) {
        return $false
    }

    $Success = Format-Code -FilePath $PSM1FilePath -FormatCode $FormatCodePSM1
    if ($Success -eq $false) {
        return $false
    }

    if ($LibariesPath) {
        $Success = Format-Code -FilePath $LibariesPath -FormatCode $FormatCodePSM1
        if ($Success -eq $false) {
            return $false
        }
    }

    New-PersonalManifest -Configuration $Configuration -ManifestPath $PSD1FilePath -AddUsingsToProcess -ScriptsToProcessLibrary $ScriptsToProcessLibrary -OnMerge

    $Success = Format-Code -FilePath $PSD1FilePath -FormatCode $FormatCodePSD1
    if ($Success -eq $false) {
        return $false
    }

    Get-ChildItem $ModulePathTarget -Recurse -Force -Directory | Sort-Object -Property FullName -Descending | `
            Where-Object { $($_ | Get-ChildItem -Force | Select-Object -First 1).Count -eq 0 } | `
                Remove-Item 

    $TimeToExecuteSign.Stop()
    Write-Text "[+] Finalizing PSM1/PSD1 [Time: $($($TimeToExecuteSign.Elapsed).Tostring())]" -Color Blue
}
function New-CreateModule {
    [CmdletBinding()]
    param (
        [string] $ProjectName,
        [string] $ModulePath,
        [string] $ProjectPath
    )
    $FullProjectPath = [io.path]::Combine($ProjectPath, $ProjectName)
    $Folders = 'Private', 'Public', 'Examples', 'Ignore', 'Publish', 'Enums', 'Data'
    Add-Directory -Directory $FullProjectPath
    foreach ($Folder in $Folders) {
        $PathToCreate = [io.path]::Combine($FullProjectPath, $Folder)
        Add-Directory -Directory $PathToCreate
    }
    $Source = [io.path]::Combine($PSScriptRoot, "..", 'Data', 'Example-Gitignore.txt')
    $Destination = [io.path]::Combine($FullProjectPath, '.gitignore')
    Copy-Item -Path $Source -Destination $Destination -ErrorAction Stop
    $Source = [io.path]::Combine($PSScriptRoot, "..", 'Data', 'Example-LicenseMIT.txt')
    $Destination = [io.path]::Combine($FullProjectPath, 'License')
    Copy-Item -Path $Source -Destination $Destination -ErrorAction Stop
    $Source = [io.path]::Combine($PSScriptRoot, "..", 'Data', 'Example-ModuleStarter.txt')
    $Destination = [io.path]::Combine($FullProjectPath, "$ProjectName.psm1")
    Copy-Item -Path $Source -Destination $Destination -ErrorAction Stop
}
function New-DLLCodeOutput {
    [CmdletBinding()]
    param(
        [string] $File,
        [bool] $DebugDLL
    )
    if ($DebugDLL) {
        $Output = @"
    `$FoundErrors = @(
        try {
            `$ImportName = "`$PSScriptRoot\$File"
            Add-Type -Path `$ImportName -ErrorAction Stop
        } catch [System.Reflection.ReflectionTypeLoadException] {
            Write-Warning "Processing `$(`$ImportName) Exception: `$(`$_.Exception.Message)"
            `$LoaderExceptions = `$(`$_.Exception.LoaderExceptions) | Sort-Object -Unique
            foreach (`$E in `$LoaderExceptions) {
                Write-Warning "Processing `$(`$ImportName) LoaderExceptions: `$(`$E.Message)"
            }
           `$true
        } catch {
            Write-Warning "Processing `$(`$ImportName) Exception: `$(`$_.Exception.Message)"
            `$LoaderExceptions = `$(`$_.Exception.LoaderExceptions) | Sort-Object -Unique
            foreach (`$E in `$LoaderExceptions) {
                Write-Warning "Processing `$(`$ImportName) LoaderExceptions: `$(`$E.Message)"
            }
            `$true
        }
    )
    if (`$FoundErrors.Count -gt 0) {
        Write-Warning "Importing module failed. Fix errors before continuing."
        break
    }
"@

    }
    else {
        $Output = 'Add-Type -Path $PSScriptRoot\' + $File
    }
    $Output
}
function New-DLLResolveConflict {
    [CmdletBinding()]
    param(
        [string] $ProjectName
    )
    if ($ProjectName) {
        $StandardName = "'$ProjectName'"
    }
    else {
        $StandardName = '$myInvocation.MyCommand.Name.Replace(".psm1", "")'
    }
    $Output = @"
 
    # Get library name, from the PSM1 file name
    `$LibraryName = $StandardName
    `$Library = "`$LibraryName.dll"
    `$Class = "`$LibraryName.Initialize"
 
    `$AssemblyFolders = Get-ChildItem -Path `$PSScriptRoot\Lib -Directory -ErrorAction SilentlyContinue
 
    # Lets find which libraries we need to load
    `$Default = `$false
    `$Core = `$false
    `$Standard = `$false
    foreach (`$A in `$AssemblyFolders.Name) {
        if (`$A -eq 'Default') {
            `$Default = `$true
        } elseif (`$A -eq 'Core') {
            `$Core = `$true
        } elseif (`$A -eq 'Standard') {
            `$Standard = `$true
        }
    }
    if (`$Standard -and `$Core -and `$Default) {
        `$FrameworkNet = 'Default'
        `$Framework = 'Standard'
    } elseif (`$Standard -and `$Core) {
        `$Framework = 'Standard'
        `$FrameworkNet = 'Standard'
    } elseif (`$Core -and `$Default) {
        `$Framework = 'Core'
        `$FrameworkNet = 'Default'
    } elseif (`$Standard -and `$Default) {
        `$Framework = 'Standard'
        `$FrameworkNet = 'Default'
    } elseif (`$Standard) {
        `$Framework = 'Standard'
        `$FrameworkNet = 'Standard'
    } elseif (`$Core) {
        `$Framework = 'Core'
        `$FrameworkNet = ''
    } elseif (`$Default) {
        `$Framework = ''
        `$FrameworkNet = 'Default'
    } else {
        Write-Error -Message 'No assemblies found'
    }
    if (`$PSEdition -eq 'Core') {
        `$LibFolder = `$Framework
    } else {
        `$LibFolder = `$FrameworkNet
    }
 
    try {
        `$ImportModule = Get-Command -Name Import-Module -Module Microsoft.PowerShell.Core
 
        if (-not (`$Class -as [type])) {
            & `$ImportModule ([IO.Path]::Combine(`$PSScriptRoot, 'Lib', `$LibFolder, `$Library)) -ErrorAction Stop
        } else {
            `$Type = "`$Class" -as [Type]
            & `$importModule -Force -Assembly (`$Type.Assembly)
        }
    } catch {
        if (`$ErrorActionPreference -eq 'Stop') {
            throw
        } else {
            Write-Warning -Message "Importing module `$Library failed. Fix errors before continuing. Error: `$(`$_.Exception.Message)"
            # we will continue, but it's not a good idea to do so
            # return
        }
    }
"@

    $Output
}
function New-PersonalManifest {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Configuration,
        [string] $ManifestPath,
        [switch] $AddScriptsToProcess,
        [switch] $AddUsingsToProcess,
        [string] $ScriptsToProcessLibrary,
        [switch] $UseWildcardForFunctions,
        [switch] $OnMerge
    )

    $TemporaryManifest = [ordered] @{ }
    $Manifest = $Configuration.Information.Manifest

    if ($UseWildcardForFunctions) {
        $Manifest.FunctionsToExport = @("*")
        $Manifest.AliasesToExport = @("*")
    }

    $Manifest.Path = $ManifestPath

    if (-not $AddScriptsToProcess) {
        $Manifest.ScriptsToProcess = @()
    }
    if ($AddUsingsToProcess -and $Configuration.UsingInPlace -and -not $ScriptsToProcessLibrary) {
        $Manifest.ScriptsToProcess = @($Configuration.UsingInPlace)
    }
    elseif ($AddUsingsToProcess -and $Configuration.UsingInPlace -and $ScriptsToProcessLibrary) {
        $Manifest.ScriptsToProcess = @($Configuration.UsingInPlace, $ScriptsToProcessLibrary)
    }
    elseif ($ScriptsToProcessLibrary) {
        $Manifest.ScriptsToProcess = @($ScriptsToProcessLibrary)
    }
    if ($Manifest.Contains('ExternalModuleDependencies')) {
        $TemporaryManifest.ExternalModuleDependencies = $Manifest.ExternalModuleDependencies
        $Manifest.Remove('ExternalModuleDependencies')
    }
    if ($Manifest.Contains('InternalModuleDependencies')) {
        $TemporaryManifest.InternalModuleDependencies = $Manifest.InternalModuleDependencies
        $Manifest.Remove('InternalModuleDependencies')
    }
    if ($Manifest.Contains('CommandModuleDependencies')) {
        $TemporaryManifest.CommandModuleDependencies = $Manifest.CommandModuleDependencies
        $Manifest.Remove('CommandModuleDependencies')
    }
    if ($Manifest.PreRelease) {
        $Configuration.CurrentSettings.PreRelease = $Manifest.PreRelease
    }
    if ($OnMerge) {
        if ($Configuration.Options.Merge.Style.PSD1) {
            $PSD1Style = $Configuration.Options.Merge.Style.PSD1
        }
    }
    else {
        if ($Configuration.Options.Standard.Style.PSD1) {
            $PSD1Style = $Configuration.Options.Standard.Style.PSD1
        }
    }
    if (-not $PSD1Style) {
        if ($Configuration.Options.Style.PSD1) {
            $PSD1Style = $Configuration.Options.Style.PSD1
        }
        else {
            $PSD1Style = 'Minimal'
        }
    }

    if ($PSD1Style -eq 'Native' -and $Configuration.Steps.PublishModule.Prerelease -eq '' -and (-not $TemporaryManifest.ExternalModuleDependencies)) {
        if ($Manifest.ModuleVersion) {
            New-ModuleManifest @Manifest
        }
        else {
            Write-Text -Text '[-] Module version is not available. Terminating.' -Color Red
            return $false
        }
        Write-TextWithTime -Text "[i] Converting $($ManifestPath) UTF8 without BOM" {
            (Get-Content -Path $ManifestPath -Raw -Encoding utf8) | Out-FileUtf8NoBom $ManifestPath
        }
    }
    else {
        if ($PSD1Style -eq 'Native') {
            Write-Text -Text '[-] Native PSD1 style is not available when using PreRelease or ExternalModuleDependencies. Switching to Minimal.' -Color Yellow
        }

        $Data = $Manifest
        $Data.PrivateData = @{
            PSData = [ordered]@{}
        }
        if ($Data.Path) {
            $Data.Remove('Path')
        }
        $ValidateEntriesPrivateData = @('Tags', 'LicenseUri', 'ProjectURI', 'IconUri', 'ReleaseNotes', 'Prerelease', 'RequireLicenseAcceptance', 'ExternalModuleDependencies')
        foreach ($Entry in [string[]] $Data.Keys) {
            if ($Entry -in $ValidateEntriesPrivateData) {
                $Data.PrivateData.PSData.$Entry = $Data.$Entry
                $Data.Remove($Entry)
            }
        }
        $ValidDataEntries = @('ModuleToProcess', 'NestedModules', 'GUID', 'Author', 'CompanyName', 'Copyright', 'ModuleVersion', 'Description', 'PowerShellVersion', 'PowerShellHostName', 'PowerShellHostVersion', 'CLRVersion', 'DotNetFrameworkVersion', 'ProcessorArchitecture', 'RequiredModules', 'TypesToProcess', 'FormatsToProcess', 'ScriptsToProcess', 'PrivateData', 'RequiredAssemblies', 'ModuleList', 'FileList', 'FunctionsToExport', 'VariablesToExport', 'AliasesToExport', 'CmdletsToExport', 'DscResourcesToExport', 'CompatiblePSEditions', 'HelpInfoURI', 'RootModule', 'DefaultCommandPrefix')
        foreach ($Entry in [string[]] $Data.Keys) {
            if ($Entry -notin $ValidDataEntries) {
                Write-Text -Text "[-] Removing wrong entries from PSD1 - $Entry" -Color Red
                $Data.Remove($Entry)
            }
        }
        foreach ($Entry in [string[]] $Data.PrivateData.PSData.Keys) {
            if ($Entry -notin $ValidateEntriesPrivateData) {
                Write-Text -Text "[-] Removing wrong entries from PSD1 Private Data - $Entry" -Color Red
                $Data.PrivateData.PSData.Remove($Entry)
            }
        }

        if ($Configuration.Steps.PublishModule.Prerelease) {
            $Data.PrivateData.PSData.Prerelease = $Configuration.Steps.PublishModule.Prerelease
        }
        if ($TemporaryManifest.ExternalModuleDependencies) {

            $Data.PrivateData.PSData.ExternalModuleDependencies = $TemporaryManifest.ExternalModuleDependencies

            $Data.RequiredModules = @(
                foreach ($Module in $Manifest.RequiredModules) {
                    if ($Module -is [System.Collections.IDictionary]) {

                        $Module = [ordered] @{
                            ModuleName    = $Module.ModuleName
                            ModuleVersion = $Module.ModuleVersion
                            Guid          = $Module.Guid
                        }
                        Remove-EmptyValue -Hashtable $Module
                        $Module
                    }
                    else {
                        $Module
                    }
                }
                foreach ($Module in $TemporaryManifest.ExternalModuleDependencies) {
                    if ($Module -is [System.Collections.IDictionary]) {

                        $Module = [ordered] @{
                            ModuleName    = $Module.ModuleName
                            ModuleVersion = $Module.ModuleVersion
                            Guid          = $Module.Guid
                        }
                        Remove-EmptyValue -Hashtable $Module
                        $Module
                    }
                    else {
                        $Module
                    }
                }
            )
        }
        if (-not $Data.RequiredModules) {
            $Data.Remove('RequiredModules')
        }
        $Data | Export-PSData -DataFile $ManifestPath -Sort
    }
}
function New-PrepareManifest {
    [CmdletBinding()]
    param(
        [string] $ProjectName,
        [string] $ModulePath,
        [string] $ProjectPath,
        $FunctionToExport,
        [string] $ProjectUrl
    )
    $Location = [System.IO.Path]::Combine($projectPath, $ProjectName)
    Set-Location -Path $Location
    $manifest = @{
        Path              = ".\$ProjectName.psd1"
        RootModule        = "$ProjectName.psm1"
        Author            = 'Przemyslaw Klys'
        CompanyName       = 'Evotec'
        Copyright         = 'Evotec (c) 2011-2022. All rights reserved.'
        Description       = "Simple project"
        FunctionsToExport = $functionToExport
        CmdletsToExport   = ''
        VariablesToExport = ''
        AliasesToExport   = ''
        FileList          = "$ProjectName.psm1", "$ProjectName.psd1"
        HelpInfoURI       = $projectUrl
        ProjectUri        = $projectUrl
    }
    New-ModuleManifest @manifest
}
function New-PrepareStructure {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary]$Configuration,
        [scriptblock] $Settings,
        [string] $PathToProject,
        [string] $ModuleName,
        [string] $FunctionsToExportFolder,
        [string] $AliasesToExportFolder,
        [string[]] $ExcludeFromPackage,
        [string[]] $IncludeRoot,
        [string[]] $IncludePS1,
        [string[]] $IncludeAll,
        [scriptblock] $IncludeCustomCode,
        [System.Collections.IDictionary] $IncludeToArray,
        [string] $LibrariesCore,
        [string] $LibrariesDefault,
        [string] $LibrariesStandard
    )

    if (-not $Configuration) {
        $Configuration = [ordered] @{}
    }
    if (-not $Configuration.Information) {
        $Configuration.Information = [ordered] @{}
    }
    if (-not $Configuration.Information.Manifest) {

        $PathToPSD1 = [io.path]::Combine($PathToProject, $ModuleName + '.psd1')
        if (Test-Path -LiteralPath $PathToPSD1) {
            try {
                $Configuration.Information.Manifest = Import-PowerShellDataFile -Path $PathToPSD1 -ErrorAction Stop

                $Configuration.Information.Manifest.RequiredModules = $null
            }
            catch {
                Write-Text "[-] Reading $PathToPSD1 failed. Error: $($_.Exception.Message)" -Color Red
                Write-Text "[+] Building $PathToPSD1 from scratch." -Color Yellow
                $Configuration.Information.Manifest = [ordered] @{}
            }
        }
        else {
            $Configuration.Information.Manifest = [ordered] @{}
        }
    }

    if (-not $Configuration.Information.DirectoryModulesCore) {
        $PathCore = [io.path]::Combine($([Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments)), "PowerShell", 'Modules')
        $Configuration.Information.DirectoryModulesCore = $PathCore
    }

    if (-not $Configuration.Information.DirectoryModules) {
        $PathStandard = [io.path]::Combine($([Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments)), "WindowsPowerShell", 'Modules')
        $Configuration.Information.DirectoryModules = $PathStandard
    }

    if (-not $Configuration.CurrentSettings) {
        $Configuration.CurrentSettings = [ordered] @{}
    }
    if (-not $Configuration.CurrentSettings['Artefact']) {
        $Configuration.CurrentSettings['Artefact'] = @()
    }
    if ($ModuleName) {
        $Configuration.Information.ModuleName = $ModuleName
    }
    if ($PSBoundParameters.ContainsKey('ExcludeFromPackage')) {
        $Configuration.Information.Exclude = $ExcludeFromPackage
    }
    if ($PSBoundParameters.ContainsKey('IncludeRoot')) {
        $Configuration.Information.IncludeRoot = $IncludeRoot
    }
    if ($PSBoundParameters.ContainsKey('IncludePS1')) {
        $Configuration.Information.IncludePS1 = $IncludePS1
    }
    if ($PSBoundParameters.ContainsKey('IncludeAll')) {
        $Configuration.Information.IncludeAll = $IncludeAll
    }
    if ($PSBoundParameters.ContainsKey('IncludeCustomCode')) {
        $Configuration.Information.IncludeCustomCode = $IncludeCustomCode
    }
    if ($PSBoundParameters.ContainsKey('IncludeToArray')) {
        $Configuration.Information.IncludeToArray = $IncludeToArray
    }
    if ($PSBoundParameters.ContainsKey('LibrariesCore')) {
        $Configuration.Information.LibrariesCore = $LibrariesCore
    }
    if ($PSBoundParameters.ContainsKey('LibrariesDefault')) {
        $Configuration.Information.LibrariesDefault = $LibrariesDefault
    }
    if ($PSBoundParameters.ContainsKey('LibrariesStandard')) {
        $Configuration.Information.LibrariesStandard = $LibrariesStandard
    }

    if ($FunctionsToExportFolder) {
        $Configuration.Information.FunctionsToExport = $FunctionsToExportFolder
    }
    if ($AliasesToExportFolder) {
        $Configuration.Information.AliasesToExport = $AliasesToExportFolder
    }
    if (-not $Configuration.Options) {
        $Configuration.Options = [ordered] @{}
    }
    if (-not $Configuration.Options.Merge) {
        $Configuration.Options.Merge = [ordered] @{}
    }
    if (-not $Configuration.Options.Merge.Integrate) {
        $Configuration.Options.Merge.Integrate = [ordered] @{}
    }
    if (-not $Configuration.Options.Standard) {
        $Configuration.Options.Standard = [ordered] @{}
    }
    if (-not $Configuration.Options.Signing) {
        $Configuration.Options.Signing = [ordered] @{}
    }
    if (-not $Configuration.Steps) {
        $Configuration.Steps = [ordered] @{}
    }
    if (-not $Configuration.Steps.PublishModule) {
        $Configuration.Steps.PublishModule = [ordered] @{}
    }
    if (-not $Configuration.Steps.ImportModules) {
        $Configuration.Steps.ImportModules = [ordered] @{}
    }
    if (-not $Configuration.Steps.BuildModule) {
        $Configuration.Steps.BuildModule = [ordered] @{}
    }
    if (-not $Configuration.Steps.BuildModule.Releases) {
        $Configuration.Steps.BuildModule.Releases = [ordered] @{}
    }
    if (-not $Configuration.Steps.BuildModule.ReleasesUnpacked) {
        $Configuration.Steps.BuildModule.ReleasesUnpacked = [ordered] @{}
    }
    if (-not $Configuration.Steps.BuildLibraries) {
        $Configuration.Steps.BuildLibraries = [ordered] @{}
    }
    if (-not $Configuration.Information.Manifest.CommandModuleDependencies) {
        $Configuration.Information.Manifest.CommandModuleDependencies = [ordered] @{}
    }
    if (-not $Configuration.Steps.BuildModule.Artefacts) {
        $Configuration.Steps.BuildModule.Artefacts = [System.Collections.Generic.List[System.Collections.IDictionary]]::new()
    }
    if (-not $Configuration.Steps.BuildModule.GitHubNugets) {
        $Configuration.Steps.BuildModule.GitHubNugets = [System.Collections.Generic.List[System.Collections.IDictionary]]::new()
    }
    if (-not $Configuration.Steps.BuildModule.GalleryNugets) {
        $Configuration.Steps.BuildModule.GalleryNugets = [System.Collections.Generic.List[System.Collections.IDictionary]]::new()
    }

    $Configuration.Information.Manifest.RootModule = "$($ModuleName).psm1"

    $Configuration.Information.Manifest.CmdletsToExport = @()

    Write-TextWithTime -Text "Reading configuration" {
        if ($Settings) {
            $ExecutedSettings = & $Settings
            foreach ($Setting in $ExecutedSettings) {
                if ($Setting.Type -eq 'RequiredModule') {
                    if ($Configuration.Information.Manifest.RequiredModules -isnot [System.Collections.Generic.List[System.Object]]) {
                        $Configuration.Information.Manifest.RequiredModules = [System.Collections.Generic.List[System.Object]]::new()
                    }
                    $Configuration.Information.Manifest.RequiredModules.Add($Setting.Configuration)
                }
                elseif ($Setting.Type -eq 'ExternalModule') {
                    if ($Configuration.Information.Manifest.ExternalModuleDependencies -isnot [System.Collections.Generic.List[System.Object]]) {
                        $Configuration.Information.Manifest.ExternalModuleDependencies = [System.Collections.Generic.List[System.Object]]::new()
                    }
                    $Configuration.Information.Manifest.ExternalModuleDependencies.Add($Setting.Configuration)
                }
                elseif ($Setting.Type -eq 'ApprovedModule') {
                    if ($Configuration.Options.Merge.Integrate.ApprovedModules -isnot [System.Collections.Generic.List[System.Object]]) {
                        $Configuration.Options.Merge.Integrate.ApprovedModules = [System.Collections.Generic.List[System.Object]]::new()
                    }
                    $Configuration.Options.Merge.Integrate.ApprovedModules.Add($Setting.Configuration)
                }
                elseif ($Setting.Type -eq 'ModuleSkip') {
                    $Configuration.Options.Merge.ModuleSkip = $Setting.Configuration
                }
                elseif ($Setting.Type -eq 'Manifest') {
                    foreach ($Key in $Setting.Configuration.Keys) {
                        $Configuration.Information.Manifest[$Key] = $Setting.Configuration[$Key]
                    }
                }
                elseif ($Setting.Type -eq 'Information') {
                    foreach ($Key in $Setting.Configuration.Keys) {
                        $Configuration.Information[$Key] = $Setting.Configuration[$Key]
                    }
                }
                elseif ($Setting.Type -eq 'Formatting') {
                    foreach ($Key in $Setting.Options.Keys) {
                        if (-not $Configuration.Options[$Key]) {
                            $Configuration.Options[$Key] = [ordered] @{}
                        }
                        foreach ($Entry in $Setting.Options[$Key].Keys) {
                            $Configuration.Options[$Key][$Entry] = $Setting.Options[$Key][$Entry]
                        }
                    }
                }
                elseif ($Setting.Type -eq 'Command') {
                    $Configuration.Information.Manifest.CommandModuleDependencies[$Setting.Configuration.ModuleName] = @($Setting.Configuration.CommandName)
                }
                elseif ($Setting.Type -eq 'Documentation') {
                    $Configuration.Options.Documentation = $Setting.Configuration
                }
                elseif ($Setting.Type -eq 'BuildDocumentation') {
                    $Configuration.Steps.BuildDocumentation = $Setting.Configuration
                }
                elseif ($Setting.Type -eq 'TestsBeforeMerge') {
                    $Configuration.Options.TestsBeforeMerge = $Setting.Configuration
                }
                elseif ($Setting.Type -eq 'TestsAfterMerge') {
                    $Configuration.Options.TestsAfterMerge = $Setting.Configuration
                }
                elseif ($Setting.Type -eq 'GitHubPublishing') {
                    $Configuration.Steps.BuildModule.Nugets.Add($Setting.Configuration)
                }
                elseif ($Setting.Type -eq 'ImportModules') {
                    foreach ($Key in $Setting.ImportModules.Keys) {
                        $Configuration.Steps.ImportModules[$Key] = $Setting.ImportModules[$Key]
                    }
                }
                elseif ($Setting.Type -in 'GalleryNuget') {
                    $Configuration.Steps.BuildModule.GalleryNugets.Add($Setting.Configuration)
                }
                elseif ($Setting.Type -in 'GitHubNuget') {
                    $Configuration.Steps.BuildModule.GitHubNugets.Add($Setting.Configuration)
                }
                elseif ($Setting.Type -in 'Unpacked', 'Packed', 'Script', 'ScriptPacked') {
                    $Configuration.Steps.BuildModule.Artefacts.Add($Setting.Configuration)
                }
                elseif ($Setting.Type -eq 'Build') {
                    foreach ($Key in $Setting.BuildModule.Keys) {
                        $Configuration.Steps.BuildModule[$Key] = $Setting.BuildModule[$Key]
                    }
                }
                elseif ($Setting.Type -eq 'BuildLibraries') {
                    foreach ($Key in $Setting.BuildLibraries.Keys) {
                        $Configuration.Steps.BuildLibraries[$Key] = $Setting.BuildLibraries[$Key]
                    }
                }
                elseif ($Setting.Type -eq 'Options') {
                    foreach ($Key in $Setting.Options.Keys) {
                        if (-not $Configuration.Options[$Key]) {
                            $Configuration.Options[$Key] = [ordered] @{}
                        }
                        foreach ($Entry in $Setting.Options[$Key].Keys) {
                            $Configuration.Options[$Key][$Entry] = $Setting.Options[$Key][$Entry]
                        }
                    }
                }
            }
        }
    } -PreAppend Information

    if (-not $Configuration.Options.Merge.Sort) {
        $Configuration.Options.Merge.Sort = 'None'
    }
    if (-not $Configuration.Options.Standard.Sort) {
        $Configuration.Options.Standard.Sort = 'None'
    }

    $Success = Start-ModuleBuilding -Configuration $Configuration -PathToProject $PathToProject
    if ($Success -contains $false) {
        return $false
    }
}
function New-PSMFile {
    [cmdletbinding()]
    param(
        [string] $Path,
        [string[]] $FunctionNames,
        [string[]] $FunctionAliaes,
        [System.Collections.IDictionary] $AliasesAndFunctions,
        [Array] $LibrariesStandard,
        [Array] $LibrariesCore,
        [Array] $LibrariesDefault,
        [string] $ModuleName,
        [switch] $UsingNamespaces,
        [string] $LibariesPath,
        [Array] $InternalModuleDependencies,
        [System.Collections.IDictionary] $CommandModuleDependencies
    )

    if ($PSVersionTable.PSVersion.Major -gt 5) {
        $Encoding = 'UTF8BOM'
    }
    else {
        $Encoding = 'UTF8'
    }

    Write-TextWithTime -Text "Adding alises/functions to load in PSM1 file - $Path" -PreAppend Plus {
        if ($FunctionNames.Count -gt 0) {
            $Functions = ($FunctionNames | Sort-Object -Unique) -join "','"
            $Functions = "'$Functions'"
        }
        else {
            $Functions = @()
        }

        if ($FunctionAliaes.Count -gt 0) {
            $Aliases = ($FunctionAliaes | Sort-Object -Unique) -join "','"
            $Aliases = "'$Aliases'"
        }
        else {
            $Aliases = @()
        }

        "" | Out-File -Append -LiteralPath $Path -Encoding $Encoding

        if ($InternalModuleDependencies.Count -gt 0) {
            @(
                "# Added internal module loading to cater for special cases "
                ""
            ) | Out-File -Append -LiteralPath $Path -Encoding $Encoding
            $ModulesText = "'$($InternalModuleDependencies -join "','")'"
            @"
            `$ModulesOptional = $ModulesText
            foreach (`$Module in `$ModulesOptional) {
                Import-Module -Name `$Module -ErrorAction SilentlyContinue
            }
"@
 | Out-File -Append -LiteralPath $Path -Encoding $Encoding
        }

        if ($CommandModuleDependencies -and $CommandModuleDependencies.Keys.Count -gt 0) {
            @(
                "`$ModuleFunctions = @{"
                foreach ($Module in $CommandModuleDependencies.Keys) {

                    "$Module = @{"

                    foreach ($Command in $($CommandModuleDependencies[$Module])) {

                        $Alias = "'$($AliasesAndFunctions[$Command] -join "','")'"
                        " '$Command' = $Alias"
                    }
                    "}"
                }
                "}"

                @"
                [Array] `$FunctionsAll = $Functions
                [Array] `$AliasesAll = $Aliases
                `$AliasesToRemove = [System.Collections.Generic.List[string]]::new()
                `$FunctionsToRemove = [System.Collections.Generic.List[string]]::new()
                foreach (`$Module in `$ModuleFunctions.Keys) {
                    try {
                        Import-Module -Name `$Module -ErrorAction Stop
                    } catch {
                        foreach (`$Function in `$ModuleFunctions[`$Module].Keys) {
                            `$FunctionsToRemove.Add(`$Function)
                            `$ModuleFunctions[`$Module][`$Function] | ForEach-Object {
                                if (`$_) {
                                    `$AliasesToRemove.Add(`$_)
                                }
                            }
                        }
                    }
                }
                `$FunctionsToLoad = foreach (`$Function in `$FunctionsAll) {
                    if (`$Function -notin `$FunctionsToRemove) {
                        `$Function
                    }
                }
                `$AliasesToLoad = foreach (`$Alias in `$AliasesAll) {
                    if (`$Alias -notin `$AliasesToRemove) {
                        `$Alias
                    }
                }
                # Export functions and aliases as required
                Export-ModuleMember -Function @(`$FunctionsToLoad) -Alias @(`$AliasesToLoad)
"@

            ) | Out-File -Append -LiteralPath $Path -Encoding $Encoding
        }
        else {

            "# Export functions and aliases as required" | Out-File -Append -LiteralPath $Path -Encoding $Encoding
            "Export-ModuleMember -Function @($Functions) -Alias @($Aliases)" | Out-File -Append -LiteralPath $Path -Encoding $Encoding
        }
    } -SpacesBefore ' '
}
function Out-FileUtf8NoBom {
    <#
    .SYNOPSIS
      Outputs to a UTF-8-encoded file *without a BOM* (byte-order mark).
 
    .DESCRIPTION
      Mimics the most important aspects of Out-File:
      * Input objects are sent to Out-String first.
      * -Append allows you to append to an existing file, -NoClobber prevents
        overwriting of an existing file.
      * -Width allows you to specify the line width for the text representations
        of input objects that aren't strings.
      However, it is not a complete implementation of all Out-String parameters:
      * Only a literal output path is supported, and only as a parameter.
      * -Force is not supported.
 
      Caveat: *All* pipeline input is buffered before writing output starts,
              but the string representations are generated and written to the target
              file one by one.
 
    .NOTES
      The raison d'être for this advanced function is that, as of PowerShell v5,
      Out-File still lacks the ability to write UTF-8 files without a BOM:
      using -Encoding UTF8 invariably prepends a BOM.
 
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0)] [string] $LiteralPath,
        [switch] $Append,
        [switch] $NoClobber,
        [AllowNull()] [int] $Width,
        [Parameter(ValueFromPipeline)] $InputObject
    )

    [System.IO.Directory]::SetCurrentDirectory($PWD) 
    $LiteralPath = [IO.Path]::GetFullPath($LiteralPath)

    if ($NoClobber -and (Test-Path $LiteralPath)) {
        Throw [IO.IOException] "The file '$LiteralPath' already exists."
    }

    $sw = New-Object IO.StreamWriter $LiteralPath, $Append

    $htOutStringArgs = @{}
    if ($Width) {
        $htOutStringArgs += @{ Width = $Width }
    }

    try {
        $Input | Out-String -Stream @htOutStringArgs | ForEach-Object { $sw.WriteLine($_) }
    }
    finally {
        $sw.Dispose()
    }
}
function Register-DataForInitialModule {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string] $FilePath,
        [Parameter(Mandatory)][string] $ModuleName,
        [Parameter(Mandatory)][string] $Guid
    )

    if ($PSVersionTable.PSVersion.Major -gt 5) {
        $Encoding = 'UTF8BOM'
    }
    else {
        $Encoding = 'UTF8'
    }
    try {
        $BuildModule = Get-Content -Path $FilePath -Raw -ErrorAction Stop
    }
    catch {
        Write-Text -Text "[-] Couldn't read $FilePath, error: $($_.Exception.Message)" -Color Red
        return $false
    }
    $BuildModule = $BuildModule -replace "\`$GUID", $Guid
    $BuildModule = $BuildModule -replace "\`$ModuleName", $ModuleName
    try {
        Set-Content -Path $FilePath -Value $BuildModule -Encoding $Encoding -ErrorAction Stop
    }
    catch {
        Write-Text -Text "[-] Couldn't save $FilePath, error: $($_.Exception.Message)" -Color Red
        return $false
    }
}
function Remove-Directory {
    [CmdletBinding()]
    param (
        [string] $Directory
    )
    if ($Directory) {
        $Exists = Test-Path -LiteralPath $Directory
        if ($Exists) {
            try {
                Remove-Item -Path $Directory -Confirm:$false -Recurse -Force -ErrorAction Stop
            }
            catch {
                $ErrorMessage = $_.Exception.Message
                Write-Text "[e] Can't delete folder $Directory. Fix error before continuing: $ErrorMessage" -Color Red
                return $false
            }
        }
    }
}
function Remove-EmptyLines {
    [CmdletBinding(DefaultParameterSetName = 'FilePath')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'FilePath')]
        [alias('FilePath', 'Path', 'LiteralPath')][string] $SourceFilePath,

        [Parameter(Mandatory, ParameterSetName = 'Content')][string] $Content,

        [Parameter(ParameterSetName = 'Content')]
        [Parameter(ParameterSetName = 'FilePath')]
        [alias('Destination')][string] $DestinationFilePath,

        [Parameter(ParameterSetName = 'Content')]
        [Parameter(ParameterSetName = 'FilePath')]
        [switch] $RemoveAllEmptyLines,

        [Parameter(ParameterSetName = 'Content')]
        [Parameter(ParameterSetName = 'FilePath')]
        [switch] $RemoveEmptyLines
    )

    if ($PSVersionTable.PSVersion.Major -gt 5) {
        $Encoding = 'UTF8BOM'
    }
    else {
        $Encoding = 'UTF8'
    }

    if ($SourceFilePath) {
        $Fullpath = Resolve-Path -LiteralPath $SourceFilePath
        $Content = [IO.File]::ReadAllText($FullPath)
    }
    if ($RemoveEmptyLines) {

        $Content = $Content -replace '(?m)^\s*$', ''
    }
    if ($RemoveAllEmptyLines) {

        $Content = $Content -replace '(?m)^\s*$(\r?\n)?', ''
    }
    if ($Content) {
        $Content = $Content.Trim()
    }
    if ($DestinationFilePath) {
        $Content | Set-Content -Path $DestinationFilePath -Encoding $Encoding
    }
    else {
        $Content
    }
}
function Remove-EmptyValue {
    [alias('Remove-EmptyValues')]
    [CmdletBinding()]
    param(
        [alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable,
        [string[]] $ExcludeParameter,
        [switch] $Recursive,
        [int] $Rerun,
        [switch] $DoNotRemoveNull,
        [switch] $DoNotRemoveEmpty,
        [switch] $DoNotRemoveEmptyArray,
        [switch] $DoNotRemoveEmptyDictionary
    )
    foreach ($Key in [string[]] $Hashtable.Keys) {
        if ($Key -notin $ExcludeParameter) {
            if ($Recursive) {
                if ($Hashtable[$Key] -is [System.Collections.IDictionary]) {
                    if ($Hashtable[$Key].Count -eq 0) {
                        if (-not $DoNotRemoveEmptyDictionary) {
                            $Hashtable.Remove($Key)
                        }
                    }
                    else {
                        Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive
                    }
                }
                else {
                    if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) {
                        $Hashtable.Remove($Key)
                    }
                    elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') {
                        $Hashtable.Remove($Key)
                    }
                    elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) {
                        $Hashtable.Remove($Key)
                    }
                }
            }
            else {
                if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) {
                    $Hashtable.Remove($Key)
                }
                elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') {
                    $Hashtable.Remove($Key)
                }
                elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) {
                    $Hashtable.Remove($Key)
                }
            }
        }
    }
    if ($Rerun) {
        for ($i = 0; $i -lt $Rerun; $i++) {
            Remove-EmptyValue -Hashtable $Hashtable -Recursive:$Recursive
        }
    }
}
function Remove-ItemAlternative {
    <#
    .SYNOPSIS
    Removes all files and folders within given path
 
    .DESCRIPTION
    Removes all files and folders within given path. Workaround for Access to the cloud file is denied issue
 
    .PARAMETER Path
    Path to file/folder
 
    .PARAMETER SkipFolder
    Do not delete top level folder
 
    .PARAMETER Exclude
    Skip files/folders matching given pattern
 
    .EXAMPLE
    Remove-ItemAlternative -Path "C:\Support\GitHub\GpoZaurr\Docs"
 
    .EXAMPLE
    Remove-ItemAlternative -Path "C:\Support\GitHub\GpoZaurr\Docs"
 
    .NOTES
    General notes
    #>

    [cmdletbinding()]
    param(
        [alias('LiteralPath')][string] $Path,
        [switch] $SkipFolder,
        [string[]] $Exclude
    )
    if ($Path -and (Test-Path -LiteralPath $Path)) {
        $getChildItemSplat = @{
            Path    = $Path
            Recurse = $true
            Force   = $true
            File    = $true
            Exclude = $Exclude
        }
        Remove-EmptyValue -Hashtable $getChildItemSplat
        $Items = Get-ChildItem @getChildItemSplat
        foreach ($Item in $Items) {
            try {
                $Item.Delete()
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    throw "Couldn't delete $($Item.FullName), error: $($_.Exception.Message)"
                }
                else {
                    Write-Warning "Remove-ItemAlternative - Couldn't delete $($Item.FullName), error: $($_.Exception.Message)"
                }
            }
        }
        $getChildItemSplat = @{
            Path    = $Path
            Recurse = $true
            Force   = $true
            Exclude = $Exclude
        }
        Remove-EmptyValue -Hashtable $getChildItemSplat
        $Items = Get-ChildItem @getChildItemSplat | Sort-Object -Descending -Property 'FullName'
        foreach ($Item in $Items) {
            try {
                $Item.Delete()
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    throw "Couldn't delete $($Item.FullName), error: $($_.Exception.Message)"
                }
                else {
                    Write-Warning "Remove-ItemAlternative - Couldn't delete $($Item.FullName), error: $($_.Exception.Message)"
                }
            }
        }
        if (-not $SkipFolder.IsPresent) {
            $Item = Get-Item -LiteralPath $Path
            try {
                $Item.Delete($true)
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    throw "Couldn't delete $($Item.FullName), error: $($_.Exception.Message)"
                }
                else {
                    Write-Warning "Remove-ItemAlternative - Couldn't delete $($Item.FullName), error: $($_.Exception.Message)"
                }
            }
        }
    }
    else {
        if ($ErrorActionPreference -eq 'Stop') {
            throw "Remove-ItemAlternative - Path $Path doesn't exists. Skipping."
        }
        else {
            Write-Warning "Remove-ItemAlternative - Path $Path doesn't exists. Skipping."
        }
    }
}
$Script:FormatterSettings = @{
    IncludeRules = @(
        'PSPlaceOpenBrace',
        'PSPlaceCloseBrace',
        'PSUseConsistentWhitespace',
        'PSUseConsistentIndentation',
        'PSAlignAssignmentStatement',
        'PSUseCorrectCasing'
    )

    Rules        = @{
        PSPlaceOpenBrace           = @{
            Enable             = $true
            OnSameLine         = $true
            NewLineAfter       = $true
            IgnoreOneLineBlock = $true
        }

        PSPlaceCloseBrace          = @{
            Enable             = $true
            NewLineAfter       = $false
            IgnoreOneLineBlock = $true
            NoEmptyLineBefore  = $false
        }

        PSUseConsistentIndentation = @{
            Enable              = $true
            Kind                = 'space'
            PipelineIndentation = 'IncreaseIndentationAfterEveryPipeline'
            IndentationSize     = 4
        }

        PSUseConsistentWhitespace  = @{
            Enable          = $true
            CheckInnerBrace = $true
            CheckOpenBrace  = $true
            CheckOpenParen  = $true
            CheckOperator   = $true
            CheckPipe       = $true
            CheckSeparator  = $true
        }

        PSAlignAssignmentStatement = @{
            Enable         = $true
            CheckHashtable = $true
        }

        PSUseCorrectCasing         = @{
            Enable = $true
        }
    }
}
function Send-FilesToGitHubRelease {
    [cmdletbinding()]
    param(
        [string[]] $filePathsToUpload,
        [string] $urlToUploadFilesTo,
        $authHeader
    )
    [int] $numberOfFilesToUpload = $filePathsToUpload.Count
    [int] $numberOfFilesUploaded = 0

    $filePathsToUpload | ForEach-Object {
        $filePath = $_
        $fileName = Get-Item $filePath | Select-Object -ExpandProperty Name

        $uploadAssetWebRequestParameters =
        @{

            Uri         = $urlToUploadFilesTo + "?name=$fileName"
            Method      = 'POST'
            Headers     = $authHeader
            ContentType = 'application/zip'
            InFile      = $filePath
        }

        $numberOfFilesUploaded = $numberOfFilesUploaded + 1
        Write-Verbose "Uploading asset $numberOfFilesUploaded of $numberOfFilesToUpload, '$filePath'."
        Invoke-RestMethodAndThrowDescriptiveErrorOnFailure $uploadAssetWebRequestParameters > $null
    }
}

function Start-ArtefactsBuilding {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $ChosenArtefact,
        [System.Collections.IDictionary] $Configuration,
        [string] $FullProjectPath,
        [System.Collections.IDictionary] $DestinationPaths,
        [ValidateSet('ReleasesUnpacked', 'Releases')][string] $Type
    )

    if ($Artefact) {
        $Artefact = $ChosenArtefact
        $ChosenType = $Artefact.Type
        $ID = if ($ChosenArtefact.ID) {
            $ChosenArtefact.ID 
        }
        else {
            $null 
        }
    }
    elseif ($Type) {
        if ($Configuration.Steps.BuildModule.$Type) {
            $Artefact = $Configuration.Steps.BuildModule.$Type
            $ChosenType = $Type
        }
        else {
            $Artefact = $null
        }
        $ID = $null
    }
    else {
        $ID = $null
        $Artefact = $null
    }

    if ($ID) {
        $TextToDisplay = "Preparing Artefact of type '$ChosenType' (ID: $ID)"
    }
    else {
        $TextToDisplay = "Preparing Artefact of type '$ChosenType'"
    }

    if ($null -eq $Artefact -or $Artefact.Count -eq 0) {
        return
    }

    $ModuleName = $Configuration.Information.ModuleName
    $ModuleVersion = $Configuration.Information.Manifest.ModuleVersion

    $FullProjectPath = Initialize-ReplacePath -ReplacementPath $FullProjectPath -ModuleName $ModuleName -ModuleVersion $ModuleVersion -Configuration $Configuration
    $Artefact.Path = Initialize-ReplacePath -ReplacementPath $Artefact.Path -ModuleName $ModuleName -ModuleVersion $ModuleVersion -Configuration $Configuration

    Write-TextWithTime -Text $TextToDisplay -PreAppend Information -SpacesBefore ' ' {
        if ($Artefact -or $Artefact.Enabled) {
            if ($Artefact -is [System.Collections.IDictionary]) {
                if ($Artefact.Path) {
                    if ($Artefact.Relative -eq $false) {
                        $FolderPathReleases = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Artefact.Path)
                    }
                    else {
                        $FolderPathReleases = [System.IO.Path]::Combine($FullProjectPath, $Artefact.Path)
                    }
                }
                else {
                    $FolderPathReleases = [System.IO.Path]::Combine($FullProjectPath, $Type)
                }
            }
            else {

                $FolderPathReleases = [System.IO.Path]::Combine($FullProjectPath, $Type)
            }
            if ($Artefact.RequiredModules.ModulesPath) {
                $DirectPathForPrimaryModule = $Artefact.RequiredModules.ModulesPath
            }
            elseif ($Artefact.RequiredModules.Path) {
                $DirectPathForPrimaryModule = $Artefact.RequiredModules.Path
            }
            elseif ($Artefact.Path) {
                $DirectPathForPrimaryModule = $Artefact.Path
            }
            else {
                $DirectPathForPrimaryModule = $FolderPathReleases
            }
            if ($Artefact.RequiredModules.Path) {
                $DirectPathForRequiredModules = $Artefact.RequiredModules.Path
            }
            elseif ($Artefact.RequiredModules.ModulesPath) {
                $DirectPathForRequiredModules = $Artefact.RequiredModules.ModulesPath
            }
            elseif ($Artefact.Path) {
                $DirectPathForRequiredModules = $Artefact.Path
            }
            else {
                $DirectPathForRequiredModules = $FolderPathReleases
            }
            if ($Artefact -eq $true -or $Artefact.Enabled) {
                if ($Artefact -is [System.Collections.IDictionary]) {
                    if ($DirectPathForPrimaryModule) {
                        if ($Artefact.Relative -eq $false) {
                            $CurrentModulePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($DirectPathForPrimaryModule)
                        }
                        else {
                            $DirectPathForPrimaryModule = Initialize-ReplacePath -ReplacementPath $DirectPathForPrimaryModule -ModuleName $ModuleName -ModuleVersion $ModuleVersion -Configuration $Configuration
                            $CurrentModulePath = [System.IO.Path]::Combine($FullProjectPath, $DirectPathForPrimaryModule)
                        }
                    }
                    else {
                        $CurrentModulePath = [System.IO.Path]::Combine($FullProjectPath, $ChosenType)
                    }
                    if ($DirectPathForRequiredModules) {
                        if ($Artefact.Relative -eq $false) {
                            $RequiredModulesPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($DirectPathForRequiredModules)
                        }
                        else {
                            $DirectPathForRequiredModules = Initialize-ReplacePath -ReplacementPath $DirectPathForRequiredModules -ModuleName $ModuleName -ModuleVersion $ModuleVersion -Configuration $Configuration
                            $RequiredModulesPath = [System.IO.Path]::Combine($FullProjectPath, $DirectPathForRequiredModules)
                        }
                    }
                    else {
                        $RequiredModulesPath = $ArtefactsPath
                    }
                    $ArtefactsPath = $Artefact.Path
                }
                else {

                    $ArtefactsPath = [System.IO.Path]::Combine($FullProjectPath, $ChosenType, $TagName)
                    $RequiredModulesPath = $ArtefactsPath
                    $CurrentModulePath = $ArtefactsPath
                }

                if ($null -eq $Artefact.DestinationFilesRelative) {
                    if ($null -ne $Artefact.Relative) {
                        $Artefact.DestinationFilesRelative = $Artefact.Relative
                    }
                }
                if ($null -eq $Artefact.DestinationDirectoriesRelative) {
                    if ($null -ne $Artefact.Relative) {
                        $Artefact.DestinationDirectoriesRelative = $Artefact.Relative
                    }
                }

                $SplatArtefact = @{
                    ModuleName                     = $Configuration.Information.ModuleName
                    ModuleVersion                  = $Configuration.Information.Manifest.ModuleVersion
                    LegacyName                     = if ($Artefact -is [bool]) {
                        $true 
                    }
                    else {
                        $false 
                    }
                    CopyMainModule                 = $true
                    CopyRequiredModules            = $Artefact.RequiredModules -eq $true -or $Artefact.RequiredModules.Enabled
                    ProjectPath                    = $FullProjectPath
                    Destination                    = $ArtefactsPath
                    DestinationMainModule          = $CurrentModulePath
                    DestinationRequiredModules     = $RequiredModulesPath
                    RequiredModules                = $Configuration.Information.Manifest.RequiredModules
                    Files                          = $Artefact.FilesOutput
                    Folders                        = $Artefact.DirectoryOutput
                    DestinationFilesRelative       = $Artefact.DestinationFilesRelative
                    DestinationDirectoriesRelative = $Artefact.DestinationDirectoriesRelative
                    Configuration                  = $Configuration
                    IncludeTag                     = $Artefact.IncludeTagName
                    ArtefactName                   = $Artefact.ArtefactName
                    ScriptName                     = $Artefact.ScriptName
                    ZipIt                          = if ($ChosenType -in 'Packed', 'Releases', 'ScriptPacked') {
                        $true 
                    }
                    else {
                        $false 
                    }
                    ConvertToScript                = if ($ChosenType -in 'ScriptPacked', 'Script') {
                        $true 
                    }
                    else {
                        $false 
                    }
                    DestinationZip                 = $ArtefactsPath
                    PreScriptMerge                 = $Artefact.PreScriptMerge
                    PostScriptMerge                = $Artefact.PostScriptMerge
                    DoNotClear                     = $Artefact.DoNotClear
                    ID                             = if ($ChosenArtefact.ID) {
                        $ChosenArtefact.ID 
                    }
                    else {
                        $null 
                    }
                }
                Remove-EmptyValue -Hashtable $SplatArtefact
                Add-Artefact @SplatArtefact
            }
        }
    } -ColorBefore Yellow -ColorTime Yellow -Color Yellow
}
function Start-DocumentationBuilding {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Configuration,
        [string] $FullProjectPath,
        [string] $ProjectName
    )

    if ($Configuration.Steps.BuildDocumentation -is [bool]) {
        $TemporaryBuildDocumentation = $Configuration.Steps.BuildDocumentation
        $Configuration.Steps.BuildDocumentation = @{
            Enable = $TemporaryBuildDocumentation
        }
    }

    if ($Configuration.Steps.BuildDocumentation -is [System.Collections.IDictionary]) {
        if ($Configuration.Steps.BuildDocumentation.Enable -eq $true) {
            $WarningVariablesMarkdown = @()
            $DocumentationPath = "$FullProjectPath\$($Configuration.Options.Documentation.Path)"
            $ReadMePath = "$FullProjectPath\$($Configuration.Options.Documentation.PathReadme)"
            Write-Text "[+] Generating documentation to $DocumentationPath with $ReadMePath" -Color Yellow

            if (-not (Test-Path -Path $DocumentationPath)) {
                $null = New-Item -Path "$FullProjectPath\Docs" -ItemType Directory -Force
            }
            [Array] $Files = Get-ChildItem -Path $DocumentationPath
            if ($Files.Count -gt 0) {
                if ($Configuration.Steps.BuildDocumentation.StartClean -ne $true) {
                    try {
                        $null = Update-MarkdownHelpModule $DocumentationPath -RefreshModulePage -ModulePagePath $ReadMePath -ErrorAction Stop -WarningVariable +WarningVariablesMarkdown -WarningAction SilentlyContinue -ExcludeDontShow
                    }
                    catch {
                        Write-Text "[-] Documentation warning: $($_.Exception.Message)" -Color Yellow
                    }
                }
                else {
                    Remove-ItemAlternative -Path $DocumentationPath -SkipFolder
                    [Array] $Files = Get-ChildItem -Path $DocumentationPath
                }
            }
            if ($Files.Count -eq 0) {
                try {
                    $null = New-MarkdownHelp -Module $ProjectName -WithModulePage -OutputFolder $DocumentationPath -ErrorAction Stop -WarningVariable +WarningVariablesMarkdown -WarningAction SilentlyContinue -ExcludeDontShow
                }
                catch {
                    Write-Text "[-] Documentation warning: $($_.Exception.Message)" -Color Yellow
                }
                $null = Move-Item -Path "$DocumentationPath\$ProjectName.md" -Destination $ReadMePath -ErrorAction SilentlyContinue

                if ($Configuration.Steps.BuildDocumentation.UpdateWhenNew) {
                    try {
                        $null = Update-MarkdownHelpModule $DocumentationPath -RefreshModulePage -ModulePagePath $ReadMePath -ErrorAction Stop -WarningVariable +WarningVariablesMarkdown -WarningAction SilentlyContinue -ExcludeDontShow
                    }
                    catch {
                        Write-Text "[-] Documentation warning: $($_.Exception.Message)" -Color Yellow
                    }
                }
            }
            foreach ($_ in $WarningVariablesMarkdown) {
                Write-Text "[-] Documentation warning: $_" -Color Yellow
            }
        }
    }
}
function Start-ImportingModules {
    [CmdletBinding()]
    param(
        [string] $ProjectName,
        [System.Collections.IDictionary] $Configuration
    )
    $TemporaryVerbosePreference = $VerbosePreference
    $TemporaryErrorPreference = $global:ErrorActionPreference
    $global:ErrorActionPreference = 'Stop'

    if ($null -ne $ImportModules.Verbose) {
        $VerbosePreference = $true
    }
    else {
        $VerbosePreference = $false
    }
    if ($Configuration.Steps.ImportModules.RequiredModules) {
        Write-TextWithTime -Text 'Importing modules (as defined in dependencies)' {
            foreach ($Module in $Configuration.Information.Manifest.RequiredModules) {
                if ($Module -is [System.Collections.IDictionary]) {
                    Write-Text " [>] Importing required module - $($Module.ModuleName)" -Color Yellow
                    if ($Module.ModuleVersion) {
                        Import-Module -Name $Module.ModuleName -MinimumVersion $Module.ModuleVersion -Force -ErrorAction Stop -Verbose:$VerbosePreference
                    }
                    elseif ($Module.ModuleName) {
                        Import-Module -Name $Module.ModuleName -Force -ErrorAction Stop -Verbose:$VerbosePreference
                    }
                }
                elseif ($Module -is [string]) {
                    Write-Text " [>] Importing required module - $($Module)" -Color Yellow
                    Import-Module -Name $Module -Force -ErrorAction Stop -Verbose:$VerbosePreference
                }
            }
        } -PreAppend 'Information'
    }
    if ($Configuration.Steps.ImportModules.Self) {
        Write-TextWithTime -Text 'Importing module - SELF' {

            Import-Module -Name $ProjectName -Force -ErrorAction Stop -Verbose:$VerbosePreference
        } -PreAppend 'Information'
    }
    $global:ErrorActionPreference = $TemporaryErrorPreference
    $VerbosePreference = $TemporaryVerbosePreference
}
function Start-LibraryBuilding {
    [CmdletBinding()]
    param(
        [string] $ModuleName,
        [string] $RootDirectory,
        [string] $Version,
        [System.Collections.IDictionary] $LibraryConfiguration

    )
    if ($LibraryConfiguration.Count -eq 0) {
        return
    }
    if ($LibraryConfiguration.Enable -ne $true) {
        return
    }

    $TranslateFrameworks = [ordered] @{
        'NetStandard2.0' = 'Standard'
        'netStandard2.1' = 'Standard'
        'net472'         = 'Default'
        'net48'          = 'Default'
        'net470'         = 'Default'
        'netcoreapp3.1'  = 'Core'
    }

    if ($LibraryConfiguration.Configuration) {
        $Configuration = $LibraryConfiguration.Configuration
    }
    else {
        $Configuration = 'Release'
    }
    if ($LibraryConfiguration.ProjectName) {
        $ModuleName = $LibraryConfiguration.ProjectName
    }

    $ModuleProjectFile = [System.IO.Path]::Combine($RootDirectory, "Sources", $ModuleName, "$ModuleName.csproj")
    $SourceFolder = [System.IO.Path]::Combine($RootDirectory, "Sources", $ModuleName)
    $ModuleBinFolder = [System.IO.Path]::Combine($RootDirectory, "Lib")
    if (Test-Path -LiteralPath $ModuleBinFolder) {
        $Items = Get-ChildItem -LiteralPath $ModuleBinFolder -Recurse -Force
        $Items | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue
    }
    $null = New-Item -Path $ModuleBinFolder -ItemType Directory -Force

    try {
        Push-Location -Path $SourceFolder -ErrorAction Stop
    }
    catch {
        Write-Text "[-] Couldn't switch to folder $SourceFolder. Error: $($_.Exception.Message)" -Color Red
        return $false
    }
    try {
        [xml] $ProjectInformation = Get-Content -Raw -LiteralPath $ModuleProjectFile -Encoding UTF8 -ErrorAction Stop
    }
    catch {
        Write-Text "[-] Can't read $ModuleProjectFile file. Error: $($_.Exception.Message)" -Color Red
        return $false
    }
    $SupportedFrameworks = foreach ($PropertyGroup in $ProjectInformation.Project.PropertyGroup) {
        if ($PropertyGroup.TargetFrameworks) {
            $PropertyGroup.TargetFrameworks -split ";"
        }
    }

    foreach ($Framework in $TranslateFrameworks.Keys) {
        if ($SupportedFrameworks.Contains($Framework.ToLower()) -and $LibraryConfiguration.Framework.Contains($Framework.ToLower())) {
            Write-Text "[+] Building $Framework ($Configuration)"
            dotnet publish --configuration $Configuration --verbosity q -nologo -p:Version=$Version --framework $Framework
            if ($LASTEXITCODE) {
                Write-Host 
                Write-Text "[-] Building $Framework - failed. Error: $LASTEXITCODE" -Color Red
                Exit
            }
        }
        else {
            continue
        }

        $PublishDirFolder = [System.IO.Path]::Combine($SourceFolder, "bin", $Configuration, $Framework, "publish", "*")
        $ModuleBinFrameworkFolder = [System.IO.Path]::Combine($ModuleBinFolder, $TranslateFrameworks[$Framework])

        New-Item -Path $ModuleBinFrameworkFolder -ItemType Directory -ErrorAction SilentlyContinue | Out-Null

        try {
            $List = Get-ChildItem -Filter "*.dll" -ErrorAction Stop -Path $PublishDirFolder -File
        }
        catch {
            Write-Text "[-] Can't list files in $PublishDirFolder folder. Error: $($_.Exception.Message)" -Color Red
            return $false
        }
        $Errors = $false
        :fileLoop foreach ($File in $List) {
            if ($LibraryConfiguration.ExcludeMainLibrary -and $File.Name -eq "$ModuleName.dll") {
                continue
            }
            if ($LibraryConfiguration.ExcludeLibraryFilter) {
                foreach ($Library in $LibraryConfiguration.ExcludeLibraryFilter) {
                    if ($File.Name -like $Library) {
                        continue fileLoop
                    }
                }
            }
            try {
                Copy-Item -Path $File.FullName -Destination $ModuleBinFrameworkFolder -ErrorAction Stop
            }
            catch {
                Write-Text "[-] Copying $File to $ModuleBinFrameworkFolder failed. Error: $($_.Exception.Message)" -Color Red
                $Errors = $true
            }
        }
        if ($Errors) {
            return $false
        }
    }
    Try {
        Pop-Location -ErrorAction Stop
    }
    catch {
        Write-Text "[-] Couldn't switch back to the root folder. Error: $($_.Exception.Message)" -Color Red
        return $false
    }
}
function Start-ModuleBuilding {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Configuration,
        [string] $PathToProject
    )
    $DestinationPaths = [ordered] @{ }
    if ($Configuration.Information.Manifest.CompatiblePSEditions) {
        if ($Configuration.Information.Manifest.CompatiblePSEditions -contains 'Desktop') {
            $DestinationPaths.Desktop = [IO.path]::Combine($Configuration.Information.DirectoryModules, $Configuration.Information.ModuleName)
        }
        if ($Configuration.Information.Manifest.CompatiblePSEditions -contains 'Core') {
            $DestinationPaths.Core = [IO.path]::Combine($Configuration.Information.DirectoryModulesCore, $Configuration.Information.ModuleName)
        }
    }
    else {

        $DestinationPaths.Desktop = [IO.path]::Combine($Configuration.Information.DirectoryModules, $Configuration.Information.ModuleName)
        $DestinationPaths.Core = [IO.path]::Combine($Configuration.Information.DirectoryModulesCore, $Configuration.Information.ModuleName)
    }

    [string] $Random = Get-Random 10000000000
    [string] $FullModuleTemporaryPath = [IO.path]::GetTempPath() + '' + $Configuration.Information.ModuleName
    [string] $FullTemporaryPath = [IO.path]::GetTempPath() + '' + $Configuration.Information.ModuleName + "_TEMP_$Random"
    if ($Configuration.Information.DirectoryProjects) {
        [string] $FullProjectPath = [IO.Path]::Combine($Configuration.Information.DirectoryProjects, $Configuration.Information.ModuleName)
    }
    else {
        [string] $FullProjectPath = $PathToProject
    }
    [string] $ProjectName = $Configuration.Information.ModuleName

    $PSD1FilePath = [System.IO.Path]::Combine($FullProjectPath, "$ProjectName.psd1")
    $PSM1FilePath = [System.IO.Path]::Combine($FullProjectPath, "$ProjectName.psm1")

    if ($Configuration.Information.Manifest.ModuleVersion) {
        if ($Configuration.Steps.BuildModule.LocalVersion) {
            $Versioning = Step-Version -Module $Configuration.Information.ModuleName -ExpectedVersion $Configuration.Information.Manifest.ModuleVersion -Advanced -LocalPSD1 $PSD1FilePath
        }
        else {
            $Versioning = Step-Version -Module $Configuration.Information.ModuleName -ExpectedVersion $Configuration.Information.Manifest.ModuleVersion -Advanced
        }
        $Configuration.Information.Manifest.ModuleVersion = $Versioning.Version
    }
    else {

        $Configuration.Information.Manifest.ModuleVersion = 1.0.0
    }
    Write-Text '----------------------------------------------------'
    Write-Text "[i] Project/Module Name: $ProjectName" -Color Yellow
    if ($Configuration.Steps.BuildModule.LocalVersion) {
        Write-Text "[i] Current Local Version: $($Versioning.CurrentVersion)" -Color Yellow
    }
    else {
        Write-Text "[i] Current PSGallery Version: $($Versioning.CurrentVersion)" -Color Yellow
    }
    Write-Text "[i] Expected Version: $($Configuration.Information.Manifest.ModuleVersion)" -Color Yellow
    Write-Text "[i] Full module temporary path: $FullModuleTemporaryPath" -Color Yellow
    Write-Text "[i] Full project path: $FullProjectPath" -Color Yellow
    Write-Text "[i] Full temporary path: $FullTemporaryPath" -Color Yellow
    Write-Text "[i] PSScriptRoot: $PSScriptRoot" -Color Yellow
    Write-Text "[i] Current PSEdition: $PSEdition" -Color Yellow
    Write-Text "[i] Destination Desktop: $($DestinationPaths.Desktop)" -Color Yellow
    Write-Text "[i] Destination Core: $($DestinationPaths.Core)" -Color Yellow
    Write-Text '----------------------------------------------------'

    if (-not $Configuration.Steps.BuildModule) {
        Write-Text '[-] Section BuildModule is missing. Terminating.' -Color Red
        return $false
    }

    if (-not $Configuration.Information.ModuleName) {
        Write-Text '[-] Section Information.ModuleName is missing. Terminating.' -Color Red
        return $false
    }

    if (-not (Test-Path -Path $FullProjectPath)) {
        Write-Text "[-] Project path doesn't exists $FullProjectPath. Terminating" -Color Red
        return $false
    }

    $Success = Start-LibraryBuilding -RootDirectory $FullProjectPath -Version $Configuration.Information.Manifest.ModuleVersion -ModuleName $ProjectName -LibraryConfiguration $Configuration.Steps.BuildLibraries
    if ($Success -eq $false) {
        return $false
    }

    $Success = Convert-RequiredModules -Configuration $Configuration
    if ($Success -eq $false) {
        return $false
    }

    if ($Configuration.Steps.BuildModule.Enable -eq $true) {
        $CurrentLocation = (Get-Location).Path

        $Success = Start-PreparingStructure -Configuration $Configuration -FullProjectPath $FullProjectPath -FullTemporaryPath $FullTemporaryPath -FullModuleTemporaryPath $FullModuleTemporaryPath -DestinationPaths $DestinationPaths
        if ($Success -eq $false) {
            return $false
        }

        $Variables = Start-PreparingVariables -Configuration $Configuration -FullProjectPath $FullProjectPath
        if ($Variables -eq $false) {
            return $false
        }

        $LinkDirectories = $Variables.LinkDirectories
        $LinkFilesRoot = $Variables.LinkFilesRoot
        $LinkPrivatePublicFiles = $Variables.LinkPrivatePublicFiles
        $DirectoriesWithClasses = $Variables.DirectoriesWithClasses
        $DirectoriesWithPS1 = $Variables.DirectoriesWithPS1
        $Files = $Variables.Files

        $AliasesAndFunctions = Start-PreparingFunctionsAndAliases -Configuration $Configuration -FullProjectPath $FullProjectPath -Files $Files
        if ($AliasesAndFunctions -eq $false) {
            return $false
        }

        $SaveConfiguration = Copy-DictionaryManual -Dictionary $Configuration

        if ($Configuration.Steps.BuildModule.UseWildcardForFunctions) {
            $Success = New-PersonalManifest -Configuration $Configuration -ManifestPath $PSD1FilePath -AddScriptsToProcess -UseWildcardForFunctions:$Configuration.Steps.BuildModule.UseWildcardForFunctions
            if ($Success -eq $false) {
                return $false
            }
        }
        else {
            $Success = New-PersonalManifest -Configuration $Configuration -ManifestPath $PSD1FilePath -AddScriptsToProcess
            if ($Success -eq $false) {
                return $false
            }
        }
        Write-TextWithTime -Text "Verifying created PSD1 is readable" -PreAppend Information {
            if (Test-Path -LiteralPath $PSD1FilePath) {
                try {
                    $null = Import-PowerShellDataFile -Path $PSD1FilePath -ErrorAction Stop
                }
                catch {
                    Write-Text "[-] PSD1 Reading $PSD1FilePath failed. Error: $($_.Exception.Message)" -Color Red
                    return $false
                }
            }
            else {
                Write-Text "[-] PSD1 Reading $PSD1FilePath failed. File not created..." -Color Red
                return $false
            }
        } -ColorBefore Yellow -ColorTime Yellow -Color Yellow

        $Configuration = $SaveConfiguration

        $Success = Format-Code -FilePath $PSD1FilePath -FormatCode $Configuration.Options.Standard.FormatCodePSD1
        if ($Success -eq $false) {
            return $false
        }
        $Success = Format-Code -FilePath $PSM1FilePath -FormatCode $Configuration.Options.Standard.FormatCodePSM1
        if ($Success -eq $false) {
            return $false
        }

        if ($Configuration.Steps.BuildModule.RefreshPSD1Only) {
            return
        }

        if ($Configuration.Steps.BuildModule.Merge) {
            foreach ($Directory in $LinkDirectories) {
                $Dir = [System.IO.Path]::Combine($FullTemporaryPath, "$Directory")
                Add-Directory -Directory $Dir
            }

            [Array] $CompareWorkaround = foreach ($Directory in $DirectoriesWithPS1) {
                if ($null -eq $IsWindows -or $IsWindows -eq $true) {
                    $Dir = -join ($Directory, "\")
                }
                else {
                    $Dir = -join ($Directory, "/")
                }
            }

            $LinkDirectoriesWithSupportFiles = $LinkDirectories | Where-Object { $_ -notin $CompareWorkaround }
            foreach ($Directory in $LinkDirectoriesWithSupportFiles) {
                $Dir = [System.IO.Path]::Combine($FullModuleTemporaryPath, "$Directory")
                Add-Directory -Directory $Dir
            }

            $LinkingFilesTime = Write-Text "[+] Linking files from root and sub directories" -Start
            Copy-InternalFiles -LinkFiles $LinkFilesRoot -FullModulePath $FullTemporaryPath -FullProjectPath $FullProjectPath
            Copy-InternalFiles -LinkFiles $LinkPrivatePublicFiles -FullModulePath $FullTemporaryPath -FullProjectPath $FullProjectPath
            Write-Text -End -Time $LinkingFilesTime

            $FilesToLink = $LinkPrivatePublicFiles | Where-Object { $_ -notlike '*.ps1' -and $_ -notlike '*.psd1' }
            Copy-InternalFiles -LinkFiles $FilesToLink -FullModulePath $FullModuleTemporaryPath -FullProjectPath $FullProjectPath

            if ($Configuration.Information.LibrariesStandard) {
            }
            elseif ($Configuration.Information.LibrariesCore -and $Configuration.Information.LibrariesDefault) {
            }
            else {

                $Configuration.Information.LibrariesStandard = [System.IO.Path]::Combine("Lib", "Standard")
                $Configuration.Information.LibrariesCore = [System.IO.Path]::Combine("Lib", "Core")
                $Configuration.Information.LibrariesDefault = [System.IO.Path]::Combine("Lib", "Default")
            }

            if (-not [string]::IsNullOrWhiteSpace($Configuration.Information.LibrariesCore)) {
                if ($null -eq $IsWindows -or $IsWindows -eq $true) {
                    $StartsWithCore = -join ($Configuration.Information.LibrariesCore, "\")
                }
                else {
                    $StartsWithCore = -join ($Configuration.Information.LibrariesCore, "/")
                }
            }
            if (-not [string]::IsNullOrWhiteSpace($Configuration.Information.LibrariesDefault)) {
                if ($null -eq $IsWindows -or $IsWindows -eq $true) {
                    $StartsWithDefault = -join ($Configuration.Information.LibrariesDefault, "\")
                }
                else {
                    $StartsWithDefault = -join ($Configuration.Information.LibrariesDefault, "/")
                }
            }
            if (-not [string]::IsNullOrWhiteSpace($Configuration.Information.LibrariesStandard)) {
                if ($null -eq $IsWindows -or $IsWindows -eq $true) {
                    $StartsWithStandard = -join ($Configuration.Information.LibrariesStandard, "\")
                }
                else {
                    $StartsWithStandard = -join ($Configuration.Information.LibrariesStandard, "/")
                }
            }

            $CoreFiles = $LinkPrivatePublicFiles | Where-Object { ($_).StartsWith($StartsWithCore) }
            $DefaultFiles = $LinkPrivatePublicFiles | Where-Object { ($_).StartsWith($StartsWithDefault) }
            $StandardFiles = $LinkPrivatePublicFiles | Where-Object { ($_).StartsWith($StartsWithStandard) }

            $Default = $false
            $Core = $false
            $Standard = $false
            if ($CoreFiles.Count -gt 0) {
                $Core = $true
            }
            if ($DefaultFiles.Count -gt 0) {
                $Default = $true
            }
            if ($StandardFiles.Count -gt 0) {
                $Standard = $true
            }
            if ($Standard -and $Core -and $Default) {
                $FrameworkNet = 'Default'
                $Framework = 'Standard'
            }
            elseif ($Standard -and $Core) {
                $Framework = 'Standard'
                $FrameworkNet = 'Standard'
            }
            elseif ($Core -and $Default) {
                $Framework = 'Core'
                $FrameworkNet = 'Default'
            }
            elseif ($Standard -and $Default) {
                $Framework = 'Standard'
                $FrameworkNet = 'Default'
            }
            elseif ($Standard) {
                $Framework = 'Standard'
                $FrameworkNet = 'Standard'
            }
            elseif ($Core) {
                $Framework = 'Core'
                $FrameworkNet = ''
            }
            elseif ($Default) {
                $Framework = ''
                $FrameworkNet = 'Default'
            }

            if ($Framework -eq 'Core') {
                $FilesLibrariesCore = $CoreFiles
            }
            elseif ($Framework -eq 'Standard') {
                $FilesLibrariesCore = $StandardFiles
            }
            if ($FrameworkNet -eq 'Default') {
                $FilesLibrariesDefault = $DefaultFiles
            }
            elseif ($FrameworkNet -eq 'Standard') {
                $FilesLibrariesDefault = $StandardFiles
            }
            if ($FrameworkNet -eq 'Standard' -and $Framework -eq 'Standard') {
                $FilesLibrariesStandard = $FilesLibrariesCore
            }
            $Success = Merge-Module -ModuleName $ProjectName `
                -ModulePathSource $FullTemporaryPath `
                -ModulePathTarget $FullModuleTemporaryPath `
                -Sort $Configuration.Options.Merge.Sort `
                -FunctionsToExport $Configuration.Information.Manifest.FunctionsToExport `
                -AliasesToExport $Configuration.Information.Manifest.AliasesToExport `
                -AliasesAndFunctions $AliasesAndFunctions `
                -LibrariesStandard $FilesLibrariesStandard `
                -LibrariesCore $FilesLibrariesCore `
                -LibrariesDefault $FilesLibrariesDefault `
                -FormatCodePSM1 $Configuration.Options.Merge.FormatCodePSM1 `
                -FormatCodePSD1 $Configuration.Options.Merge.FormatCodePSD1 `
                -Configuration $Configuration -DirectoriesWithPS1 $DirectoriesWithPS1 `
                -ClassesPS1 $DirectoriesWithClasses -IncludeAsArray $Configuration.Information.IncludeAsArray

            if ($Success -eq $false) {
                return $false
            }

            if ($Configuration.Steps.BuildModule.CreateFileCatalog) {

                $TimeToExecuteSign = [System.Diagnostics.Stopwatch]::StartNew()
                Write-Text "[+] Creating file catalog" -Color Blue
                $TimeToExecuteSign = [System.Diagnostics.Stopwatch]::StartNew()
                $CategoryPaths = @(
                    $FullModuleTemporaryPath
                    $NotEmptyPaths = (Get-ChildItem -Directory -Path $FullModuleTemporaryPath -Recurse).FullName
                    if ($NotEmptyPaths) {
                        $NotEmptyPaths
                    }
                )
                foreach ($CatPath in $CategoryPaths) {
                    $CatalogFile = [io.path]::Combine($CatPath, "$ProjectName.cat")
                    $FileCreated = New-FileCatalog -Path $CatPath -CatalogFilePath $CatalogFile -CatalogVersion 2.0
                    if ($FileCreated) {
                        Write-Text " [>] Catalog file covering $CatPath was created $($FileCreated.Name)" -Color Yellow
                    }
                }
                $TimeToExecuteSign.Stop()
                Write-Text "[+] Creating file catalog [Time: $($($TimeToExecuteSign.Elapsed).Tostring())]" -Color Blue
            }
            $SuccessFullSigning = Start-ModuleSigning -Configuration $Configuration -FullModuleTemporaryPath $FullModuleTemporaryPath
            if ($SuccessFullSigning -eq $false) {
                return $false
            }
        }
        if (-not $Configuration.Steps.BuildModule.Merge) {
            foreach ($Directory in $LinkDirectories) {
                $Dir = [System.IO.Path]::Combine($FullModuleTemporaryPath, "$Directory")
                Add-Directory -Directory $Dir
            }
            $LinkingFilesTime = Write-Text "[+] Linking files from root and sub directories" -Start
            Copy-InternalFiles -LinkFiles $LinkFilesRoot -FullModulePath $FullModuleTemporaryPath -FullProjectPath $FullProjectPath
            Copy-InternalFiles -LinkFiles $LinkPrivatePublicFiles -FullModulePath $FullModuleTemporaryPath -FullProjectPath $FullProjectPath
            Write-Text -End -Time $LinkingFilesTime
        }

        Set-Location -Path $CurrentLocation

        $Success = if ($Configuration.Steps.BuildModule.Enable) {
            if ($DestinationPaths.Desktop) {
                Write-TextWithTime -Text "Copy module to PowerShell 5 destination: $($DestinationPaths.Desktop)" {
                    $Success = Remove-Directory -Directory $DestinationPaths.Desktop
                    if ($Success -eq $false) {
                        return $false
                    }
                    Add-Directory -Directory $DestinationPaths.Desktop
                    Get-ChildItem -LiteralPath $FullModuleTemporaryPath | Copy-Item -Destination $DestinationPaths.Desktop -Recurse

                    Get-ChildItem $DestinationPaths.Desktop -Recurse -Force -Directory | Sort-Object -Property FullName -Descending | `
                            Where-Object { $($_ | Get-ChildItem -Force | Select-Object -First 1).Count -eq 0 } | `
                                Remove-Item 
                } -PreAppend Plus
            }
            if ($DestinationPaths.Core) {
                Write-TextWithTime -Text "Copy module to PowerShell 6/7 destination: $($DestinationPaths.Core)" {
                    $Success = Remove-Directory -Directory $DestinationPaths.Core
                    if ($Success -eq $false) {
                        return $false
                    }
                    Add-Directory -Directory $DestinationPaths.Core
                    Get-ChildItem -LiteralPath $FullModuleTemporaryPath | Copy-Item -Destination $DestinationPaths.Core -Recurse

                    Get-ChildItem $DestinationPaths.Core -Recurse -Force -Directory | Sort-Object -Property FullName -Descending | `
                            Where-Object { $($_ | Get-ChildItem -Force | Select-Object -First 1).Count -eq 0 } | `
                                Remove-Item 
                } -PreAppend Plus
            }
        }
        if ($Success -contains $false) {
            return $false
        }
        $Success = Write-TextWithTime -Text "Building artefacts" -PreAppend Information {

            $Success = Start-ArtefactsBuilding -Configuration $Configuration -FullProjectPath $FullProjectPath -DestinationPaths $DestinationPaths -Type 'Releases'
            if ($Success -eq $false) {
                return $false
            }

            $Success = Start-ArtefactsBuilding -Configuration $Configuration -FullProjectPath $FullProjectPath -DestinationPaths $DestinationPaths -Type 'ReleasesUnpacked'
            if ($Success -eq $false) {
                return $false
            }

            foreach ($Artefact in  $Configuration.Steps.BuildModule.Artefacts) {
                $Success = Start-ArtefactsBuilding -Configuration $Configuration -FullProjectPath $FullProjectPath -DestinationPaths $DestinationPaths -ChosenArtefact $Artefact
                if ($Success -contains $false) {
                    return $false
                }
            }
        } -ColorBefore Yellow -ColorTime Yellow -Color Yellow
        if ($Success -contains $false) {
            return $false
        }
    }

    if ($Configuration.Steps.ImportModules) {
        $ImportSuccess = Start-ImportingModules -Configuration $Configuration -ProjectName $ProjectName
        if ($ImportSuccess -contains $false) {
            return $false
        }
    }

    if ($Configuration.Options.TestsAfterMerge) {
        $TestsSuccess = Initialize-InternalTests -Configuration $Configuration -Type 'TestsAfterMerge'
        if ($TestsSuccess -eq $false) {
            return $false
        }
    }

    if ($Configuration.Steps.PublishModule.Enabled) {
        $Publishing = Start-PublishingGallery -Configuration $Configuration
        if ($Publishing -eq $false) {
            return $false
        }
    }

    if ($Configuration.Steps.PublishModule.GitHub) {
        $Publishing = Start-PublishingGitHub -Configuration $Configuration -ProjectName $ProjectName
        if ($Publishing -eq $false) {
            return $false
        }
    }

    foreach ($ChosenNuget in $Configuration.Steps.BuildModule.GalleryNugets) {
        $Success = Start-PublishingGallery -Configuration $Configuration -ChosenNuget $ChosenNuget
        if ($Success -eq $false) {
            return $false
        }
    }

    foreach ($ChosenNuget in $Configuration.Steps.BuildModule.GitHubNugets) {
        $Success = Start-PublishingGitHub -Configuration $Configuration -ChosenNuget $ChosenNuget -ProjectName $ProjectName
        if ($Success -eq $false) {
            return $false
        }
    }

    if ($Configuration.Steps.BuildDocumentation) {
        Start-DocumentationBuilding -Configuration $Configuration -FullProjectPath $FullProjectPath -ProjectName $ProjectName
    }

    Write-Text "[+] Cleaning up directories created in TEMP directory" -Color Yellow
    $Success = Remove-Directory $FullModuleTemporaryPath
    if ($Success -eq $false) {
        return $false
    }
    $Success = Remove-Directory $FullTemporaryPath
    if ($Success -eq $false) {
        return $false
    }
}
function Start-ModuleSigning {
    [CmdletBinding()]
    param(
        $Configuration,
        $FullModuleTemporaryPath
    )
    if ($Configuration.Steps.BuildModule.SignMerged) {
        Write-TextWithTime -Text 'Applying signature to files' {
            $registerCertificateSplat = @{
                WarningAction   = 'SilentlyContinue'
                WarningVariable = 'Warnings'
                LocalStore      = 'CurrentUser'
                Path            = $FullModuleTemporaryPath
                Include         = @('*.ps1', '*.psd1', '*.psm1', '*.dll', '*.cat')
                TimeStampServer = 'http://timestamp.digicert.com'
            }

            if ($Configuration.Options.Signing) {
                if ($Configuration.Options.Signing.CertificatePFXBase64) {
                    $Success = Import-ValidCertificate -CertificateAsBase64 $Configuration.Options.Signing.CertificatePFXBase64 -PfxPassword $Configuration.Options.Signing.CertificatePFXPassword
                    if (-not $Success) {
                        return $false
                    }
                    $registerCertificateSplat.Thumbprint = $Success.Thumbprint
                }
                elseif ($Configuration.Options.Signing.CertificatePFXPath) {
                    $Success = Import-ValidCertificate -FilePath $Configuration.Options.Signing.CertificatePFXPath -PfxPassword $Configuration.Options.Signing.CertificatePFXPassword
                    if (-not $Success) {
                        return $false
                    }

                    $registerCertificateSplat.Thumbprint = $Success.Thumbprint
                }
                else {
                    if ($Configuration.Options.Signing -and $Configuration.Options.Signing.Thumbprint) {
                        $registerCertificateSplat.Thumbprint = $Configuration.Options.Signing.Thumbprint
                    }
                    elseif ($Configuration.Options.Signing -and $Configuration.Options.Signing.CertificateThumbprint) {
                        $registerCertificateSplat.Thumbprint = $Configuration.Options.Signing.CertificateThumbprint
                    }
                }
                [Array] $SignedFiles = Register-Certificate @registerCertificateSplat
                if ($Warnings) {
                    foreach ($W in $Warnings) {
                        Write-Text -Text " [!] $($W.Message)" -Color Red
                    }
                }
                if ($SignedFiles.Count -eq 0) {
                    throw "Please configure certificate for use, or disable signing."
                    return $false
                }
                else {
                    if ($SignedFiles[0].Thumbprint) {
                        Write-Text -Text " [i] Multiple certificates found for signing:"
                        foreach ($Certificate in $SignedFiles) {
                            Write-Text " [>] Certificate $($Certificate.Thumbprint) with subject: $($Certificate.Subject)" -Color Yellow
                        }
                        throw "Please configure single certificate for use or disable signing."
                        return $false
                    }
                    else {
                        foreach ($File in $SignedFiles) {
                            Write-Text " [>] File $($File.Path) with status: $($File.StatusMessage)" -Color Yellow
                        }
                    }
                }
            }
        } -PreAppend Plus
    }
}
function Start-PreparingFunctionsAndAliases {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Configuration,
        $FullProjectPath,
        $Files
    )
    $AliasesAndFunctions = Write-TextWithTime -Text 'Preparing function and aliases names' {
        Get-FunctionAliasesFromFolder -FullProjectPath $FullProjectPath -Files $Files 
    } -PreAppend Information
    Write-TextWithTime -Text "Checking for duplicates in funcions and aliases" {
        if ($AliasesAndFunctions -is [System.Collections.IDictionary]) {
            $Configuration.Information.Manifest.FunctionsToExport = $AliasesAndFunctions.Keys | Where-Object { $_ }
            if (-not $Configuration.Information.Manifest.FunctionsToExport) {
                $Configuration.Information.Manifest.FunctionsToExport = @()
            }

            $Configuration.Information.Manifest.AliasesToExport = $AliasesAndFunctions.Values | ForEach-Object { $_ } | Where-Object { $_ }
            if (-not $Configuration.Information.Manifest.AliasesToExport) {
                $Configuration.Information.Manifest.AliasesToExport = @()
            }
        }
        else {

            $Configuration.Information.Manifest.FunctionsToExport = $AliasesAndFunctions.Name | Where-Object { $_ }
            if (-not $Configuration.Information.Manifest.FunctionsToExport) {
                $Configuration.Information.Manifest.FunctionsToExport = @()
            }
            $Configuration.Information.Manifest.AliasesToExport = $AliasesAndFunctions.Alias | ForEach-Object { $_ } | Where-Object { $_ }
            if (-not $Configuration.Information.Manifest.AliasesToExport) {
                $Configuration.Information.Manifest.AliasesToExport = @()
            }
        }
        $FoundDuplicateAliases = $false
        if ($Configuration.Information.Manifest.AliasesToExport) {
            $UniqueAliases = $Configuration.Information.Manifest.AliasesToExport | Select-Object -Unique
            $DiffrenceAliases = Compare-Object -ReferenceObject $Configuration.Information.Manifest.AliasesToExport -DifferenceObject $UniqueAliases
            foreach ($Alias in $Configuration.Information.Manifest.AliasesToExport) {
                if ($Alias -in $Configuration.Information.Manifest.FunctionsToExport) {
                    Write-Text " [-] Alias $Alias is also used as function name. Fix it!" -Color Red
                    $FoundDuplicateAliases = $true
                }
            }
            foreach ($Alias in $DiffrenceAliases.InputObject) {
                Write-TextWithTime -Text " [-] Alias $Alias is used multiple times. Fix it!" -Color Red
                $FoundDuplicateAliases = $true
            }
            if ($FoundDuplicateAliases) {
                return $false
            }
        }
        if (-not [string]::IsNullOrWhiteSpace($Configuration.Information.ScriptsToProcess)) {
            if ($null -eq $IsWindows -or $IsWindows -eq $true) {
                $StartsWithEnums = "$($Configuration.Information.ScriptsToProcess)\"
            }
            else {
                $StartsWithEnums = "$($Configuration.Information.ScriptsToProcess)/"
            }
            $FilesEnums = @(
                $LinkPrivatePublicFiles | Where-Object { ($_).StartsWith($StartsWithEnums) }
            )

            if ($FilesEnums.Count -gt 0) {
                Write-TextWithTime -Text "ScriptsToProcess export $FilesEnums" -PreAppend Plus -SpacesBefore ' '
                $Configuration.Information.Manifest.ScriptsToProcess = $FilesEnums
            }
        }
    } -PreAppend Information
    $AliasesAndFunctions
}
function Start-PreparingStructure {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Configuration,
        [System.Collections.IDictionary] $DestinationPaths,
        [string] $FullProjectPath,
        [string] $FullModuleTemporaryPath,
        [string] $FullTemporaryPath
    )
    Write-TextWithTime -Text "Preparing structure" -PreAppend Information {
        if ($Configuration.Steps.BuildModule.DeleteBefore -eq $true) {
            Write-TextWithTime -Text "Deleting old module (Desktop destination) $($DestinationPaths.Desktop)" {
                $Success = Remove-Directory -Directory $($DestinationPaths.Desktop) -ErrorAction Stop
                if ($Success -eq $false) {
                    return $false
                }
            } -PreAppend Minus -SpacesBefore " " -Color Blue -ColorError Red -ColorTime Green -ColorBefore Yellow

            Write-TextWithTime -Text "Deleting old module (Core destination) $($DestinationPaths.Core)" {
                $Success = Remove-Directory -Directory $($DestinationPaths.Core)
                if ($Success -eq $false) {
                    return $false
                }
            } -PreAppend Minus -SpacesBefore " " -Color Blue -ColorError Red -ColorTime Green -ColorBefore Yellow
        }

        Set-Location -Path $FullProjectPath

        Write-TextWithTime -Text "Cleaning up temporary path $($FullModuleTemporaryPath)" {
            $Success = Remove-Directory -Directory $FullModuleTemporaryPath
            if ($Success -eq $false) {
                return $false
            }
            Add-Directory -Directory $FullModuleTemporaryPath
        } -PreAppend Minus -SpacesBefore " " -Color Blue -ColorError Red -ColorTime Green -ColorBefore Yellow
        Write-TextWithTime -Text "Cleaning up temporary path $($FullTemporaryPath)" {
            $Success = Remove-Directory -Directory $FullTemporaryPath
            if ($Success -eq $false) {
                return $false
            }
            Add-Directory -Directory $FullTemporaryPath
        } -PreAppend Minus -SpacesBefore " " -Color Blue -ColorError Red -ColorTime Green -ColorBefore Yellow
    }
}
function Start-PreparingVariables {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Configuration,
        [string] $FullProjectPath
    )
    Write-TextWithTime -Text "Preparing files and folders variables" -PreAppend Plus {
        $LinkDirectories = @()
        $LinkPrivatePublicFiles = @()

        if ($null -ne $Configuration.Information.Exclude) {
            $Exclude = $Configuration.Information.Exclude
        }
        else {
            $Exclude = '.*', 'Ignore', 'Examples', 'package.json', 'Publish', 'Docs'
        }
        if ($null -ne $Configuration.Information.IncludeRoot) {
            $IncludeFilesRoot = $Configuration.Information.IncludeRoot
        }
        else {
            $IncludeFilesRoot = '*.psm1', '*.psd1', 'License*'
        }
        if ($null -ne $Configuration.Information.IncludePS1) {
            $DirectoriesWithPS1 = $Configuration.Information.IncludePS1
        }
        else {
            $DirectoriesWithPS1 = 'Classes', 'Private', 'Public', 'Enums'
        }

        $DirectoriesWithArrays = $Configuration.Information.IncludeAsArray.Values

        if ($null -ne $Configuration.Information.IncludeClasses) {
            $DirectoriesWithClasses = $Configuration.Information.IncludeClasses
        }
        else {
            $DirectoriesWithClasses = 'Classes'
        }
        if ($null -ne $Configuration.Information.IncludeAll) {
            $DirectoriesWithAll = $Configuration.Information.IncludeAll | ForEach-Object {
                if ($null -eq $IsWindows -or $IsWindows -eq $true) {
                    if ($_.EndsWith('\')) {
                        $_
                    }
                    else {
                        "$_\"
                    }
                }
                else {
                    if ($_.EndsWith('/')) {
                        $_
                    }
                    else {
                        "$_/"
                    }
                }
            }
        }
        else {
            if ($null -eq $IsWindows -or $IsWindows -eq $true) {
                $DirectoriesWithAll = 'Images\', 'Resources\', 'Templates\', 'Bin\', 'Lib\', 'Data\'
            }
            else {
                $DirectoriesWithAll = 'Images/', 'Resources/', 'Templates/', 'Bin/', 'Lib/', 'Data/'
            }
        }

        $Path = [io.path]::Combine($FullProjectPath, '*')
        if ($PSEdition -eq 'core') {
            $Directories = @(
                $TempDirectories = Get-ChildItem -Path $FullProjectPath -Directory -Exclude $Exclude -FollowSymlink
                @(
                    $TempDirectories
                    $TempDirectories | Get-ChildItem -Directory -Recurse -FollowSymlink
                )
            )
            $Files = Get-ChildItem -Path $FullProjectPath -Exclude $Exclude -FollowSymlink | Get-ChildItem -File -Recurse -FollowSymlink
            $FilesRoot = Get-ChildItem -Path $Path -Include $IncludeFilesRoot -File -FollowSymlink
        }
        else {
            $Directories = @(
                $TempDirectories = Get-ChildItem -Path $FullProjectPath -Directory -Exclude $Exclude
                @(
                    $TempDirectories
                    $TempDirectories | Get-ChildItem -Directory -Recurse
                )
            )
            $Files = Get-ChildItem -Path $FullProjectPath -Exclude $Exclude | Get-ChildItem -File -Recurse
            $FilesRoot = Get-ChildItem -Path $Path -Include $IncludeFilesRoot -File
        }
        $LinkDirectories = @(
            foreach ($Directory in $Directories) {
                if ($null -eq $IsWindows -or $IsWindows -eq $true) {
                    $RelativeDirectoryPath = (Resolve-Path -LiteralPath $Directory.FullName -Relative).Replace('.\', '')
                    $RelativeDirectoryPath = "$RelativeDirectoryPath\"
                }
                else {
                    $RelativeDirectoryPath = (Resolve-Path -LiteralPath $Directory.FullName -Relative).Replace('./', '')
                    $RelativeDirectoryPath = "$RelativeDirectoryPath/"
                }
                $RelativeDirectoryPath
            }
        )
        $AllFiles = foreach ($File in $Files) {
            if ($null -eq $IsWindows -or $IsWindows -eq $true) {
                $RelativeFilePath = (Resolve-Path -LiteralPath $File.FullName -Relative).Replace('.\', '')
            }
            else {
                $RelativeFilePath = (Resolve-Path -LiteralPath $File.FullName -Relative).Replace('./', '')
            }
            $RelativeFilePath
        }
        $RootFiles = foreach ($File in $FilesRoot) {
            if ($null -eq $IsWindows -or $IsWindows -eq $true) {
                $RelativeFilePath = (Resolve-Path -LiteralPath $File.FullName -Relative).Replace('.\', '')
            }
            else {
                $RelativeFilePath = (Resolve-Path -LiteralPath $File.FullName -Relative).Replace('./', '')
            }
            $RelativeFilePath
        }

        $LinkFilesRoot = @(
            foreach ($File in $RootFiles | Sort-Object -Unique) {
                switch -Wildcard ($file) {
                    '*.psd1' {
                        $File
                    }
                    '*.psm1' {
                        $File
                    }
                    'License*' {
                        $File
                    }
                }
            }
        )

        $LinkPrivatePublicFiles = @(
            foreach ($file in $AllFiles | Sort-Object -Unique) {
                switch -Wildcard ($file) {
                    '*.ps1' {
                        foreach ($dir in $DirectoriesWithPS1) {
                            if ($file -like "$dir*") {
                                $file
                            }
                        }
                        foreach ($dir in $DirectoriesWithArrays) {
                            if ($file -like "$dir*") {
                                $file
                            }
                        }
                        continue
                    }
                    '*.*' {
                        foreach ($dir in $DirectoriesWithAll) {
                            if ($file -like "$dir*") {
                                $file
                            }
                        }
                        continue
                    }
                }
            }
        )
        $LinkPrivatePublicFiles = $LinkPrivatePublicFiles | Select-Object -Unique

        [ordered] @{
            LinkDirectories        = $LinkDirectories
            LinkFilesRoot          = $LinkFilesRoot
            LinkPrivatePublicFiles = $LinkPrivatePublicFiles
            DirectoriesWithClasses = $DirectoriesWithClasses
            Files                  = $Files
            DirectoriesWithPS1     = $DirectoriesWithPS1
        }
    }
}
function Start-PublishingGallery {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Configuration,
        [System.Collections.IDictionary] $ChosenNuget
    )

    if ($ChosenNuget) {
        $Repository = if ($ChosenNuget.RepositoryName) {
            $ChosenNuget.RepositoryName 
        }
        else {
            'PSGallery' 
        }
        Write-TextWithTime -Text "Publishing Module to Gallery ($Repository)" {
            if ($ChosenNuget.ApiKey) {
                $publishModuleSplat = @{
                    Name        = $Configuration.Information.ModuleName
                    Repository  = $Repository
                    NuGetApiKey = $ChosenNuget.ApiKey
                    Force       = $ChosenNuget.Force
                    Verbose     = $ChosenNuget.Verbose
                    ErrorAction = 'Stop'
                }
                Publish-Module @publishModuleSplat
            }
            else {
                return $false
            }
        } -PreAppend Plus
    }
    elseif ($Configuration.Steps.PublishModule.Enabled) {

        Write-TextWithTime -Text "Publishing Module to PowerShellGallery" {
            if ($Configuration.Options.PowerShellGallery.FromFile) {
                $ApiKey = Get-Content -Path $Configuration.Options.PowerShellGallery.ApiKey -ErrorAction Stop -Encoding UTF8
            }
            else {
                $ApiKey = $Configuration.Options.PowerShellGallery.ApiKey
            }
            $publishModuleSplat = @{
                Name        = $Configuration.Information.ModuleName
                Repository  = 'PSGallery'
                NuGetApiKey = $ApiKey
                Force       = $Configuration.Steps.PublishModule.RequireForce
                Verbose     = if ($Configuration.Steps.PublishModule.PSGalleryVerbose) {
                    $Configuration.Steps.PublishModule.PSGalleryVerbose 
                }
                else {
                    $false 
                }
                ErrorAction = 'Stop'
            }
            Publish-Module @publishModuleSplat
        } -PreAppend Plus
    }
}
function Start-PublishingGitHub {
    [cmdletBinding()]
    param(
        [System.Collections.IDictionary] $ChosenNuget,
        [System.Collections.IDictionary] $Configuration,
        [string] $ProjectName
    )
    if ($ChosenNuget) {
        [Array] $ListZips = if ($ChosenNuget.Id) {
            foreach ($Zip in $Configuration.CurrentSettings['Artefact']) {
                if ($Zip.Id -eq $ChosenNuget.Id) {
                    $ZipPath = $Zip.ZipPath
                    if ($ZipPath -and (Test-Path -LiteralPath $ZipPath)) {
                        $ZipPath
                    }
                }
            }
        }
        else {
            if ($Configuration.CurrentSettings['ArtefactDefault']) {

                $ZipPath = $Configuration.CurrentSettings['ArtefactDefault'].ZipPath
                if ($ZipPath -and (Test-Path -LiteralPath $ZipPath)) {
                    $ZipPath
                }
            }
            else {
            }
        }
        if ($ChosenNuget.ID) {
            $TextToUse = "Publishing to GitHub [ID: $($ChosenNuget.Id)] ($ZipPath)"
        }
        else {
            $TextToUse = "Publishing to GitHub ($ZipPath)"
        }
        if ($ListZips.Count -gt 0) {
            Write-TextWithTime -Text $TextToUse -PreAppend Information -ColorBefore Yellow {
                if ($ZipPath -and (Test-Path -LiteralPath $ZipPath)) {
                    if ($ChosenNuget.OverwriteTagName) {
                        $ModuleName = $Configuration.Information.Manifest.ModuleName
                        $ModuleVersion = $Configuration.Information.Manifest.ModuleVersion

                        if ($Configuration.CurrentSettings.PreRelease) {
                            $ModuleVersionWithPreRelease = "$($ModuleVersion)-$($Configuration.CurrentSettings.PreRelease)"
                            $TagModuleVersionWithPreRelease = "v$($ModuleVersionWithPreRelease)"
                        }
                        else {
                            $ModuleVersionWithPreRelease = $ModuleVersion
                            $TagModuleVersionWithPreRelease = "v$($ModuleVersion)"
                        }
                        $TagNameDefault = "v$($ModuleVersion)"

                        $TagName = $ChosenNuget.OverwriteTagName
                        $TagName = $TagName.Replace('{ModuleName}', $ModuleName)
                        $TagName = $TagName.Replace('<ModuleName>', $ModuleName)
                        $TagName = $TagName.Replace('{ModuleVersion}', $ModuleVersion)
                        $TagName = $TagName.Replace('<ModuleVersion>', $ModuleVersion)
                        $TagName = $TagName.Replace('{ModuleVersionWithPreRelease}', $ModuleVersionWithPreRelease)
                        $TagName = $TagName.Replace('<ModuleVersionWithPreRelease>', $ModuleVersionWithPreRelease)
                        $TagName = $TagName.Replace('{TagModuleVersionWithPreRelease}', $TagModuleVersionWithPreRelease)
                        $TagName = $TagName.Replace('<TagModuleVersionWithPreRelease>', $TagModuleVersionWithPreRelease)
                        $TagName = $TagName.Replace('{TagName}', $TagNameDefault)
                        $TagName = $TagName.Replace('<TagName>', $TagNameDefault)
                    }
                    else {
                        $TagName = "v$($Configuration.Information.Manifest.ModuleVersion)"
                    }

                    if ($Configuration.CurrentSettings.Prerelease) {
                        if ($ChosenNuget.DoNotMarkAsPreRelease) {
                            $IsPreRelease = $false
                        }
                        else {
                            $IsPreRelease = $true
                        }
                    }
                    else {
                        $IsPreRelease = $false
                    }

                    $sendGitHubReleaseSplat = [ordered] @{
                        GitHubUsername       = $ChosenNuget.UserName
                        GitHubRepositoryName = if ($ChosenNuget.RepositoryName) {
                            $ChosenNuget.RepositoryName 
                        }
                        else {
                            $ProjectName 
                        }
                        GitHubAccessToken    = $ChosenNuget.ApiKey
                        TagName              = $TagName
                        AssetFilePaths       = $ListZips
                        IsPreRelease         = $IsPreRelease

                        Verbose              = if ($ChosenNuget.Verbose) {
                            $ChosenNuget.Verbose 
                        }
                        else {
                            $false 
                        }
                    }

                    $StatusGithub = Send-GitHubRelease @sendGitHubReleaseSplat

                    if ($StatusGithub.ReleaseCreationSucceeded -and $statusGithub.Succeeded) {
                        $GithubColor = 'Green'
                        $GitHubText = '+'
                    }
                    else {
                        $GithubColor = 'Red'
                        $GitHubText = '-'
                    }

                    Write-Text "[$GitHubText] GitHub Release Creation Status: $($StatusGithub.ReleaseCreationSucceeded)" -Color $GithubColor
                    Write-Text "[$GitHubText] GitHub Release Succeeded: $($statusGithub.Succeeded)" -Color $GithubColor
                    Write-Text "[$GitHubText] GitHub Release Asset Upload Succeeded: $($statusGithub.AllAssetUploadsSucceeded)" -Color $GithubColor
                    Write-Text "[$GitHubText] GitHub Release URL: $($statusGitHub.ReleaseUrl)" -Color $GithubColor
                    if ($statusGithub.ErrorMessage) {
                        Write-Text "[$GitHubText] GitHub Release ErrorMessage: $($statusGithub.ErrorMessage)" -Color $GithubColor
                        return $false
                    }
                }
                else {
                    Write-Text " [e] GitHub Release Creation Status: Failed" -Color Red
                    Write-Text " [e] GitHub Release Creation Reason: $ZipPath doesn't exists. Most likely Releases option is disabled." -Color Red
                    return $false
                }
            }
        }
        else {
            Write-Text -Text "[-] Publishing to GitHub failed. No ZIPs to process." -Color Red
            return $false
        }
    }
    else {

        if (-not $Configuration.CurrentSettings.ArtefactZipPath -or -not (Test-Path -LiteralPath $Configuration.CurrentSettings.ArtefactZipPath)) {
            Write-Text -Text "[-] Publishing to GitHub failed. File $($Configuration.CurrentSettings.ArtefactZipPath) doesn't exists" -Color Red
            return $false
        }
        $TagName = "v$($Configuration.Information.Manifest.ModuleVersion)"

        $ZipPath = $Configuration.CurrentSettings.ArtefactZipPath

        if ($Configuration.Options.GitHub.FromFile) {
            $GitHubAccessToken = Get-Content -LiteralPath $Configuration.Options.GitHub.ApiKey -Encoding UTF8
        }
        else {
            $GitHubAccessToken = $Configuration.Options.GitHub.ApiKey
        }
        if ($GitHubAccessToken) {
            if ($Configuration.Options.GitHub.RepositoryName) {
                $GitHubRepositoryName = $Configuration.Options.GitHub.RepositoryName
            }
            else {
                $GitHubRepositoryName = $ProjectName
            }
            Write-TextWithTime -Text "Publishing to GitHub ($ZipPath)" -PreAppend Information -ColorBefore Yellow {
                if (Test-Path -LiteralPath $ZipPath) {
                    if ($Configuration.Steps.PublishModule.Prerelease) {
                        $IsPreRelease = $true
                    }
                    else {
                        $IsPreRelease = $false
                    }

                    $sendGitHubReleaseSplat = [ordered] @{
                        GitHubUsername       = $Configuration.Options.GitHub.UserName
                        GitHubRepositoryName = $GitHubRepositoryName
                        GitHubAccessToken    = $GitHubAccessToken
                        TagName              = $TagName
                        AssetFilePaths       = $ZipPath
                        IsPreRelease         = $IsPreRelease

                        Verbose              = if ($Configuration.Steps.PublishModule.GitHubVerbose) {
                            $Configuration.Steps.PublishModule.GitHubVerbose 
                        }
                        else {
                            $false 
                        }
                    }

                    $StatusGithub = Send-GitHubRelease @sendGitHubReleaseSplat

                    if ($StatusGithub.ReleaseCreationSucceeded -and $statusGithub.Succeeded) {
                        $GithubColor = 'Green'
                        $GitHubText = '>'
                    }
                    else {
                        $GithubColor = 'Red'
                        $GitHubText = '-'
                    }

                    Write-Text " [$GitHubText] GitHub Release Creation Status: $($StatusGithub.ReleaseCreationSucceeded)" -Color $GithubColor
                    Write-Text " [$GitHubText] GitHub Release Succeeded: $($statusGithub.Succeeded)" -Color $GithubColor
                    Write-Text " [$GitHubText] GitHub Release Asset Upload Succeeded: $($statusGithub.AllAssetUploadsSucceeded)" -Color $GithubColor
                    Write-Text " [$GitHubText] GitHub Release URL: $($statusGitHub.ReleaseUrl)" -Color $GithubColor
                    if ($statusGithub.ErrorMessage) {
                        Write-Text "[$GitHubText] GitHub Release ErrorMessage: $($statusGithub.ErrorMessage)" -Color $GithubColor
                        return $false
                    }
                }
                else {
                    Write-Text " [e] GitHub Release Creation Status: Failed" -Color Red
                    Write-Text " [e] GitHub Release Creation Reason: $ZipPath doesn't exists. Most likely Releases option is disabled." -Color Red
                    return $false
                }
            }
        }
    }
}
function Step-Version {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Module
    Parameter description
 
    .PARAMETER ExpectedVersion
    Parameter description
 
    .PARAMETER Advanced
    Parameter description
 
    .EXAMPLE
    Step-Version -Module Testimo12 -ExpectedVersion '0.1.X'
    Step-Version -ExpectedVersion '0.1.X'
    Step-Version -ExpectedVersion '0.1.5.X'
    Step-Version -ExpectedVersion '1.2.X'
    Step-Version -Module PSWriteHTML -ExpectedVersion '0.0.X'
    Step-Version -Module PSWriteHTML1 -ExpectedVersion '0.1.X'
    Step-Version -Module PSPublishModule -ExpectedVersion '0.9.X' -Advanced -LocalPSD1 "C:\Support\GitHub\PSPublishModule\PSPublishModule.psd1"
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [string] $Module,
        [Parameter(Mandatory)][string] $ExpectedVersion,
        [switch] $Advanced,
        [string] $LocalPSD1
    )
    $Version = $null
    $VersionCheck = [version]::TryParse($ExpectedVersion, [ref] $Version)
    if ($VersionCheck) {

        @{
            Version        = $ExpectedVersion
            CurrentVersion = 'Not aquired, no auto versioning.'
        }
    }
    else {
        if ($Module) {
            if (-not $LocalPSD1) {
                try {
                    $ModuleGallery = Find-Module -Name $Module -ErrorAction Stop -Verbose:$false -WarningAction SilentlyContinue
                    $CurrentVersion = [version] $ModuleGallery.Version
                }
                catch {

                    $CurrentVersion = [version] $null
                }
            }
            else {
                if (Test-Path -LiteralPath $LocalPSD1) {
                    $PSD1Data = Import-PowerShellDataFile -Path $LocalPSD1
                    if ($PSD1Data.ModuleVersion) {
                        try {
                            $CurrentVersion = [version] $PSD1Data.ModuleVersion
                        }
                        catch {
                            Write-Warning -Message "Couldn't parse version $($PSD1Data.ModuleVersion) from PSD1 file $LocalPSD1"
                            $CurrentVersion = $null
                        }
                    }
                }
                else {
                    Write-Warning -Message "Couldn't find local PSD1 file $LocalPSD1"
                    $CurrentVersion = $null
                }
            }
        }
        else {
            $CurrentVersion = $null
        }
        $Splitted = $ExpectedVersion.Split('.')
        $PreparedVersion = [ordered] @{
            Major    = $Splitted[0]
            Minor    = $Splitted[1]
            Build    = $Splitted[2]
            Revision = $Splitted[3]
        }
        [string] $StepType = foreach ($Key in $PreparedVersion.Keys) {
            if ($PreparedVersion[$Key] -eq 'X') {
                $Key
                break
            }
        }

        if ($null -eq $CurrentVersion) {
            $PreparedVersion.$StepType = 1
        }
        else {
            $PreparedVersion.$StepType = $CurrentVersion.$StepType
        }

        if ([version] (($PreparedVersion.Values | Where-Object { $null -ne $_ }) -join '.') -gt $CurrentVersion) {
            $PreparedVersion.$StepType = 0
        }

        while ([version] (($PreparedVersion.Values | Where-Object { $null -ne $_ }) -join '.') -le $CurrentVersion) {
            $PreparedVersion.$StepType = $PreparedVersion.$StepType + 1
        }

        $ProposedVersion = ([version] (($PreparedVersion.Values | Where-Object { $null -ne $_ }) -join '.')).ToString()

        $FinalVersion = $null
        $VersionCheck = [version]::TryParse($ProposedVersion, [ref] $FinalVersion)
        if ($VersionCheck) {
            if ($Advanced) {
                [ordered] @{
                    Version        = $ProposedVersion
                    CurrentVersion = $CurrentVersion
                }
            }
            else {
                $ProposedVersion
            }
        }
        else {
            throw "Couldn't properly verify version is version. Terminating."
        }
    }
}
function Test-AllFilePathsAndThrowErrorIfOneIsNotValid {
    [CmdletBinding()]
    param(
        [string[]] $filePaths
    )
    foreach ($filePath in $filePaths) {
        [bool] $fileWasNotFoundAtPath = [string]::IsNullOrEmpty($filePath) -or !(Test-Path -Path $filePath -PathType Leaf)
        if ($fileWasNotFoundAtPath) {
            throw "There is no file at the specified path, '$filePath'."
        }
    }
}
function Test-ReparsePoint {
    [CmdletBinding()]
    param (
        [string]$path
    )
    $file = Get-Item $path -Force -ea SilentlyContinue
    return [bool]($file.Attributes -band [IO.FileAttributes]::ReparsePoint)
}
function Write-PowerShellHashtable {
    <#
    .SYNOPSIS
    Takes an creates a script to recreate a hashtable
 
    .DESCRIPTION
    Allows you to take a hashtable and create a hashtable you would embed into a script.
    Handles nested hashtables and indents nested hashtables automatically.
 
    .PARAMETER InputObject
    The hashtable to turn into a script
 
    .PARAMETER AsScriptBlock
    Determines if a string or a scriptblock is returned
 
    .PARAMETER Sort
    Sorts the hashtable alphabetically
 
    .EXAMPLE
    # Corrects the presentation of a PowerShell hashtable
    @{Foo='Bar';Baz='Bing';Boo=@{Bam='Blang'}} | Write-PowerShellHashtable
 
    .NOTES
    Original idea: https://github.com/StartAutomating/Pipeworks
    Modifications by: Przemyslaw Klys
 
    #>

    [cmdletbinding()]
    [OutputType([string], [ScriptBlock])]
    param(
        [Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][PSObject] $InputObject,
        # Returns the content as a script block, rather than a string
        [Alias('ScriptBlock')][switch]$AsScriptBlock,
        # If set, items in the hashtable will be sorted alphabetically
        [Switch]$Sort
    )
    process {
        $callstack = @(foreach ($_ in (Get-PSCallStack)) {
                if ($_.Command -eq "Write-PowerShellHashtable") {
                    $_
                }
            })
        $depth = $callStack.Count
        if ($inputObject -isnot [System.Collections.IDictionary]) {

            $newInputObject = @{
                PSTypeName = @($inputobject.pstypenames)[-1]
            }
            foreach ($prop in $inputObject.psobject.properties) {
                $newInputObject[$prop.Name] = $prop.Value
            }
            $inputObject = $newInputObject
        }

        if ($inputObject -is [System.Collections.IDictionary]) {

            $scriptString = ""
            $indent = $depth * 4
            $scriptString += "@{
"


            $items = $inputObject.GetEnumerator()

            if ($Sort) {
                $items = $items | Sort-Object Key
            }

            foreach ($kv in $items) {
                $scriptString += " " * $indent

                $keyString = "$($kv.Key)"
                if ($keyString.IndexOfAny(" _.#-+:;()'!?^@#$%&".ToCharArray()) -ne -1) {
                    if ($keyString.IndexOf("'") -ne -1) {
                        $scriptString += "'$($keyString.Replace("'","''"))'="
                    }
                    else {
                        $scriptString += "'$keyString'="
                    }
                }
                elseif ($keyString) {
                    $scriptString += "$keyString="
                }

                $value = $kv.Value

                if ($value -is [string]) {
                    $value = "'" + $value.Replace("'", "''").Replace("’", "’’").Replace("‘", "‘‘") + "'"
                }
                elseif ($value -is [ScriptBlock]) {
                    $value = "{$value}"
                }
                elseif ($value -is [switch]) {
                    $value = if ($value) {
                        '$true' 
                    }
                    else {
                        '$false' 
                    }
                }
                elseif ($value -is [DateTime]) {
                    $value = if ($value) {
                        "[DateTime]'$($value.ToString("o"))'" 
                    }
                }
                elseif ($value -is [bool]) {
                    $value = if ($value) {
                        '$true' 
                    }
                    else {
                        '$false' 
                    }
                }
                elseif ($value -is [System.Collections.IList] -and $value.Count -eq 0) {
                    $value = '@()'
                }
                elseif ($value -is [System.Collections.IList] -and $value.Count -gt 0) {

                    $value = foreach ($v in $value) {
                        if ($v -is [System.Collections.IDictionary]) {
                            Write-PowerShellHashtable $v -Sort:$Sort.IsPresent
                        }
                        elseif ($v -is [Object] -and $v -isnot [string]) {
                            Write-PowerShellHashtable $v -Sort:$Sort.IsPresent
                        }
                        else {
                            ("'" + "$v".Replace("'", "''").Replace("’", "’’").Replace("‘", "‘‘") + "'")
                        }
                    }
                    $oldOfs = $ofs
                    $ofs = ",$(' ' * ($indent + 4))"
                    $value = "@($value)"
                    $ofs = $oldOfs
                }
                elseif ($value -as [System.Collections.IDictionary[]]) {
                    $value = foreach ($v in $value) {
                        Write-PowerShellHashtable $v -Sort:$Sort.IsPresent
                    }
                    $value = $value -join ","
                }
                elseif ($value -is [System.Collections.IDictionary]) {
                    $value = "$(Write-PowerShellHashtable $value -Sort:$Sort.IsPresent)"
                }
                elseif ($value -as [Double]) {
                    $value = "$value"
                }
                else {
                    $valueString = "'$value'"
                    if ($valueString[0] -eq "'" -and
                        $valueString[1] -eq "@" -and
                        $valueString[2] -eq "{") {
                        $value = Write-PowerShellHashtable -InputObject $value -Sort:$Sort.IsPresent
                    }
                    else {
                        $value = $valueString
                    }
                }
                $scriptString += "$value
"

            }
            $scriptString += " " * ($depth - 1) * 4
            $scriptString += "}"
            if ($AsScriptBlock) {
                [ScriptBlock]::Create($scriptString)
            }
            else {
                $scriptString
            }
        }
    }
}
function Write-Text {
    [CmdletBinding()]
    param(
        [string] $Text,
        [System.ConsoleColor] $Color = [System.ConsoleColor]::Cyan,
        [System.ConsoleColor] $ColorTime = [System.ConsoleColor]::Green,
        [switch] $Start,
        [switch] $End,
        [System.Diagnostics.Stopwatch] $Time
    )
    if (-not $Start -and -not $End) {
        Write-Host "$Text" -ForegroundColor $Color
    }
    if ($Start) {
        Write-Host "$Text" -NoNewline -ForegroundColor $Color
        $Time = [System.Diagnostics.Stopwatch]::StartNew()
    }
    if ($End) {
        $TimeToExecute = $Time.Elapsed.ToString()
        Write-Host " [Time: $TimeToExecute]" -ForegroundColor $ColorTime
        $Time.Stop()
    }
    else {
        if ($Time) {
            return $Time
        }
    }
}
function Write-TextWithTime {
    [CmdletBinding()]
    param(
        [ScriptBlock] $Content,
        [ValidateSet('Plus', 'Minus', 'Information', 'Addition')][string] $PreAppend,
        [string] $Text,
        [switch] $Continue,
        [System.ConsoleColor] $Color = [System.ConsoleColor]::Cyan,
        [System.ConsoleColor] $ColorTime = [System.ConsoleColor]::Green,
        [System.ConsoleColor] $ColorError = [System.ConsoleColor]::Red,
        [System.ConsoleColor] $ColorBefore,
        [string] $SpacesBefore
    )
    if ($PreAppend) {
        if ($PreAppend -eq "Information") {
            $TextBefore = "$SpacesBefore[i] "
            if (-not $ColorBefore) {
                $ColorBefore = [System.ConsoleColor]::Yellow
            }
        }
        elseif ($PreAppend -eq 'Minus') {
            $TextBefore = "$SpacesBefore[-] "
            if (-not $ColorBefore) {
                $ColorBefore = [System.ConsoleColor]::Red
            }
        }
        elseif ($PreAppend -eq 'Plus') {
            $TextBefore = "$SpacesBefore[+] "
            if (-not $ColorBefore) {
                $ColorBefore = [System.ConsoleColor]::Cyan
            }
        }
        elseif ($PreAppend -eq 'Addition') {
            $TextBefore = "$SpacesBefore[>] "
            if (-not $ColorBefore) {
                $ColorBefore = [System.ConsoleColor]::Yellow
            }
        }
        Write-Host -Object "$TextBefore" -NoNewline -ForegroundColor $ColorBefore
        Write-Host -Object "$Text" -ForegroundColor $Color
    }
    else {
        Write-Host -Object "$Text" -ForegroundColor $Color
    }
    $Time = [System.Diagnostics.Stopwatch]::StartNew()
    if ($null -ne $Content) {
        try {
            $InputData = & $Content
            if ($InputData -contains $false) {
                $ErrorMessage = "Failure in action above. Check output above."
            }
            else {
                $InputData
            }
        }
        catch {
            $ErrorMessage = $_.Exception.Message + " (File: $($_.InvocationInfo.ScriptName), Line: " + $_.InvocationInfo.ScriptLineNumber + ")"
        }
    }
    $TimeToExecute = $Time.Elapsed.ToString()
    if ($ErrorMessage) {
        Write-Host -Object "$SpacesBefore[e] $Text [Error: $ErrorMessage]" -ForegroundColor $ColorError
        if ($PreAppend) {
            Write-Host -Object "$($TextBefore)" -NoNewline -ForegroundColor $ColorError
        }
        Write-Host -Object "$Text [Time: $TimeToExecute]" -ForegroundColor $ColorError
        $Time.Stop()
        return $false
        break
    }
    else {
        if ($PreAppend) {
            Write-Host -Object "$($TextBefore)" -NoNewline -ForegroundColor $ColorBefore
        }
        Write-Host -Object "$Text [Time: $TimeToExecute]" -ForegroundColor $ColorTime
    }
    if (-not $Continue) {
        $Time.Stop()
    }
}
function Convert-CommandsToList {
    [cmdletbinding()]
    param(
        [parameter(Mandatory)][string] $ModuleName,
        [string[]] $CommandTypes
    )

    $Commands = Get-Command -Module $ModuleName
    $CommandsOnly = $Commands | Where-Object { $_.CommandType -eq 'Function' }

    $List = [ordered] @{}
    foreach ($Command in $CommandsOnly) {
        if ($Command.Name.StartsWith('Get')) {
            $CommandType = 'Get'
        }
        elseif ($Command.Name.StartsWith('Set')) {
            $CommandType = 'Set'
        }
        else {
            $CommandType = 'Other'
        }
        if ($CommandType -ne 'Other') {
            $Name = $Command.Name.Replace("Get-", '').Replace("Set-", '')
            if (-not $List[$Name]) {
                $List[$Name] = [PSCustomObject] @{

                    Get = if ($CommandType -eq 'Get') {
                        $Command.Name 
                    }
                    else {
                        '' 
                    }

                    Set = if ($CommandType -eq 'Set') {
                        $Command.Name 
                    }
                    else {
                        '' 
                    }
                }
            }
            else {
                $List[$Name].$CommandType = $Command.Name
            }
        }
    }

    $List.Values
}
function Get-MissingFunctions {
    [CmdletBinding()]
    param(
        [alias('Path')][string] $FilePath,
        [alias('ScriptBlock')][scriptblock] $Code,
        [string[]] $Functions,
        [switch] $Summary,
        [switch] $SummaryWithCommands,
        [Array] $ApprovedModules,
        [Array] $IgnoreFunctions
    )
    $ListCommands = [System.Collections.Generic.List[Object]]::new()
    if ($FilePath) {
        $CommandsUsedInCode = Get-ScriptCommands -FilePath $FilePath -CommandsOnly
    }
    elseif ($Code) {
        $CommandsUsedInCode = Get-ScriptCommands -CommandsOnly -Code $Code
    }
    else {
        return
    }
    if ($IgnoreFunctions.Count -gt 0) {
        $Result = foreach ($_ in $CommandsUsedInCode) {
            if ($IgnoreFunctions -notcontains $_) {
                $_
            }
        }
    }
    else {
        $Result = $CommandsUsedInCode
    }

    [Array] $FilteredCommands = Get-FilteredScriptCommands -Commands $Result -Functions $Functions -FilePath $FilePath -ApprovedModules $ApprovedModules
    foreach ($_ in $FilteredCommands) {
        $ListCommands.Add($_)
    }

    [Array] $FilteredCommandsName = foreach ($Name in $FilteredCommands.Name) {
        $Name
    }

    [Array] $FunctionsOutput = foreach ($_ in $ListCommands) {
        if ($_.ScriptBlock) {
            if ($ApprovedModules.Count -gt 0 -and $_.Source -in $ApprovedModules) {
                "function $($_.Name) { $($_.ScriptBlock) }"
            }
            elseif ($ApprovedModules.Count -eq 0) {
            }
        }
    }

    if ($FunctionsOutput.Count -gt 0) {
        $IgnoreAlreadyKnownCommands = ($FilteredCommandsName + $IgnoreFunctions) | Sort-Object -Unique
        $ScriptBlockMissing = [scriptblock]::Create($FunctionsOutput)
        $AnotherRun = Get-MissingFunctions -SummaryWithCommands -ApprovedModules $ApprovedModules -Code $ScriptBlockMissing -IgnoreFunctions $IgnoreAlreadyKnownCommands
    }

    if ($SummaryWithCommands) {
        if ($AnotherRun) {
            $Hash = @{ }
            $Hash.Summary = foreach ($_ in $FilteredCommands + $AnotherRun.Summary) {
                $_
            }
            $Hash.SummaryFiltered = foreach ($_ in $ListCommands + $AnotherRun.SummaryFiltered) {
                $_
            }
            $Hash.Functions = foreach ($_ in $FunctionsOutput + $AnotherRun.Functions) {
                $_
            }
        }
        else {
            $Hash = @{
                Summary         = $FilteredCommands
                SummaryFiltered = $ListCommands
                Functions       = $FunctionsOutput
            }
        }
        return $Hash
    }
    elseif ($Summary) {
        if ($AnotherRun) {
            foreach ($_ in $ListCommands + $AnotherRun.SummaryFiltered) {
                $_
            }
        }
        else {
            return $ListCommands
        }
    }
    else {
        return $FunctionsOutput
    }
}
function Initialize-PortableModule {
    [CmdletBinding()]
    param(
        [alias('ModuleName')][string] $Name,
        [string] $Path = $PSScriptRoot,
        [switch] $Download,
        [switch] $Import
    )

    if ($PSVersionTable.PSVersion.Major -gt 5) {
        $Encoding = 'UTF8BOM'
    }
    else {
        $Encoding = 'UTF8'
    }

    if (-not $Name) {
        Write-Warning "Initialize-ModulePortable - Module name not given. Terminating."
        return
    }
    if (-not $Download -and -not $Import) {
        Write-Warning "Initialize-ModulePortable - Please choose Download/Import switch. Terminating."
        return
    }
    if ($Download) {
        try {
            if (-not $Path -or -not (Test-Path -LiteralPath $Path)) {
                $null = New-Item -ItemType Directory -Path $Path -Force
            }
            Save-Module -Name $Name -LiteralPath $Path -WarningVariable WarningData -WarningAction SilentlyContinue -ErrorAction Stop
        }
        catch {
            $ErrorMessage = $_.Exception.Message
            if ($WarningData) {
                Write-Warning "Initialize-ModulePortable - $WarningData"
            }
            Write-Warning "Initialize-ModulePortable - Error $ErrorMessage"
            return
        }
    }
    if ($Download -or $Import) {
        [Array] $Modules = Get-RequiredModule -Path $Path -Name $Name | Where-Object { $null -ne $_ }
        if ($null -ne $Modules) {
            [array]::Reverse($Modules)
        }
        $CleanedModules = [System.Collections.Generic.List[string]]::new()
        foreach ($_ in $Modules) {
            if ($CleanedModules -notcontains $_) {
                $CleanedModules.Add($_)
            }
        }
        $CleanedModules.Add($Name)
        $Items = foreach ($_ in $CleanedModules) {
            Get-ChildItem -LiteralPath "$Path\$_" -Filter '*.psd1' -Recurse -ErrorAction SilentlyContinue -Depth 1
        }
        [Array] $PSD1Files = $Items.FullName
    }
    if ($Download) {
        $ListFiles = foreach ($PSD1 in $PSD1Files) {
            $PSD1.Replace("$Path", '$PSScriptRoot')
        }

        $Content = @(
            '$Modules = @('
            foreach ($_ in $ListFiles) {
                " `"$_`""
            }
            ')'
            "foreach (`$_ in `$Modules) {"
            " Import-Module `$_ -Verbose:`$false -Force"
            "}"
        )
        $Content | Set-Content -Path $Path\$Name.ps1 -Force -Encoding $Encoding
    }
    if ($Import) {
        $ListFiles = foreach ($PSD1 in $PSD1Files) {
            $PSD1
        }
        foreach ($_ in $ListFiles) {
            Import-Module $_ -Verbose:$false -Force
        }
    }
}
function Initialize-PortableScript {
    [cmdletBinding()]
    param(
        [string] $FilePath,
        [string] $OutputPath,
        [Array] $ApprovedModules
    )

    if ($PSVersionTable.PSVersion.Major -gt 5) {
        $Encoding = 'UTF8BOM'
    }
    else {
        $Encoding = 'UTF8'
    }

    $Output = Get-MissingFunctions -FilePath $FilePath -SummaryWithCommands -ApprovedModules $ApprovedModules
    $Script = Get-Content -LiteralPath $FilePath -Encoding UTF8
    $FinalScript = @(
        $Output.Functions
        $Script
    )
    $FinalScript | Set-Content -LiteralPath $OutputPath -Encoding $Encoding
    $Output
}
function Initialize-ProjectManager {
    <#
    .SYNOPSIS
    Builds VSCode Project manager config from filesystem
 
    .DESCRIPTION
    Builds VSCode Project manager config from filesystem
 
    .PARAMETER Path
    Path to where the projects are located
 
    .PARAMETER DisableSorting
    Disables sorting of the projects by last modified date
 
    .EXAMPLE
    Initialize-ProjectManager -Path "C:\Support\GitHub"
 
    .EXAMPLE
    Initialize-ProjectManager -Path "C:\Support\GitHub" -DisableSorting
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [parameter(Mandatory)][string] $Path,
        [switch] $DisableSorting
    )
    $ProjectsPath = Get-ChildItem -LiteralPath $Path -Directory

    $SortedProjects = foreach ($Project in $ProjectsPath) {
        $AllFiles = Get-ChildItem -LiteralPath $Project.FullName -Exclude ".\.git"
        $NewestFile = $AllFiles | Sort-Object -Descending -Property LastWriteTime | Select-Object -First 1

        [PSCustomObject] @{
            name          = $Project.name
            FullName      = $Project.FullName
            LastWriteTime = $NewestFile.LastWriteTime
        }
    }
    if (-not $DisableSorting) {
        $SortedProjects = $SortedProjects | Sort-Object -Descending -Property LastWriteTime
    }

    $ProjectManager = foreach ($Project in $SortedProjects) {
        [PSCustomObject] @{
            name     = $Project.name
            rootPath = $Project.FullName
            paths    = @()
            tags     = @()
            enabled  = $true
        }
    }
    $PathProjects = [io.path]::Combine($Env:APPDATA, "Code\User\globalStorage\alefragnani.project-manager"), [io.path]::Combine($Env:APPDATA, "Code - Insiders\User\globalStorage\alefragnani.project-manager")

    foreach ($Project in $PathProjects) {
        if (Test-Path -LiteralPath $Project) {
            $JsonPath = [io.path]::Combine($Project, 'projects.json')
            if (Test-Path -LiteralPath $JsonPath) {
                Get-Content -LiteralPath $JsonPath -Encoding UTF8 | Set-Content -LiteralPath "$JsonPath.backup"
            }
            $ProjectManager | ConvertTo-Json | Set-Content -LiteralPath $JsonPath
        }
    }
}
function Invoke-ModuleBuild {
    <#
    .SYNOPSIS
    Command to create new module or update existing one.
    It will create new module structure and everything around it, or update existing one.
 
    .DESCRIPTION
    Command to create new module or update existing one.
    It will create new module structure and everything around it, or update existing one.
 
    .PARAMETER Settings
    Provide settings for the module in form of scriptblock.
    It's using DSL to define settings for the module.
 
    .PARAMETER Path
    Path to the folder where new project will be created, or existing project will be updated.
    If not provided it will be created in one up folder from the location of build script.
 
    .PARAMETER ModuleName
    Provide name of the module. It's required parameter.
 
    .PARAMETER FunctionsToExportFolder
    Public functions folder name. Default is 'Public'.
    It will be used as part of PSD1 and PSM1 to export only functions from this folder.
 
    .PARAMETER AliasesToExportFolder
    Public aliases folder name. Default is 'Public'.
    It will be used as part of PSD1 and PSM1 to export only aliases from this folder.
 
    .PARAMETER Configuration
    Provides a way to configure module using hashtable.
    It's the old way of configuring module, that requires knowledge of inner workings of the module to name proper key/value pairs
    It's required for compatibility with older versions of the module.
 
    .PARAMETER ExcludeFromPackage
    Exclude files from Artefacts. Default is '.*, 'Ignore', 'Examples', 'package.json', 'Publish', 'Docs'.
 
    .PARAMETER IncludeRoot
    Include files in the Artefacts from root of the project. Default is '*.psm1', '*.psd1', 'License*' files.
    Other files will be ignored.
 
    .PARAMETER IncludePS1
    Include *.ps1 files in the Artefacts from given folders. Default are 'Private', 'Public', 'Enums', 'Classes' folders.
    If the folder doesn't exists it will be ignored.
 
    .PARAMETER IncludeAll
    Include all files in the Artefacts from given folders. Default are 'Images', 'Resources', 'Templates', 'Bin', 'Lib', 'Data' folders.
 
    .PARAMETER IncludeCustomCode
    Parameter description
 
    .PARAMETER IncludeToArray
    Parameter description
 
    .PARAMETER LibrariesCore
    Parameter description
 
    .PARAMETER LibrariesDefault
    Parameter description
 
    .PARAMETER LibrariesStandard
    Parameter description
 
    .PARAMETER ExitCode
    Exit code to be returned to the caller. If not provided, it will not exit the script, but finish gracefully.
    Exit code 0 means success, 1 means failure.
 
    .EXAMPLE
    An example
 
    .NOTES
    General notes
    #>

    [alias('New-PrepareModule', 'Build-Module', 'Invoke-ModuleBuilder')]
    [CmdletBinding(DefaultParameterSetName = 'Modern')]
    param (
        [Parameter(Position = 0, ParameterSetName = 'Modern')][scriptblock] $Settings,
        [parameter(ParameterSetName = 'Modern')][string] $Path,
        [parameter(Mandatory, ParameterSetName = 'Modern')][alias('ProjectName')][string] $ModuleName,
        [parameter(ParameterSetName = 'Modern')][string] $FunctionsToExportFolder = 'Public',
        [parameter(ParameterSetName = 'Modern')][string] $AliasesToExportFolder = 'Public',
        [Parameter(Mandatory, ParameterSetName = 'Configuration')][System.Collections.IDictionary] $Configuration = [ordered] @{},
        [parameter(ParameterSetName = 'Modern')][string[]] $ExcludeFromPackage = @('.*', 'Ignore', 'Examples', 'package.json', 'Publish', 'Docs'),
        [parameter(ParameterSetName = 'Modern')][string[]] $IncludeRoot = @('*.psm1', '*.psd1', 'License*'),
        [parameter(ParameterSetName = 'Modern')][string[]] $IncludePS1 = @('Private', 'Public', 'Enums', 'Classes'),
        [parameter(ParameterSetName = 'Modern')][string[]] $IncludeAll = @('Images', 'Resources', 'Templates', 'Bin', 'Lib', 'Data'),
        [parameter(ParameterSetName = 'Modern')][scriptblock] $IncludeCustomCode,
        [parameter(ParameterSetName = 'Modern')][System.Collections.IDictionary] $IncludeToArray,
        [parameter(ParameterSetName = 'Modern')][string] $LibrariesCore = [io.path]::Combine("Lib", "Core"),
        [parameter(ParameterSetName = 'Modern')][string] $LibrariesDefault = [io.path]::Combine("Lib", "Default"),
        [parameter(ParameterSetName = 'Modern')][string] $LibrariesStandard = [io.path]::Combine("Lib", "Standard"),
        [parameter(ParameterSetName = 'Configuration')]
        [parameter(ParameterSetName = 'Modern')]
        [switch] $ExitCode
    )
    if ($PsCmdlet.ParameterSetName -eq 'Configuration') {
        $ModuleName = $Configuration.Information.ModuleName
    }

    if ($Path) {

        $FullProjectPath = [io.path]::Combine($Path, $ModuleName)
    }
    else {

        $ProjectPathToUse = [io.path]::Combine($MyInvocation.PSScriptRoot, "..")
        $FullProjectPath = Get-Item -LiteralPath $ProjectPathToUse
    }

    $GlobalTime = [System.Diagnostics.Stopwatch]::StartNew()

    if ($Path -and $ModuleName) {
        $FullProjectPath = [io.path]::Combine($Path, $ModuleName)
        if (-not (Test-Path -Path $Path)) {
            Write-Text -Text "[-] Path $Path doesn't exists. Please create it, before continuing." -Color Red
            if ($ExitCode) {
                Exit 1
            }
            else {
                return
            }
        }
        else {
            $CopiedBuildModule = $false
            $CopiedPSD1 = $false
            if (Test-Path -Path $FullProjectPath) {
                Write-Text -Text "[i] Module $ModuleName ($FullProjectPath) already exists. Skipping inital steps" -Color DarkGray
            }
            else {
                Write-Text -Text "[i] Preparing module structure for $ModuleName in $Path" -Color DarkGray
                $Folders = 'Private', 'Public', 'Examples', 'Ignore', 'Build'
                Add-Directory -Directory $FullProjectPath
                foreach ($folder in $Folders) {
                    $SubFolder = [io.path]::Combine($FullProjectPath, $Folder)
                    Add-Directory -Directory $SubFolder
                }

                $PathToData = [io.path]::Combine($PSScriptRoot, "..", "Data")
                if (-not (Test-Path -LiteralPath $PathToData)) {
                    $PathToData = [io.path]::Combine($PSScriptRoot, "Data")
                }
                $FilesToCopy = [ordered] @{
                    '.gitignore'       = @{
                        Source      = [io.path]::Combine($PathToData, "Example-Gitignore.txt")
                        Destination = [io.path]::Combine($FullProjectPath, ".gitignore")
                    }
                    'CHANGELOG.MD'     = @{
                        Source      = [io.path]::Combine($PathToData, "Example-CHANGELOG.MD")
                        Destination = [io.path]::Combine($FullProjectPath, "CHANGELOG.MD")
                    }
                    'README.MD'        = @{
                        Source      = [io.path]::Combine($PathToData, "Example-README.MD")
                        Destination = [io.path]::Combine($FullProjectPath, "README.MD")
                    }
                    'License'          = @{
                        Source      = [io.path]::Combine($PathToData, "Example-LicenseMIT.txt")
                        Destination = [io.path]::Combine($FullProjectPath, "License")
                    }
                    'Build-Module.ps1' = @{
                        Source      = [io.path]::Combine($PathToData, "Example-ModuleBuilder.txt")
                        Destination = [io.path]::Combine($FullProjectPath, "Build", "Build-Module.ps1")
                    }
                    "$ModuleName.psm1" = @{
                        Source      = [io.path]::Combine($PathToData, "Example-ModulePSM1.txt")
                        Destination = [io.path]::Combine($FullProjectPath, "$ModuleName.psm1")
                    }
                    "$ModuleName.psd1" = @{
                        Source      = [io.path]::Combine($PathToData, "Example-ModulePSD1.txt")
                        Destination = [io.path]::Combine($FullProjectPath, "$ModuleName.psd1")
                    }
                }
                foreach ($File in $FilesToCopy.Keys) {
                    $ValueToProcess = $FilesToCopy[$File]
                    $SourceFilePath = $ValueToProcess.Source
                    $DestinationFilePath = $ValueToProcess.Destination
                    if (-not (Test-Path -LiteralPath $DestinationFilePath)) {
                        Write-Text -Text " [+] Copying '$($File)' file ($SourceFilePath)" -Color DarkGray
                        Copy-Item -Path $SourceFilePath -Destination $DestinationFilePath -ErrorAction Stop
                        if ($File -eq 'Build-Module.ps1') {
                            $CopiedBuildModule = $True
                        }
                        elseif ($File -eq "$ModuleName.psd1") {
                            $CopiedPSD1 = $True
                        }
                    }
                }

                $Guid = (New-Guid).Guid
                if ($CopiedBuildModule) {
                    $FilePath = [io.path]::Combine($FullProjectPath, "Build", "Build-Module.ps1")
                    $Success = Register-DataForInitialModule -FilePath $FilePath -ModuleName $ModuleName -Guid $Guid
                    if ($Success -eq $false) {
                        if ($ExitCode) {
                            Exit 1 
                        }
                        else {
                            return 
                        }
                    }
                }
                if ($CopiedPSD1) {
                    $FilePath = [io.path]::Combine($FullProjectPath, "$ModuleName.psd1")
                    $Success = Register-DataForInitialModule -FilePath $FilePath -ModuleName $ModuleName -Guid $Guid
                    if ($Success -eq $false) {
                        if ($ExitCode) {
                            Exit 1 
                        }
                        else {
                            return 
                        }
                    }
                }
                Write-Text -Text "[i] Preparing module structure for $ModuleName in $Path. Completed." -Color DarkGray
            }
        }
    }

    $newPrepareStructureSplat = [ordered] @{
        Configuration           = $Configuration
        Settings                = $Settings
        PathToProject           = $FullProjectPath
        ModuleName              = $ModuleName
        FunctionsToExportFolder = $FunctionsToExportFolder
        AliasesToExportFolder   = $AliasesToExportFolder
        ExcludeFromPackage      = $ExcludeFromPackage
        IncludeRoot             = $IncludeRoot
        IncludePS1              = $IncludePS1
        IncludeAll              = $IncludeAll
        IncludeCustomCode       = $IncludeCustomCode
        IncludeToArray          = $IncludeToArray
        LibrariesCore           = $LibrariesCore
        LibrariesDefault        = $LibrariesDefault
        LibrariesStandard       = $LibrariesStandard
    }

    $ModuleOutput = New-PrepareStructure @newPrepareStructureSplat

    $Execute = "$($GlobalTime.Elapsed.Days) days, $($GlobalTime.Elapsed.Hours) hours, $($GlobalTime.Elapsed.Minutes) minutes, $($GlobalTime.Elapsed.Seconds) seconds, $($GlobalTime.Elapsed.Milliseconds) milliseconds"
    if ($ModuleOutput -notcontains $false) {
        Write-Host "[i] Module Build Completed " -NoNewline -ForegroundColor Green
        Write-Host "[Time Total: $Execute]" -ForegroundColor Green
        if ($ExitCode) {
            Exit 0
        }
    }
    else {
        Write-Host "[i] Module Build Failed " -NoNewline -ForegroundColor Red
        Write-Host "[Time Total: $Execute]" -ForegroundColor Red
        if ($ExitCode) {
            Exit 1
        }
    }
}
function New-ConfigurationArtefact {
    <#
    .SYNOPSIS
    Tells the module to create artefact of specified type
 
    .DESCRIPTION
    Tells the module to create artefact of specified type
    There can be multiple artefacts created (even of same type)
    At least one packed artefact is required for publishing to GitHub
 
    .PARAMETER PreScriptMerge
    ScriptBlock that will be added in the beggining of the script. It's only applicable to type of Script, PackedScript.
    If useed with PreScriptMergePath, this will be ignored.
 
    .PARAMETER PostScriptMerge
    ScriptBlock that will be added in the end of the script. It's only applicable to type of Script, PackedScript.
    If useed with PostScriptMergePath, this will be ignored.
 
    .PARAMETER PreScriptMergePath
    Path to file that will be added in the beggining of the script. It's only applicable to type of Script, PackedScript.
 
    .PARAMETER PostScriptMergePath
    Path to file that will be added in the end of the script. It's only applicable to type of Script, PackedScript.
 
    .PARAMETER Type
    There are 4 types of artefacts:
    - Unpacked - unpacked module (useful for testing)
    - Packed - packed module (as zip) - usually used for publishing to GitHub or copying somewhere
    - Script - script that is module in form of PS1 without PSD1 - only applicable to very simple modules
    - PackedScript - packed module (as zip) that is script that is module in form of PS1 without PSD1 - only applicable to very simple modules
 
    .PARAMETER ID
    Optional ID of the artefact. To be used by New-ConfigurationPublish cmdlet
    If not specified, the first packed artefact will be used for publishing to GitHub
 
    .PARAMETER Enable
    Enable artefact creation. By default artefact creation is disabled.
 
    .PARAMETER IncludeTagName
    Include tag name in artefact name. By default tag name is not included.
    Alternatively you can provide ArtefactName parameter to specify your own artefact name (with or without TagName)
 
    .PARAMETER Path
    Path where artefact will be created.
    Please choose a separate directory for each artefact type, as logic may be interfering one another.
 
    You can use following variables that will be replaced with actual values:
    - <ModuleName> / {ModuleName} - the name of the module i.e PSPublishModule
    - <ModuleVersion> / {ModuleVersion} - the version of the module i.e 1.0.0
    - <ModuleVersionWithPreRelease> / {ModuleVersionWithPreRelease} - the version of the module with pre-release tag i.e 1.0.0-Preview1
    - <TagModuleVersionWithPreRelease> / {TagModuleVersionWithPreRelease} - the version of the module with pre-release tag i.e v1.0.0-Preview1
    - <TagName> / {TagName} - the name of the tag - i.e. v1.0.0
 
    .PARAMETER AddRequiredModules
    Add required modules to artefact by copying them over. By default required modules are not added.
 
    .PARAMETER ModulesPath
    Path where main module or required module (if not specified otherwise in RequiredModulesPath) will be copied to.
    By default it will be put in the Path folder if not specified
    You can use following variables that will be replaced with actual values:
    - <ModuleName> / {ModuleName} - the name of the module i.e PSPublishModule
    - <ModuleVersion> / {ModuleVersion} - the version of the module i.e 1.0.0
    - <ModuleVersionWithPreRelease> / {ModuleVersionWithPreRelease} - the version of the module with pre-release tag i.e 1.0.0-Preview1
    - <TagModuleVersionWithPreRelease> / {TagModuleVersionWithPreRelease} - the version of the module with pre-release tag i.e v1.0.0-Preview1
    - <TagName> / {TagName} - the name of the tag - i.e. v1.0.0
 
    .PARAMETER RequiredModulesPath
    Path where required modules will be copied to. By default it will be put in the Path folder if not specified.
    If ModulesPath is specified, but RequiredModulesPath is not specified it will be put into ModulesPath folder.
    You can use following variables that will be replaced with actual values:
    - <ModuleName> / {ModuleName} - the name of the module i.e PSPublishModule
    - <ModuleVersion> / {ModuleVersion} - the version of the module i.e 1.0.0
    - <ModuleVersionWithPreRelease> / {ModuleVersionWithPreRelease} - the version of the module with pre-release tag i.e 1.0.0-Preview1
    - <TagModuleVersionWithPreRelease> / {TagModuleVersionWithPreRelease} - the version of the module with pre-release tag i.e v1.0.0-Preview1
    - <TagName> / {TagName} - the name of the tag - i.e. v1.0.0
 
    .PARAMETER CopyDirectories
    Provide Hashtable of directories to copy to artefact. Key is source directory, value is destination directory.
 
    .PARAMETER CopyFiles
    Provide Hashtable of files to copy to artefact. Key is source file, value is destination file.
 
    .PARAMETER CopyDirectoriesRelative
    Define if destination directories should be relative to artefact root. By default they are not.
 
    .PARAMETER CopyFilesRelative
    Define if destination files should be relative to artefact root. By default they are not.
 
    .PARAMETER ArtefactName
    The name of the artefact. If not specified, the default name will be used.
    You can use following variables that will be replaced with actual values:
    - <ModuleName> / {ModuleName} - the name of the module i.e PSPublishModule
    - <ModuleVersion> / {ModuleVersion} - the version of the module i.e 1.0.0
    - <ModuleVersionWithPreRelease> / {ModuleVersionWithPreRelease} - the version of the module with pre-release tag i.e 1.0.0-Preview1
    - <TagModuleVersionWithPreRelease> / {TagModuleVersionWithPreRelease} - the version of the module with pre-release tag i.e v1.0.0-Preview1
    - <TagName> / {TagName} - the name of the tag - i.e. v1.0.0
 
    .PARAMETER ScriptName
    The name of the script. If not specified, the default name will be used.
    Only applicable to Script and ScriptPacked artefacts.
    You can use following variables that will be replaced with actual values:
    - <ModuleName> / {ModuleName} - the name of the module i.e PSPublishModule
    - <ModuleVersion> / {ModuleVersion} - the version of the module i.e 1.0.0
    - <ModuleVersionWithPreRelease> / {ModuleVersionWithPreRelease} - the version of the module with pre-release tag i.e 1.0.0-Preview1
    - <TagModuleVersionWithPreRelease> / {TagModuleVersionWithPreRelease} - the version of the module with pre-release tag i.e v1.0.0-Preview1
    - <TagName> / {TagName} - the name of the tag - i.e. v1.0.0
 
    .PARAMETER DoNotClear
    Do not clear artefact directory before creating artefact. By default artefact directory is cleared.
 
    .EXAMPLE
    New-ConfigurationArtefact -Type Unpacked -Enable -Path "$PSScriptRoot\Artefacts\Unpacked" -RequiredModulesPath "$PSScriptRoot\Artefacts\Unpacked\Modules"
 
    .EXAMPLE
    # standard artefact, packed with tag name without any additional modules or required modules
    New-ConfigurationArtefact -Type Packed -Enable -Path "$PSScriptRoot\Artefacts\Packed" -IncludeTagName
 
    .EXAMPLE
    # Create artefact in form of a script. This is useful for very simple modules that should be just single PS1 file
    New-ConfigurationArtefact -Type Script -Enable -Path "$PSScriptRoot\Artefacts\Script" -IncludeTagName
 
    .EXAMPLE
    # Create artefact in form of a script. This is useful for very simple modules that should be just single PS1 file
    # But additionally pack it into zip fileĄŚż$%#
    New-ConfigurationArtefact -Type ScriptPacked -Enable -Path "$PSScriptRoot\Artefacts\ScriptPacked" -ArtefactName "Script-<ModuleName>-$((Get-Date).ToString('yyyy-MM-dd')).zip"
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [Parameter(Position = 0)][ScriptBlock] $PostScriptMerge,
        [Parameter(Position = 1)][ScriptBlock] $PreScriptMerge,
        [Parameter(Mandatory)][ValidateSet('Unpacked', 'Packed', 'Script', 'ScriptPacked')][string] $Type,
        [switch] $Enable,
        [switch] $IncludeTagName,
        [string] $Path,
        [alias('RequiredModules')][switch] $AddRequiredModules,
        [string] $ModulesPath,
        [string] $RequiredModulesPath,
        [System.Collections.IDictionary] $CopyDirectories,
        [System.Collections.IDictionary] $CopyFiles,
        [switch] $CopyDirectoriesRelative,
        [switch] $CopyFilesRelative,
        [switch] $DoNotClear,
        [string] $ArtefactName,
        [alias('FileName')][string] $ScriptName,
        [string] $ID,
        [string] $PostScriptMergePath,
        [string] $PreScriptMergePath
    )
    $Artefact = [ordered ] @{
        Type          = $Type 
        Configuration = [ordered] @{
            Type            = $Type
            RequiredModules = [ordered] @{}
        }
    }

    if ($PSVersionTable.PSVersion.Major -gt 5) {
        $Encoding = 'UTF8BOM'
    }
    else {
        $Encoding = 'UTF8'
    }

    if ($PSBoundParameters.ContainsKey('Enable')) {
        $Artefact['Configuration']['Enabled'] = $Enable
    }
    if ($PSBoundParameters.ContainsKey('IncludeTagName')) {
        $Artefact['Configuration']['IncludeTagName'] = $IncludeTagName
    }
    if ($PSBoundParameters.ContainsKey('Path')) {
        if ($null -eq $IsWindows -or $IsWindows -eq $true) {
            $Artefact['Configuration']['Path'] = $Path.Replace('/', '\')
        }
        else {
            $Artefact['Configuration']['Path'] = $Path.Replace('\', '/')
        }
    }
    if ($PSBoundParameters.ContainsKey('RequiredModulesPath')) {
        if ($null -eq $IsWindows -or $IsWindows -eq $true) {
            $Artefact['Configuration']['RequiredModules']['Path'] = $RequiredModulesPath.Replace('/', '\')
        }
        else {
            $Artefact['Configuration']['RequiredModules']['Path'] = $RequiredModulesPath.Replace('\', '/')
        }
    }
    if ($PSBoundParameters.ContainsKey('AddRequiredModules')) {
        $Artefact['Configuration']['RequiredModules']['Enabled'] = $true
    }
    if ($PSBoundParameters.ContainsKey('ModulesPath')) {
        if ($null -eq $IsWindows -or $IsWindows -eq $true) {
            $Artefact['Configuration']['RequiredModules']['ModulesPath'] = $ModulesPath.Replace('/', '\')
        }
        else {
            $Artefact['Configuration']['RequiredModules']['ModulesPath'] = $ModulesPath.Replace('\', '/')
        }
    }
    if ($PSBoundParameters.ContainsKey('CopyDirectories')) {
        foreach ($Directory in [string[]] $CopyDirectories.Keys) {
            if ($null -eq $IsWindows -or $IsWindows -eq $true) {
                $CopyDirectories[$Directory] = $CopyDirectories[$Directory].Replace('/', '\')
            }
            else {
                $CopyDirectories[$Directory] = $CopyDirectories[$Directory].Replace('\', '/')
            }
        }
        $Artefact['Configuration']['DirectoryOutput'] = $CopyDirectories
    }
    if ($PSBoundParameters.ContainsKey('CopyDirectoriesRelative')) {
        $Artefact['Configuration']['DestinationDirectoriesRelative'] = $CopyDirectoriesRelative.IsPresent
    }
    if ($PSBoundParameters.ContainsKey('CopyFiles')) {
        foreach ($File in [string[]] $CopyFiles.Keys) {
            if ($null -eq $IsWindows -or $IsWindows -eq $true) {
                $CopyFiles[$File] = $CopyFiles[$File].Replace('/', '\')
            }
            else {
                $CopyFiles[$File] = $CopyFiles[$File].Replace('\', '/')
            }
        }
        $Artefact['Configuration']['FilesOutput'] = $CopyFiles
    }
    if ($PSBoundParameters.ContainsKey('CopyFilesRelative')) {
        $Artefact['Configuration']['DestinationFilesRelative'] = $CopyFilesRelative.IsPresent
    }
    if ($PSBoundParameters.ContainsKey('DoNotClear')) {
        $Artefact['Configuration']['DoNotClear'] = $DoNotClear.IsPresent
    }
    if ($PSBoundParameters.ContainsKey('ArtefactName')) {
        $Artefact['Configuration']['ArtefactName'] = $ArtefactName
    }
    if ($PSBoundParameters.ContainsKey('ScriptName')) {
        $Artefact['Configuration']['ScriptName'] = $ScriptName
    }
    if ($PSBoundParameters.ContainsKey('PreScriptMerge')) {
        try {
            $Artefact['Configuration']['PreScriptMerge'] = Invoke-Formatter -ScriptDefinition $PreScriptMerge.ToString()
        }
        catch {
            Write-Text -Text "[i] Unable to format merge script provided by user. Error: $($_.Exception.Message). Using original script." -Color Red
            $Artefact['Configuration']['PreScriptMerge'] = $PreScriptMerge.ToString()
        }
    }
    if ($PSBoundParameters.ContainsKey('PostScriptMerge')) {
        try {
            $Artefact['Configuration']['PostScriptMerge'] = Invoke-Formatter -ScriptDefinition $PostScriptMerge.ToString()
        }
        catch {
            Write-Text -Text "[i] Unable to format merge script provided by user. Error: $($_.Exception.Message). Using original script." -Color Red
            $Artefact['Configuration']['PostScriptMerge'] = $PostScriptMerge.ToString()
        }
    }

    if ($PSBoundParameters.ContainsKey('PreScriptMergePath')) {
        $ScriptContent = Get-Content -Path $PreScriptMergePath -Raw -Encoding $Encoding
        if ($ScriptContent) {
            try {
                $Artefact['Configuration']['PreScriptMerge'] = Invoke-Formatter -ScriptDefinition $ScriptContent
            }
            catch {
                Write-Text -Text "[i] Unable to format merge script provided by user. Error: $($_.Exception.Message). Using original script." -Color Red
                $Artefact['Configuration']['PreScriptMerge'] = $ScriptContent.ToString()
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('PostScriptMergePath')) {
        $ScriptContent = Get-Content -Path $PostScriptMergePath -Raw -Encoding $Encoding
        if ($ScriptContent) {
            try {
                $Artefact['Configuration']['PostScriptMerge'] = Invoke-Formatter -ScriptDefinition $ScriptContent.ToString()
            }
            catch {
                Write-Text -Text "[i] Unable to format merge script provided by user. Error: $($_.Exception.Message). Using original script." -Color Red
                $Artefact['Configuration']['PostScriptMerge'] = $ScriptContent.ToString()
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('ID')) {
        $Artefact['Configuration']['ID'] = $ID
    }
    $Artefact
}
function New-ConfigurationBuild {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Enable
    Parameter description
 
    .PARAMETER DeleteTargetModuleBeforeBuild
    Parameter description
 
    .PARAMETER MergeModuleOnBuild
    Parameter description
 
    .PARAMETER MergeFunctionsFromApprovedModules
    Parameter description
 
    .PARAMETER SignModule
    Parameter description
 
    .PARAMETER DotSourceClasses
    Parameter description
 
    .PARAMETER DotSourceLibraries
    Parameter description
 
    .PARAMETER SeparateFileLibraries
    Parameter description
 
    .PARAMETER RefreshPSD1Only
    Parameter description
 
    .PARAMETER UseWildcardForFunctions
    Parameter description
 
    .PARAMETER LocalVersioning
    Parameter description
 
    .PARAMETER DoNotAttemptToFixRelativePaths
    Configures module builder to not replace $PSScriptRoot\ with $PSScriptRoot\
    This is useful if you have a module that has a lot of relative paths that are required when using Private/Public folders,
    but for merge process those are not supposed to be there as the paths change.
    By default module builder will attempt to fix it. This option disables this functionality.
    Best practice is to use $MyInvocation.MyCommand.Module.ModuleBase or similar instead of relative paths.
 
    .PARAMETER MergeLibraryDebugging
    Parameter description
 
    .PARAMETER ResolveBinaryConflicts
    Parameter description
 
    .PARAMETER ResolveBinaryConflictsName
    Parameter description
 
    .PARAMETER CertificateThumbprint
    Parameter description
 
    .PARAMETER CertificatePFXPath
    Parameter description
 
    .PARAMETER CertificatePFXBase64
    Parameter description
 
    .PARAMETER CertificatePFXPassword
    Parameter description
 
    .PARAMETER NETConfiguration
    Parameter description
 
    .PARAMETER NETFramework
    Parameter description
 
    .PARAMETER NETProjectName
    Parameter description
 
    .PARAMETER NETExcludeMainLibrary
    Exclude main library from build, this is useful if you have C# project that you want to build
    that is used mostly for generating libraries that are used in PowerShell module
    It won't include main library in the build, but it will include all other libraries
 
    .PARAMETER NETExcludeLibraryFilter
    Provide list of filters for libraries that you want to exclude from build, this is useful if you have C# project that you want to build, but don't want to include all libraries for some reason
 
    .EXAMPLE
    An example
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")]
    param(
        [switch] $Enable,
        [switch] $DeleteTargetModuleBeforeBuild,
        [switch] $MergeModuleOnBuild,
        [switch] $MergeFunctionsFromApprovedModules,
        [switch] $SignModule,
        [switch] $DotSourceClasses,
        [switch] $DotSourceLibraries,
        [switch] $SeparateFileLibraries,
        [switch] $RefreshPSD1Only,
        [switch] $UseWildcardForFunctions,
        [switch] $LocalVersioning,

        [switch] $DoNotAttemptToFixRelativePaths,

        [switch] $MergeLibraryDebugging,
        [switch] $ResolveBinaryConflicts,
        [string] $ResolveBinaryConflictsName,

        [string] $CertificateThumbprint,
        [string] $CertificatePFXPath,
        [string] $CertificatePFXBase64,
        [string] $CertificatePFXPassword,

        [ValidateSet('Release', 'Debug')][string] $NETConfiguration, # may need to allow user choice
        [string[]] $NETFramework,
        [string] $NETProjectName,
        [switch] $NETExcludeMainLibrary,
        [string[]] $NETExcludeLibraryFilter
    )

    if ($PSBoundParameters.ContainsKey('Enable')) {
        [ordered] @{
            Type        = 'Build'
            BuildModule = [ordered] @{
                Enable = $Enable.IsPresent
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('DeleteTargetModuleBeforeBuild')) {
        [ordered] @{
            Type        = 'Build'
            BuildModule = [ordered] @{
                DeleteBefore = $DeleteTargetModuleBeforeBuild.IsPresent
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('MergeModuleOnBuild')) {
        [ordered] @{
            Type        = 'Build'
            BuildModule = [ordered] @{
                Merge = $MergeModuleOnBuild.IsPresent
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('MergeFunctionsFromApprovedModules')) {
        [ordered] @{
            Type        = 'Build'
            BuildModule = [ordered] @{
                MergeMissing = $MergeFunctionsFromApprovedModules.IsPresent
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('SignModule')) {
        [ordered] @{
            Type        = 'Build'
            BuildModule = [ordered] @{
                SignMerged = $SignModule.IsPresent
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('DotSourceClasses')) {

        [ordered] @{
            Type        = 'Build'
            BuildModule = [ordered] @{
                ClassesDotSource = $DotSourceClasses.IsPresent
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('DotSourceLibraries')) {
        [ordered] @{
            Type        = 'Build'
            BuildModule = [ordered] @{
                LibraryDotSource = $DotSourceLibraries.IsPresent
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('SeparateFileLibraries')) {
        [ordered] @{
            Type        = 'Build'
            BuildModule = [ordered] @{
                LibrarySeparateFile = $SeparateFileLibraries.IsPresent
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('RefreshPSD1Only')) {
        [ordered] @{
            Type        = 'Build'
            BuildModule = [ordered] @{
                RefreshPSD1Only = $RefreshPSD1Only.IsPresent
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('UseWildcardForFunctions')) {

        [ordered] @{
            Type        = 'Build'
            BuildModule = [ordered] @{
                UseWildcardForFunctions = $UseWildcardForFunctions.IsPresent
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('LocalVersioning')) {

        [ordered] @{
            Type        = 'Build'
            BuildModule = [ordered] @{
                LocalVersion = $LocalVersioning.IsPresent
            }
        }
    }

    if ($PSBoundParameters.ContainsKey('DoNotAttemptToFixRelativePaths')) {
        [ordered] @{
            Type        = 'Build'
            BuildModule = [ordered] @{
                DoNotAttemptToFixRelativePaths = $DoNotAttemptToFixRelativePaths.IsPresent
            }
        }
    }

    if ($PSBoundParameters.ContainsKey('MergeLibraryDebugging')) {
        [ordered] @{
            Type        = 'Build'
            BuildModule = [ordered] @{
                DebugDLL = $MergeLibraryDebugging.IsPresent
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('ResolveBinaryConflictsName')) {
        [ordered] @{
            Type        = 'Build'
            BuildModule = [ordered] @{
                ResolveBinaryConflicts = @{
                    ProjectName = $ResolveBinaryConflictsName
                }
            }
        }
    }
    elseif ($PSBoundParameters.ContainsKey('ResolveBinaryConflicts')) {
        [ordered] @{
            Type        = 'Build'
            BuildModule = [ordered] @{
                ResolveBinaryConflicts = $ResolveBinaryConflicts.IsPresent
            }
        }
    }

    if ($PSBoundParameters.ContainsKey('CertificateThumbprint')) {
        [ordered] @{
            Type    = 'Options'
            Options = [ordered] @{
                Signing = [ordered] @{
                    CertificateThumbprint = $CertificateThumbprint
                }
            }
        }
    }
    elseif ($PSBoundParameters.ContainsKey('CertificatePFXPath')) {
        if ($PSBoundParameters.ContainsKey('CertificatePFXPassword')) {

            [ordered] @{
                Type    = 'Options'
                Options = [ordered] @{
                    Signing = [ordered] @{
                        CertificatePFXPath     = $CertificatePFXPath
                        CertificatePFXPassword = $CertificatePFXPassword
                    }
                }
            }
        }
        else {
            throw "CertificatePFXPassword is required when using CertificatePFXPath"
        }
    }
    elseif ($PSBoundParameters.ContainsKey('CertificatePFXBase64')) {
        if ($PSBoundParameters.ContainsKey('CertificatePFXPassword')) {

            [ordered] @{
                Type    = 'Options'
                Options = [ordered] @{
                    Signing = [ordered] @{
                        CertificatePFXBase64   = $CertificatePFXBase64
                        CertificatePFXPassword = $CertificatePFXPassword
                    }
                }
            }
        }
        else {
            throw "CertificatePFXPassword is required when using CertificatePFXBase64"
        }
    }

    if ($PSBoundParameters.ContainsKey('NETConfiguration')) {
        [ordered] @{
            Type           = 'BuildLibraries'
            BuildLibraries = [ordered] @{
                Enable        = $true
                Configuration = $NETConfiguration
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('NETFramework')) {
        [ordered] @{
            Type           = 'BuildLibraries'
            BuildLibraries = [ordered] @{
                Enable    = $true
                Framework = $NETFramework
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('NETProjectName')) {

        [ordered] @{
            Type           = 'BuildLibraries'
            BuildLibraries = [ordered] @{
                ProjectName = $NETProjectName
            }
        }
    }

    if ($PSBoundParameters.ContainsKey('NETExcludeMainLibrary')) {
        [ordered] @{
            Type           = 'BuildLibraries'
            BuildLibraries = [ordered] @{
                ExcludeMainLibrary = $NETExcludeMainLibrary.IsPresent
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('NETExcludeLibraryFilter')) {
        [ordered] @{
            Type           = 'BuildLibraries'
            BuildLibraries = [ordered] @{
                ExcludeLibraryFilter = $NETExcludeLibraryFilter
            }
        }
    }
}
function New-ConfigurationCommand {
    [CmdletBinding()]
    param(
        [string] $ModuleName,
        [string[]] $CommandName
    )

    $Configuration = [ordered] @{
        Type          = 'Command'
        Configuration = [ordered] @{
            ModuleName  = $ModuleName
            CommandName = $CommandName
        }
    }
    $Configuration
}
function New-ConfigurationDocumentation {
    <#
    .SYNOPSIS
    Enables or disables creation of documentation from the module using PlatyPS
 
    .DESCRIPTION
    Enables or disables creation of documentation from the module using PlatyPS
 
    .PARAMETER Enable
    Enables creation of documentation from the module. If not specified, the documentation will not be created.
 
    .PARAMETER StartClean
    Removes all files from the documentation folder before creating new documentation.
    Otherwise the `Update-MarkdownHelpModule` will be used to update the documentation.
 
    .PARAMETER UpdateWhenNew
    Updates the documentation right after running `New-MarkdownHelp` due to platyPS bugs.
 
    .PARAMETER Path
    Path to the folder where documentation will be created.
 
    .PARAMETER PathReadme
    Path to the readme file that will be used for the documentation.
 
    .EXAMPLE
    New-ConfigurationDocumentation -Enable:$false -StartClean -UpdateWhenNew -PathReadme 'Docs\Readme.md' -Path 'Docs'
 
    .EXAMPLE
    New-ConfigurationDocumentation -Enable -PathReadme 'Docs\Readme.md' -Path 'Docs'
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [switch] $Enable,
        [switch] $StartClean,
        [switch] $UpdateWhenNew,
        [Parameter(Mandatory)][string] $Path,
        [Parameter(Mandatory)][string] $PathReadme
    )

    if ($Path -or $PathReadme) {
        $Documentation = [ordered] @{
            Path       = $Path
            PathReadme = $PathReadme
        }
        $Option = @{
            Type          = 'Documentation'
            Configuration = $Documentation
        }
        $Option
    }

    if ($Enable -or $StartClean -or $UpdateWhenNew) {
        $BuildDocumentation = @{
            Enable        = $Enable
            StartClean    = $StartClean
            UpdateWhenNew = $UpdateWhenNew
        }
        $Option = @{
            Type          = 'BuildDocumentation'
            Configuration = $BuildDocumentation
        }
        $Option
    }
}
function New-ConfigurationExecute {
    [CmdletBinding()]
    param(

    )
}
function New-ConfigurationFormat {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [validateSet(
            'OnMergePSM1', 'OnMergePSD1',
            'DefaultPSM1', 'DefaultPSD1'
            #"DefaultPublic", 'DefaultPrivate', 'DefaultOther'
        )][string[]]$ApplyTo,

        [switch] $EnableFormatting,

        [validateSet('None', 'Asc', 'Desc')][string] $Sort,

        [switch] $RemoveComments,
        [switch] $RemoveEmptyLines,
        [switch] $RemoveAllEmptyLines,
        [switch] $RemoveCommentsInParamBlock,
        [switch] $RemoveCommentsBeforeParamBlock,

        [switch] $PlaceOpenBraceEnable,
        [switch] $PlaceOpenBraceOnSameLine,
        [switch] $PlaceOpenBraceNewLineAfter,
        [switch] $PlaceOpenBraceIgnoreOneLineBlock,

        [switch] $PlaceCloseBraceEnable,
        [switch] $PlaceCloseBraceNewLineAfter,
        [switch] $PlaceCloseBraceIgnoreOneLineBlock,
        [switch] $PlaceCloseBraceNoEmptyLineBefore,

        [switch] $UseConsistentIndentationEnable,
        [ValidateSet('space', 'tab')][string] $UseConsistentIndentationKind,
        [ValidateSet('IncreaseIndentationAfterEveryPipeline', 'NoIndentation')][string] $UseConsistentIndentationPipelineIndentation,
        [int] $UseConsistentIndentationIndentationSize,

        [switch] $UseConsistentWhitespaceEnable,
        [switch] $UseConsistentWhitespaceCheckInnerBrace,
        [switch] $UseConsistentWhitespaceCheckOpenBrace,
        [switch] $UseConsistentWhitespaceCheckOpenParen,
        [switch] $UseConsistentWhitespaceCheckOperator,
        [switch] $UseConsistentWhitespaceCheckPipe,
        [switch] $UseConsistentWhitespaceCheckSeparator,

        [switch] $AlignAssignmentStatementEnable,
        [switch] $AlignAssignmentStatementCheckHashtable,

        [switch] $UseCorrectCasingEnable,

        [ValidateSet('Minimal', 'Native')][string] $PSD1Style
    )
    $SettingsCount = 0

    $Options = [ordered] @{
        Merge    = [ordered] @{
        }
        Standard = [ordered] @{
        }
    }

    foreach ($Apply in $ApplyTo) {
        $Formatting = [ordered] @{}
        if ($PSBoundParameters.ContainsKey('RemoveComments')) {
            $Formatting.RemoveComments = $RemoveComments.IsPresent
        }
        if ($PSBoundParameters.ContainsKey('RemoveEmptyLines')) {
            $Formatting.RemoveEmptyLines = $RemoveEmptyLines.IsPresent
        }
        if ($PSBoundParameters.ContainsKey('RemoveAllEmptyLines')) {
            $Formatting.RemoveAllEmptyLines = $RemoveAllEmptyLines.IsPresent
        }
        if ($PSBoundParameters.ContainsKey('RemoveCommentsInParamBlock')) {
            $Formatting.RemoveCommentsInParamBlock = $RemoveCommentsInParamBlock.IsPresent
        }
        if ($PSBoundParameters.ContainsKey('RemoveCommentsBeforeParamBlock')) {
            $Formatting.RemoveCommentsBeforeParamBlock = $RemoveCommentsBeforeParamBlock.IsPresent
        }

        $Formatting.FormatterSettings = [ordered] @{
            IncludeRules = @(
                if ($PlaceOpenBraceEnable) {
                    'PSPlaceOpenBrace' 
                }
                if ($PlaceCloseBraceEnable) {
                    'PSPlaceCloseBrace' 
                }
                if ($UseConsistentIndentationEnable) {
                    'PSUseConsistentIndentation' 
                }
                if ($UseConsistentWhitespaceEnable) {
                    'PSUseConsistentWhitespace' 
                }
                if ($AlignAssignmentStatementEnable) {
                    'PSAlignAssignmentStatement' 
                }
                if ($UseCorrectCasingEnable) {
                    'PSUseCorrectCasing' 
                }
            )
            Rules        = [ordered] @{}
        }
        if ($PlaceOpenBraceEnable) {
            $Formatting.FormatterSettings.Rules.PSPlaceOpenBrace = [ordered] @{
                Enable             = $true
                OnSameLine         = $PlaceOpenBraceOnSameLine.IsPresent
                NewLineAfter       = $PlaceOpenBraceNewLineAfter.IsPresent
                IgnoreOneLineBlock = $PlaceOpenBraceIgnoreOneLineBlock.IsPresent
            }
        }
        if ($PlaceCloseBraceEnable) {
            $Formatting.FormatterSettings.Rules.PSPlaceCloseBrace = [ordered] @{
                Enable             = $true
                NewLineAfter       = $PlaceCloseBraceNewLineAfter.IsPresent
                IgnoreOneLineBlock = $PlaceCloseBraceIgnoreOneLineBlock.IsPresent
                NoEmptyLineBefore  = $PlaceCloseBraceNoEmptyLineBefore.IsPresent
            }
        }
        if ($UseConsistentIndentationEnable) {
            $Formatting.FormatterSettings.Rules.PSUseConsistentIndentation = [ordered] @{
                Enable              = $true
                Kind                = $UseConsistentIndentationKind
                PipelineIndentation = $UseConsistentIndentationPipelineIndentation
                IndentationSize     = $UseConsistentIndentationIndentationSize
            }
        }
        if ($UseConsistentWhitespaceEnable) {
            $Formatting.FormatterSettings.Rules.PSUseConsistentWhitespace = [ordered] @{
                Enable          = $true
                CheckInnerBrace = $UseConsistentWhitespaceCheckInnerBrace.IsPresent
                CheckOpenBrace  = $UseConsistentWhitespaceCheckOpenBrace.IsPresent
                CheckOpenParen  = $UseConsistentWhitespaceCheckOpenParen.IsPresent
                CheckOperator   = $UseConsistentWhitespaceCheckOperator.IsPresent
                CheckPipe       = $UseConsistentWhitespaceCheckPipe.IsPresent
                CheckSeparator  = $UseConsistentWhitespaceCheckSeparator.IsPresent
            }
        }
        if ($AlignAssignmentStatementEnable) {
            $Formatting.FormatterSettings.Rules.PSAlignAssignmentStatement = [ordered] @{
                Enable         = $true
                CheckHashtable = $AlignAssignmentStatementCheckHashtable.IsPresent
            }
        }
        if ($UseCorrectCasingEnable) {
            $Formatting.FormatterSettings.Rules.PSUseCorrectCasing = [ordered] @{
                Enable = $true
            }
        }
        Remove-EmptyValue -Hashtable $Formatting.FormatterSettings -Recursive
        if ($Formatting.FormatterSettings.Keys.Count -eq 0) {
            $null = $Formatting.Remove('FormatterSettings')
        }

        if ($Formatting.Count -gt 0 -or $EnableFormatting) {
            $SettingsCount++
            $Formatting.Enabled = $true

            if ($Apply -eq 'OnMergePSM1') {
                $Options.Merge.FormatCodePSM1 = $Formatting
            }
            elseif ($Apply -eq 'OnMergePSD1') {
                $Options.Merge.FormatCodePSD1 = $Formatting
            }
            elseif ($Apply -eq 'DefaultPSM1') {
                $Options.Standard.FormatCodePSM1 = $Formatting
            }
            elseif ($Apply -eq 'DefaultPSD1') {
                $Options.Standard.FormatCodePSD1 = $Formatting
            }
            elseif ($Apply -eq 'DefaultPublic') {
                $Options.Standard.FormatCodePublic = $Formatting
            }
            elseif ($Apply -eq 'DefaultPrivate') {
                $Options.Standard.FormatCodePrivate = $Formatting
            }
            elseif ($Apply -eq 'DefaultOther') {
                $Options.Standard.FormatCodeOther = $Formatting
            }
            else {
                throw "Unknown ApplyTo: $Apply"
            }
        }
        if ($PSD1Style) {
            if ($Apply -eq 'OnMergePSD1') {
                $SettingsCount++
                $Options['Merge']['Style'] = [ordered] @{}
                $Options['Merge']['Style']['PSD1'] = $PSD1Style
            }
            elseif ($Apply -eq 'DefaultPSD1') {
                $SettingsCount++
                $Options['Standard']['Style'] = [ordered] @{}
                $Options['Standard']['Style']['PSD1'] = $PSD1Style
            }
        }
    }

    if ($SettingsCount -gt 0) {
        $Output = [ordered] @{
            Type    = 'Formatting'
            Options = $Options
        }
        $Output
    }
}

function New-ConfigurationImportModule {
    [CmdletBinding()]
    param(
        [switch] $ImportSelf,
        [switch] $ImportRequiredModules
    )

    $Output = [ordered] @{
        Type          = 'ImportModules'
        ImportModules = [ordered] @{}
    }
    if ($PSBoundParameters.Keys.Contains('ImportSelf')) {
        $Output['ImportModules']['Self'] = $ImportSelf
    }
    if ($PSBoundParameters.Keys.Contains('ImportRequiredModules')) {
        $Output['ImportModules']['RequiredModules'] = $ImportRequiredModules
    }
    if ($VerbosePreference) {
        $Output['ImportModules']['Verbose'] = $true
    }
    $Output
}
function New-ConfigurationInformation {
    [cmdletbinding()]
    param(
        [string] $FunctionsToExportFolder,
        [string] $AliasesToExportFolder,
        [string[]] $ExcludeFromPackage,
        [string[]] $IncludeRoot,
        [string[]] $IncludePS1,
        [string[]] $IncludeAll,
        [scriptblock] $IncludeCustomCode,
        [System.Collections.IDictionary] $IncludeToArray,
        [string] $LibrariesCore,
        [string] $LibrariesDefault,
        [string] $LibrariesStandard
    )

    $Configuration = [ordered] @{
        FunctionsToExportFolder = $FunctionsToExportFolder
        AliasesToExportFolder   = $AliasesToExportFolder
        ExcludeFromPackage      = $ExcludeFromPackage
        IncludeRoot             = $IncludeRoot
        IncludePS1              = $IncludePS1
        IncludeAll              = $IncludeAll
        IncludeCustomCode       = $IncludeCustomCode
        IncludeToArray          = $IncludeToArray
        LibrariesCore           = $LibrariesCore
        LibrariesDefault        = $LibrariesDefault
        LibrariesStandard       = $LibrariesStandard
    }
    Remove-EmptyValue -Hashtable $Configuration

    $Option = @{
        Type          = 'Information'
        Configuration = $Configuration
    }
    $Option
}
function New-ConfigurationManifest {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string] $ModuleVersion,
        [ValidateSet('Desktop', 'Core')][string[]] $CompatiblePSEditions = @('Desktop', 'Core'),
        [Parameter(Mandatory)][string] $GUID,
        [Parameter(Mandatory)][string] $Author,
        [string] $CompanyName,
        [string] $Copyright,
        [string] $Description,
        [string] $PowerShellVersion = '5.1',
        [string[]] $Tags,
        [string] $IconUri,
        [string] $ProjectUri,
        [string] $DotNetFrameworkVersion,
        [string] $LicenseUri,
        [alias('PrereleaseTag')][string] $Prerelease
    )

    $Manifest = [ordered] @{
        ModuleVersion          = $ModuleVersion
        CompatiblePSEditions   = @($CompatiblePSEditions)
        GUID                   = $GUID
        Author                 = $Author
        CompanyName            = $CompanyName
        Copyright              = $Copyright
        Description            = $Description
        PowerShellVersion      = $PowerShellVersion
        Tags                   = $Tags
        IconUri                = $IconUri
        ProjectUri             = $ProjectUri
        DotNetFrameworkVersion = $DotNetFrameworkVersion
        LicenseUri             = $LicenseUri
        Prerelease             = $Prerelease
    }
    Remove-EmptyValue -Hashtable $Manifest

    $Option = @{
        Type          = 'Manifest'
        Configuration = $Manifest
    }
    $Option
}
function New-ConfigurationModule {
    <#
    .SYNOPSIS
    Provides a way to configure Required Modules or External Modules that will be used in the project.
 
    .DESCRIPTION
    Provides a way to configure Required Modules or External Modules that will be used in the project.
 
    .PARAMETER Type
    Choose between RequiredModule, ExternalModule and ApprovedModule, where RequiredModule is the default.
 
    .PARAMETER Name
    Name of PowerShell module that you want your module to depend on.
 
    .PARAMETER Version
    Version of PowerShell module that you want your module to depend on.
    If you don't specify a version, any version of the module is acceptable.
    You can also use word 'Latest' to specify that you want to use the latest version of the module, and the module will be pickup up latest version available on the system.
 
    .PARAMETER RequiredVersion
    RequiredVersion of PowerShell module that you want your module to depend on.
    This forces the module to require this specific version.
    When using Version, the module will be picked up if it's equal or higher than the version specified.
    When using RequiredVersion, the module will be picked up only if it's equal to the version specified.
 
    .PARAMETER Guid
    Guid of PowerShell module that you want your module to depend on. If you don't specify a Guid, any Guid of the module is acceptable, but it is recommended to specify it.
    Alternatively you can use word 'Auto' to specify that you want to use the Guid of the module, and the module GUID
 
    .EXAMPLE
    # Add standard module dependencies (directly, but can be used with loop as well)
    New-ConfigurationModule -Type RequiredModule -Name 'platyPS' -Guid 'Auto' -Version 'Latest'
    New-ConfigurationModule -Type RequiredModule -Name 'powershellget' -Guid 'Auto' -Version 'Latest'
    New-ConfigurationModule -Type RequiredModule -Name 'PSScriptAnalyzer' -Guid 'Auto' -Version 'Latest'
 
    .EXAMPLE
    # Add external module dependencies, using loop for simplicity
    foreach ($Module in @('Microsoft.PowerShell.Utility', 'Microsoft.PowerShell.Archive', 'Microsoft.PowerShell.Management', 'Microsoft.PowerShell.Security')) {
        New-ConfigurationModule -Type ExternalModule -Name $Module
    }
 
    .EXAMPLE
    # Add approved modules, that can be used as a dependency, but only when specific function from those modules is used
    # And on that time only that function and dependant functions will be copied over
    # Keep in mind it has it's limits when "copying" functions such as it should not depend on DLLs or other external files
    New-ConfigurationModule -Type ApprovedModule -Name 'PSSharedGoods', 'PSWriteColor', 'Connectimo', 'PSUnifi', 'PSWebToolbox', 'PSMyPassword'
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [validateset('RequiredModule', 'ExternalModule', 'ApprovedModule')] $Type = 'RequiredModule',
        [Parameter(Mandatory)][string[]] $Name,
        [string] $Version,
        [string] $RequiredVersion,
        [string] $Guid
    )
    foreach ($N in $Name) {
        if ($Type -eq 'ApprovedModule') {

            $Configuration = $N
        }
        else {
            $ModuleInformation = [ordered] @{
                ModuleName      = $N
                ModuleVersion   = $Version
                RequiredVersion = $RequiredVersion
                Guid            = $Guid
            }
            if ($Version -and $RequiredVersion) {
                throw 'You cannot use both Version and RequiredVersion at the same time for the same module. Please choose one or the other (New-ConfigurationModule) '
            }
            Remove-EmptyValue -Hashtable $ModuleInformation
            if ($ModuleInformation.Count -eq 0) {
                return
            }
            elseif ($ModuleInformation.Count -eq 1 -and $ModuleInformation.Contains('ModuleName')) {
                $Configuration = $N
            }
            else {
                $Configuration = $ModuleInformation
            }
        }
        $Option = @{
            Type          = $Type
            Configuration = $Configuration
        }
        $Option
    }
}

Register-ArgumentCompleter -CommandName New-ConfigurationModule -ParameterName Version -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    'Auto', 'Latest' | Where-Object { $_ -like "*$wordToComplete*" }
}

Register-ArgumentCompleter -CommandName New-ConfigurationModule -ParameterName Guid -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    'Auto', 'Latest' | Where-Object { $_ -like "*$wordToComplete*" }
}
function New-ConfigurationModuleSkip {
    <#
    .SYNOPSIS
    Provides a way to ignore certain commands or modules during build process and continue module building on errors.
 
    .DESCRIPTION
    Provides a way to ignore certain commands or modules during build process and continue module building on errors.
    During build if a build module can't find require module or command it will fail the build process to prevent incomplete module from being created.
    This option allows to skip certain modules or commands and continue building the module.
    This is useful for commands we know are not available on all systems, or we get them different way.
 
    .PARAMETER IgnoreModuleName
    Ignore module name or names. If the module is not available on the system it will be ignored and build process will continue.
 
    .PARAMETER IgnoreFunctionName
    Ignore function name or names. If the function is not available in the module it will be ignored and build process will continue.
 
    .PARAMETER Force
    This switch will force build process to continue even if the module or command is not available (aka you know what you are doing)
 
    .EXAMPLE
    New-ConfigurationModuleSkip -IgnoreFunctionName 'Invoke-Formatter', 'Find-Module' -IgnoreModuleName 'platyPS'
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [string[]] $IgnoreModuleName,
        [string[]] $IgnoreFunctionName,
        [switch] $Force
    )

    $Configuration = [ordered] @{
        Type          = 'ModuleSkip'
        Configuration = [ordered] @{
            IgnoreModuleName   = $IgnoreModuleName
            IgnoreFunctionName = $IgnoreFunctionName
            Force              = $Force
        }
    }
    Remove-EmptyValue -Hashtable $Configuration.Configuration
    $Configuration
}
function New-ConfigurationPublish {
    <#
    .SYNOPSIS
    Provide a way to configure publishing to PowerShell Gallery or GitHub
 
    .DESCRIPTION
    Provide a way to configure publishing to PowerShell Gallery or GitHub
    You can configure publishing to both at the same time
    You can publish to multiple PowerShellGalleries at the same time as well
    You can have multiple GitHub configurations at the same time as well
 
    .PARAMETER Type
    Choose between PowerShellGallery and GitHub
 
    .PARAMETER FilePath
    API Key to be used for publishing to GitHub or PowerShell Gallery in clear text in file
 
    .PARAMETER UserName
    When used for GitHub this parameter is required to know to which repository to publish.
    This parameter is not used for PSGallery publishing
 
    .PARAMETER RepositoryName
    When used for PowerShellGallery publishing this parameter provides a way to overwrite default PowerShellGallery and publish to a different repository
    When not used, the default PSGallery will be used.
    When used for GitHub publishing this parameter provides a way to overwrite default repository name and publish to a different repository
    When not used, the default repository name will be used, that matches the module name
 
    .PARAMETER ApiKey
    API Key to be used for publishing to GitHub or PowerShell Gallery in clear text
 
    .PARAMETER Enabled
    Enable publishing to GitHub or PowerShell Gallery
 
    .PARAMETER PreReleaseTag
    Allow to publish to GitHub as pre-release. By default it will be published as release
 
    .PARAMETER OverwriteTagName
    Allow to overwrite tag name when publishing to GitHub. By default "v<ModuleVersion>" will be used i.e v1.0.0
 
    You can use following variables that will be replaced with actual values:
    - <ModuleName> / {ModuleName} - the name of the module i.e PSPublishModule
    - <ModuleVersion> / {ModuleVersion} - the version of the module i.e 1.0.0
    - <ModuleVersionWithPreRelease> / {ModuleVersionWithPreRelease} - the version of the module with pre-release tag i.e 1.0.0-Preview1
    - <TagModuleVersionWithPreRelease> / {TagModuleVersionWithPreRelease} - the version of the module with pre-release tag i.e v1.0.0-Preview1
    - <TagName> / {TagName} - the name of the tag - i.e. v1.0.0
 
    .PARAMETER DoNotMarkAsPreRelease
    Allow to publish to GitHub as release even if pre-release tag is set on the module version.
    By default it will be published as pre-release if pre-release tag is set.
    This setting prevents it.
 
    .PARAMETER Force
    Allow to publish lower version of module on PowerShell Gallery. By default it will fail if module with higher version already exists.
 
    .PARAMETER ID
    Optional ID of the artefact. If not specified, the default packed artefact will be used.
    If no packed artefact is specified, the first packed artefact will be used (if enabled)
    If no packed artefact is enabled, the publishing will fail
 
    .EXAMPLE
    New-ConfigurationPublish -Type PowerShellGallery -FilePath 'C:\Support\Important\PowerShellGalleryAPI.txt' -Enabled:$true
 
    .EXAMPLE
    New-ConfigurationPublish -Type GitHub -FilePath 'C:\Support\Important\GitHubAPI.txt' -UserName 'EvotecIT' -Enabled:$true -ID 'ToGitHub'
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ParameterSetName = 'ApiKey')]
        [Parameter(Mandatory, ParameterSetName = 'ApiFromFile')]
        [ValidateSet('PowerShellGallery', 'GitHub')][string] $Type,

        [Parameter(Mandatory, ParameterSetName = 'ApiFromFile')][string] $FilePath,

        [Parameter(Mandatory, ParameterSetName = 'ApiKey')][string] $ApiKey,

        [Parameter(ParameterSetName = 'ApiKey')]
        [Parameter(ParameterSetName = 'ApiFromFile')]
        [string] $UserName,

        [Parameter(ParameterSetName = 'ApiKey')]
        [Parameter(ParameterSetName = 'ApiFromFile')]
        [string] $RepositoryName,

        [Parameter(ParameterSetName = 'ApiKey')]
        [Parameter(ParameterSetName = 'ApiFromFile')]
        [switch] $Enabled,

        # [Parameter(ParameterSetName = 'ApiKey')]
        # [Parameter(ParameterSetName = 'ApiFromFile')]
        # [string] $PreReleaseTag,

        [Parameter(ParameterSetName = 'ApiKey')]
        [Parameter(ParameterSetName = 'ApiFromFile')]
        [string] $OverwriteTagName,

        [Parameter(ParameterSetName = 'ApiKey')]
        [Parameter(ParameterSetName = 'ApiFromFile')]
        [switch] $Force,

        [Parameter(ParameterSetName = 'ApiKey')]
        [Parameter(ParameterSetName = 'ApiFromFile')]
        [string] $ID,

        [Parameter(ParameterSetName = 'ApiKey')]
        [Parameter(ParameterSetName = 'ApiFromFile')]
        [switch] $DoNotMarkAsPreRelease
    )

    if ($FilePath) {
        $ApiKeyToUse = Get-Content -Path $FilePath -ErrorAction Stop -Encoding UTF8
    }
    else {
        $ApiKeyToUse = $ApiKey
    }

    if ($Type -eq 'PowerShellGallery') {
        $TypeToUse = 'GalleryNuget'
    }
    elseif ($Type -eq 'GitHub') {
        $TypeToUse = 'GitHubNuget'
        if (-not $UserName) {
            throw 'UserName is required for GitHub. Please fix New-ConfigurationPublish and provide UserName'
        }
    }
    else {
        return
    }

    $Settings = [ordered] @{
        Type          = $TypeToUse
        Configuration = [ordered] @{
            Type                  = $Type
            ApiKey                = $ApiKeyToUse
            ID                    = $ID
            Enabled               = $Enabled
            UserName              = $UserName
            RepositoryName        = $RepositoryName
            Force                 = $Force.IsPresent
            OverwriteTagName      = $OverwriteTagName
            DoNotMarkAsPreRelease = $DoNotMarkAsPreRelease.IsPresent
            Verbose               = $VerbosePreference
        }
    }
    Remove-EmptyValue -Hashtable $Settings -Recursive 2
    $Settings
}
function New-ConfigurationTest {
    [CmdletBinding()]
    param(
        #[Parameter(Mandatory)][ValidateSet('BeforeMerge', 'AfterMerge')][string[]] $When,
        [Parameter(Mandatory)][string] $TestsPath,
        [switch] $Enable,
        [switch] $Force
    )

    if ($Enable) {
        if ($null -eq $IsWindows -or $IsWindows -eq $true) {
            $TestsPath = $TestsPath.Replace('/', '\')
        }
        else {
            $TestsPath = $TestsPath.Replace('\', '/')
        }

        $When = 'AfterMerge'
        foreach ($W in $When) {
            $Configuration = [ordered] @{
                Type          = "Tests$W"
                Configuration = [ordered] @{
                    When      = $W
                    TestsPath = $TestsPath
                    Force     = $Force.ispresent
                }
            }
            Remove-EmptyValue -Hashtable $Configuration.Configuration
            $Configuration
        }
    }
}
function Register-Certificate {
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, ParameterSetName = 'PFX')][string] $CertificatePFX,
        [Parameter(Mandatory, ParameterSetName = 'Store')][ValidateSet('LocalMachine', 'CurrentUser')][string] $LocalStore,
        [alias('CertificateThumbprint')][Parameter(ParameterSetName = 'Store')][string] $Thumbprint,
        [Parameter(Mandatory)][string] $Path,
        [string] $TimeStampServer = 'http://timestamp.digicert.com',
        [ValidateSet('All', 'NotRoot', 'Signer')] [string] $IncludeChain = 'All',
        [string[]] $Include = @('*.ps1', '*.psd1', '*.psm1', '*.dll', '*.cat'),
        [ValidateSet('SHA1', 'SHA256', 'SHA384', 'SHA512')][string] $HashAlgorithm = 'SHA256'
    )
    if ($PSBoundParameters.Keys -contains 'LocalStore') {
        $Cert = Get-ChildItem -Path "Cert:\$LocalStore\My" -CodeSigningCert
        if ($Thumbprint) {
            $Certificate = $Cert | Where-Object { $_.Thumbprint -eq $Thumbprint }
            if (-not $Certificate) {
                Write-Warning -Message "Register-Certificate - No certificates found by that thumbprint"
                return
            }
        }
        elseif ($Cert.Count -eq 0) {
            Write-Warning -Message "Register-Certificate - No certificates found in store."
            return
        }
        elseif ($Cert.Count -eq 1) {
            $Certificate = $Cert
        }
        else {
            if ($Thumbprint) {
                $Certificate = $Cert | Where-Object { $_.Thumbprint -eq $Thumbprint }
                if (-not $Certificate) {
                    Write-Warning -Message "Register-Certificate - No certificates found by that thumbprint"
                    return
                }
            }
            else {
                $CodeError = "Get-ChildItem -Path Cert:\$LocalStore\My -CodeSigningCert"
                Write-Warning -Message "Register-Certificate - More than one certificate found in store. Provide Thumbprint for expected certificate"
                Write-Warning -Message "Register-Certificate - Use: $CodeError"
                $Cert
                return
            }
        }
    }
    elseif ($PSBoundParameters.Keys -contains 'CertificatePFX') {
        if (Test-Path -LiteralPath $CertificatePFX) {
            $Certificate = Get-PfxCertificate -FilePath $CertificatePFX
            if (-not $Certificate) {
                Write-Warning -Message "Register-Certificate - No certificates found for PFX"
                return
            }
        }
    }
    if ($Certificate -and $Path) {
        if (Test-Path -LiteralPath $Path) {
            if ($null -ne $IsWindows -and $IsWindows -eq $false) {

                $ModuleOpenAuthenticode = Get-Module -ListAvailable -Name 'OpenAuthenticode'
                if ($null -eq $ModuleOpenAuthenticode) {
                    Write-Warning -Message "Register-Certificate - OpenAuthenticode module not found. Please install it from PSGallery"
                    return
                }
                if ($IncludeChain -eq 'All') {
                    $IncludeOption = 'WholeChain'
                }
                elseif ($IncludeChain -eq 'NotRoot') {
                    $IncludeOption = 'ExcludeRoot'
                }
                elseif ($IncludeChain -eq 'Signer') {
                    $IncludeOption = 'EndCertOnly'
                }
                else {
                    $IncludeOption = 'None'
                }
                Get-ChildItem -Path $Path -Filter * -Include $Include -Recurse -ErrorAction SilentlyContinue | Where-Object {
                        ($_ | Get-OpenAuthenticodeSignature).Status -eq 'NotSigned'
                } | Set-OpenAuthenticodeSignature -Certificate $Certificate -TimeStampServer $TimeStampServer -IncludeChain $IncludeOption -HashAlgorithm $HashAlgorithm
            }
            else {

                $ModuleSigning = Get-Command -Name Set-AuthenticodeSignature
                if (-not $ModuleSigning) {
                    Write-Warning -Message "Register-Certificate - Code signing commands not found. Skipping signing."
                    return
                }
                Get-ChildItem -Path $Path -Filter * -Include $Include -Recurse -ErrorAction SilentlyContinue | Where-Object {
                ($_ | Get-AuthenticodeSignature).Status -eq 'NotSigned'
                } | Set-AuthenticodeSignature -Certificate $Certificate -TimestampServer $TimeStampServer -IncludeChain $IncludeChain -HashAlgorithm $HashAlgorithm
            }
        }
    }
}
function Remove-Comments {
    <#
    .SYNOPSIS
    Remove comments from PowerShell file
 
    .DESCRIPTION
    Remove comments from PowerShell file and optionally remove empty lines
    By default comments in param block are not removed
    By default comments before param block are not removed
 
    .PARAMETER SourceFilePath
    File path to the source file
 
    .PARAMETER Content
    Content of the file
 
    .PARAMETER DestinationFilePath
    File path to the destination file. If not provided, the content will be returned
 
    .PARAMETER RemoveEmptyLines
    Remove empty lines if more than one empty line is found
 
    .PARAMETER RemoveAllEmptyLines
    Remove all empty lines from the content
 
    .PARAMETER RemoveCommentsInParamBlock
    Remove comments in param block. By default comments in param block are not removed
 
    .PARAMETER RemoveCommentsBeforeParamBlock
    Remove comments before param block. By default comments before param block are not removed
 
    .EXAMPLE
    Remove-Comments -SourceFilePath 'C:\Support\GitHub\PSPublishModule\Examples\TestScript.ps1' -DestinationFilePath 'C:\Support\GitHub\PSPublishModule\Examples\TestScript1.ps1' -RemoveAllEmptyLines -RemoveCommentsInParamBlock -RemoveCommentsBeforeParamBlock
 
    .NOTES
    Most of the work done by Chris Dent, with improvements by Przemyslaw Klys
 
    #>

    [CmdletBinding(DefaultParameterSetName = 'FilePath')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'FilePath')]
        [alias('FilePath', 'Path', 'LiteralPath')][string] $SourceFilePath,

        [Parameter(Mandatory, ParameterSetName = 'Content')][string] $Content,

        [Parameter(ParameterSetName = 'Content')]
        [Parameter(ParameterSetName = 'FilePath')]
        [alias('Destination', 'OutputFile', 'OutputFilePath')][string] $DestinationFilePath,

        [Parameter(ParameterSetName = 'Content')]
        [Parameter(ParameterSetName = 'FilePath')]
        [switch] $RemoveAllEmptyLines,

        [Parameter(ParameterSetName = 'Content')]
        [Parameter(ParameterSetName = 'FilePath')]
        [switch] $RemoveEmptyLines,

        [Parameter(ParameterSetName = 'Content')]
        [Parameter(ParameterSetName = 'FilePath')]
        [switch] $RemoveCommentsInParamBlock,

        [Parameter(ParameterSetName = 'Content')]
        [Parameter(ParameterSetName = 'FilePath')]
        [switch] $RemoveCommentsBeforeParamBlock,

        [Parameter(ParameterSetName = 'Content')]
        [Parameter(ParameterSetName = 'FilePath')]
        [switch] $DoNotRemoveSignatureBlock
    )

    if ($PSVersionTable.PSVersion.Major -gt 5) {
        $Encoding = 'UTF8BOM'
    }
    else {
        $Encoding = 'UTF8'
    }

    if ($SourceFilePath) {
        $Fullpath = Resolve-Path -LiteralPath $SourceFilePath
        $Content = [IO.File]::ReadAllText($FullPath, [System.Text.Encoding]::UTF8)
    }

    $Tokens = $Errors = @()
    $Ast = [System.Management.Automation.Language.Parser]::ParseInput($Content, [ref]$Tokens, [ref]$Errors)

    $groupedTokens = $Tokens | Group-Object { $_.Extent.StartLineNumber }
    $DoNotRemove = $false
    $DoNotRemoveCommentParam = $false
    $CountParams = 0
    $ParamFound = $false
    $SignatureBlock = $false
    $toRemove = foreach ($line in $groupedTokens) {
        if ($Ast.Body.ParamBlock.Extent.StartLineNumber -gt $line.Name) {
            continue
        }
        $tokens = $line.Group
        for ($i = 0; $i -lt $line.Count; $i++) {
            $token = $tokens[$i]
            if ($token.Extent.StartOffset -lt $Ast.Body.ParamBlock.Extent.StartOffset) {
                continue
            }

            if ($token.Extent.Text -eq 'function') {
                if (-not $RemoveCommentsBeforeParamBlock) {
                    $DoNotRemove = $true
                }
                continue
            }
            if ($token.Extent.Text -eq 'param') {
                $ParamFound = $true
                $DoNotRemove = $false
            }
            if ($DoNotRemove) {
                continue
            }

            if ($token.Extent.Text -eq 'param') {
                if (-not $RemoveCommentsInParamBlock) {
                    $DoNotRemoveCommentParam = $true
                }
                continue
            }
            if ($ParamFound -and ($token.Extent.Text -eq '(' -or $token.Extent.Text -eq '@(')) {
                $CountParams += 1
            }
            elseif ($ParamFound -and $token.Extent.Text -eq ')') {
                $CountParams -= 1
            }
            if ($ParamFound -and $token.Extent.Text -eq ')') {
                if ($CountParams -eq 0) {
                    $DoNotRemoveCommentParam = $false
                    $ParamFound = $false
                }
            }
            if ($DoNotRemoveCommentParam) {
                continue
            }

            if ($token.Kind -ne 'Comment') {
                continue
            }

            if ($DoNotRemoveSignatureBlock) {
                if ($token.Kind -eq 'Comment' -and $token.Text -eq '# SIG # Begin signature block') {
                    $SignatureBlock = $true
                    continue
                }
                if ($SignatureBlock) {
                    if ($token.Kind -eq 'Comment' -and $token.Text -eq '# SIG # End signature block') {
                        $SignatureBlock = $false
                    }
                    continue
                }
            }
            $token
        }
    }
    $toRemove = $toRemove | Sort-Object { $_.Extent.StartOffset } -Descending
    foreach ($token in $toRemove) {
        $StartIndex = $token.Extent.StartOffset
        $HowManyChars = $token.Extent.EndOffset - $token.Extent.StartOffset
        $content = $content.Remove($StartIndex, $HowManyChars)
    }
    if ($RemoveEmptyLines) {

        $Content = $Content -replace '(?m)^\s*$', ''
        $Content = $Content -replace "(?:`r?`n|\n|\r)", "`r`n"
    }
    if ($RemoveAllEmptyLines) {

        $Content = $Content -replace '(?m)^\s*$(\r?\n)?', ''
    }
    if ($Content) {
        $Content = $Content.Trim()
    }
    if ($DestinationFilePath) {
        $Content | Set-Content -Path $DestinationFilePath -Encoding $Encoding
    }
    else {
        $Content
    }
}
function Send-GitHubRelease {
    <#
    .SYNOPSIS
    Creates a new Release for the given GitHub repository.
 
    .DESCRIPTION
    Uses the GitHub API to create a new Release for a given repository.
    Allows you to specify all of the Release properties, such as the Tag, Name, Assets, and if it's a Draft or Prerelease or not.
 
    .PARAMETER GitHubUsername
    The username that the GitHub repository exists under.
    e.g. For the repository https://github.com/deadlydog/New-GitHubRelease, the username is 'deadlydog'.
 
    .PARAMETER GitHubRepositoryName
    The name of the repository to create the Release for.
    e.g. For the repository https://github.com/deadlydog/New-GitHubRelease, the repository name is 'New-GitHubRelease'.
 
    .PARAMETER GitHubAccessToken
    The Access Token to use as credentials for GitHub.
    Access tokens can be generated at https://github.com/settings/tokens.
    The access token will need to have the repo/public_repo permission on it for it to be allowed to create a new Release.
 
    .PARAMETER TagName
    The name of the tag to create at the Commitish.
 
    .PARAMETER ReleaseName
    The name to use for the new release.
    If blank, the TagName will be used.
 
    .PARAMETER ReleaseNotes
    The text describing the contents of the release.
 
    .PARAMETER AssetFilePaths
    The full paths of the files to include in the release.
 
    .PARAMETER Commitish
    Specifies the commitish value that determines where the Git tag is created from.
    Can be any branch or commit SHA. Unused if the Git tag already exists.
    Default: the repository's default branch (usually master).
 
    .PARAMETER IsDraft
    True to create a draft (unpublished) release, false to create a published one.
    Default: false
 
    .PARAMETER IsPreRelease
    True to identify the release as a prerelease. false to identify the release as a full release.
    Default: false
 
    .OUTPUTS
    A hash table with the following properties is returned:
 
    Succeeded = $true if the Release was created successfully and all assets were uploaded to it, $false if some part of the process failed.
    ReleaseCreationSucceeded = $true if the Release was created successfully (does not include asset uploads), $false if the Release was not created.
    AllAssetUploadsSucceeded = $true if all assets were uploaded to the Release successfully, $false if one of them failed, $null if there were no assets to upload.
    ReleaseUrl = The URL of the new Release that was created.
    ErrorMessage = A message describing what went wrong in the case that Succeeded is $false.
 
    .EXAMPLE
    # Import the module dynamically from the PowerShell Gallery. Use CurrentUser scope to avoid having to run as admin.
    Import-Module -Name New-GitHubRelease -Scope CurrentUser
 
    # Specify the parameters required to create the release. Do it as a hash table for easier readability.
    $newGitHubReleaseParameters =
    @{
        GitHubUsername = 'deadlydog'
        GitHubRepositoryName = 'New-GitHubRelease'
        GitHubAccessToken = 'SomeLongHexidecimalString'
        ReleaseName = "New-GitHubRelease v1.0.0"
        TagName = "v1.0.0"
        ReleaseNotes = "This release contains the following changes: ..."
        AssetFilePaths = @('C:\MyProject\Installer.exe','C:\MyProject\Documentation.md')
        IsPreRelease = $false
        IsDraft = $true # Set to true when testing so we don't publish a real release (visible to everyone) by accident.
    }
 
    # Try to create the Release on GitHub and save the results.
    $result = New-GitHubRelease @newGitHubReleaseParameters
 
    # Provide some feedback to the user based on the results.
    if ($result.Succeeded -eq $true)
    {
        Write-Output "Release published successfully! View it at $($result.ReleaseUrl)"
    }
    elseif ($result.ReleaseCreationSucceeded -eq $false)
    {
        Write-Error "The release was not created. Error message is: $($result.ErrorMessage)"
    }
    elseif ($result.AllAssetUploadsSucceeded -eq $false)
    {
        Write-Error "The release was created, but not all of the assets were uploaded to it. View it at $($result.ReleaseUrl). Error message is: $($result.ErrorMessage)"
    }
 
    Attempt to create a new Release on GitHub, and provide feedback to the user indicating if it succeeded or not.
 
    .LINK
    Project home: https://github.com/deadlydog/New-GitHubRelease
 
    .NOTES
    Name: New-GitHubRelease
    Author: Daniel Schroeder (originally based on the script at https://github.com/majkinetor/au/blob/master/scripts/Github-CreateRelease.ps1)
    GitHub Release API Documentation: https://developer.github.com/v3/repos/releases/#create-a-release
    Version: 1.0.2
    #>

    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, HelpMessage = "The username the repository is under (e.g. deadlydog).")]
        [string] $GitHubUsername,

        [Parameter(Mandatory = $true, HelpMessage = "The repository name to create the release in (e.g. Invoke-MsBuild).")]
        [string] $GitHubRepositoryName,

        [Parameter(Mandatory = $true, HelpMessage = "The Acess Token to use as credentials for GitHub.")]
        [string] $GitHubAccessToken,

        [Parameter(Mandatory = $true, HelpMessage = "The name of the tag to create at the the Commitish.")]
        [string] $TagName,

        [Parameter(Mandatory = $false, HelpMessage = "The name of the release. If blank, the TagName will be used.")]
        [string] $ReleaseName,

        [Parameter(Mandatory = $false, HelpMessage = "Text describing the contents of the tag.")]
        [string] $ReleaseNotes,

        [Parameter(Mandatory = $false, HelpMessage = "The full paths of the files to include in the release.")]
        [string[]] $AssetFilePaths,

        [Parameter(Mandatory = $false, HelpMessage = "Specifies the commitish value that determines where the Git tag is created from. Can be any branch or commit SHA. Unused if the Git tag already exists. Default: the repository's default branch (usually master).")]
        [string] $Commitish,

        [Parameter(Mandatory = $false, HelpMessage = "True to create a draft (unpublished) release, false to create a published one. Default: false")]
        [bool] $IsDraft = $false,

        [Parameter(Mandatory = $false, HelpMessage = "True to identify the release as a prerelease. false to identify the release as a full release. Default: false")]
        [bool] $IsPreRelease = $false

        #[switch] $GenerateReleaseNotes,
        #[switch] $MakeLatest
    )

    BEGIN {

        [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 -bor [System.Net.SecurityProtocolType]::Tls11 -bor [System.Net.SecurityProtocolType]::Tls

        [string] $NewLine = [Environment]::NewLine

        if ([string]::IsNullOrEmpty($ReleaseName)) {
            $ReleaseName = $TagName
        }

        Test-AllFilePathsAndThrowErrorIfOneIsNotValid $AssetFilePaths
    }

    END { 
    }

    PROCESS {

        $result = @{ }
        $result.Succeeded = $false
        $result.ReleaseCreationSucceeded = $false
        $result.AllAssetUploadsSucceeded = $false
        $result.ReleaseUrl = $null
        $result.ErrorMessage = $null

        [bool] $thereAreNoAssetsToIncludeInTheRelease = ($null -eq $AssetFilePaths) -or ($AssetFilePaths.Count -le 0)
        if ($thereAreNoAssetsToIncludeInTheRelease) {
            $result.AllAssetUploadsSucceeded = $null
        }

        $authHeader = [ordered] @{
            Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($GitHubAccessToken + ":x-oauth-basic"))
        }

        $releaseData = [ordered] @{
            tag_name         = $TagName
            target_commitish = $Commitish
            name             = $ReleaseName
            body             = $ReleaseNotes
            draft            = $IsDraft
            prerelease       = $IsPreRelease
        }

        $createReleaseWebRequestParameters = [ordered] @{
            Uri         = "https://api.github.com/repos/$GitHubUsername/$GitHubRepositoryName/releases"
            Method      = 'POST'
            Headers     = $authHeader
            ContentType = 'application/vnd.github+json'
            Body        = (ConvertTo-Json $releaseData -Compress)
        }

        try {
            Write-Verbose "Sending web request to create the new Release..."
            $createReleaseWebRequestResults = Invoke-RestMethodAndThrowDescriptiveErrorOnFailure -requestParametersHashTable $createReleaseWebRequestParameters
        }
        catch {
            $result.ReleaseCreationSucceeded = $false
            $result.ErrorMessage = $_.Exception.Message
            return $result
        }

        $result.ReleaseCreationSucceeded = $true
        $result.ReleaseUrl = $createReleaseWebRequestResults.html_url

        if ($thereAreNoAssetsToIncludeInTheRelease) {
            $result.Succeeded = $true
            return $result
        }

        [string] $urlToUploadFilesTo = $createReleaseWebRequestResults.upload_url -replace '{.+}'

        try {
            Write-Verbose "Uploading asset files to the new release..."
            Send-FilesToGitHubRelease -filePathsToUpload $AssetFilePaths -urlToUploadFilesTo $urlToUploadFilesTo -authHeader $authHeader
        }
        catch {
            $result.AllAssetUploadsSucceeded = $false
            $result.ErrorMessage = $_.Exception.Message
            return $result
        }

        $result.AllAssetUploadsSucceeded = $true
        $result.Succeeded = $true
        return $result
    }
}
function Test-BasicModule {
    [cmdletBinding()]
    param(
        [string] $Path,
        [string] $Type
    )
    if ($Type -contains 'Encoding') {
        Get-ChildItem -LiteralPath $Path -Recurse -Filter '*.ps1' | Get-Encoding
    }
}
Function Test-ScriptFile {
    <#
    .Synopsis
 
    Test a PowerShell script for cmdlets
 
    .Description
 
    This command will analyze a PowerShell script file and display a list of detected commands such as PowerShell cmdlets and functions. Commands will be compared to what is installed locally. It is recommended you run this on a Windows 8.1 client with the latest version of RSAT installed. Unknown commands could also be internally defined functions. If in doubt view the contents of the script file in the PowerShell ISE or a script editor.
    You can test any .ps1, .psm1 or .txt file.
    .Parameter Path
 
    The path to the PowerShell script file. You can test any .ps1, .psm1 or .txt file.
 
    .Example
 
    PS C:\> test-scriptfile C:\scripts\Remove-MyVM2.ps1
 
    CommandType Name ModuleName
    ----------- ---- ----------
        Cmdlet Disable-VMEventing Hyper-V
        Cmdlet ForEach-Object Microsoft.PowerShell.Core
        Cmdlet Get-VHD Hyper-V
        Cmdlet Get-VMSnapshot Hyper-V
        Cmdlet Invoke-Command Microsoft.PowerShell.Core
        Cmdlet New-PSSession Microsoft.PowerShell.Core
        Cmdlet Out-Null Microsoft.PowerShell.Core
        Cmdlet Out-String Microsoft.PowerShell.Utility
        Cmdlet Remove-Item Microsoft.PowerShell.Management
        Cmdlet Remove-PSSession Microsoft.PowerShell.Core
        Cmdlet Remove-VM Hyper-V
        Cmdlet Remove-VMSnapshot Hyper-V
        Cmdlet Write-Debug Microsoft.PowerShell.Utility
        Cmdlet Write-Verbose Microsoft.PowerShell.Utility
        Cmdlet Write-Warning Microsoft.PowerShell.Utility
 
    .EXAMPLE
 
    PS C:\> Test-ScriptFile -Path 'C:\Users\przemyslaw.klys\Documents\WindowsPowerShell\Modules\PSWinReportingV2\PSWinReportingV2.psm1' | Sort-Object -Property Source, Name | ft -AutoSize
 
    .Notes
 
    Original script provided by Jeff Hicks at (https://www.petri.com/powershell-problem-solver-find-script-commands) and https://twitter.com/donnie_taylor/status/1160920407031058432
 
    #>


    [cmdletbinding()]
    Param(
        [Parameter(Position = 0, Mandatory = $True, HelpMessage = "Enter the path to a PowerShell script file,",
            ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
        [ValidatePattern( "\.(ps1|psm1|txt)$")]
        [ValidateScript( { Test-Path $_ })]
        [string]$Path
    )

    Begin {
        Write-Verbose "Starting $($MyInvocation.Mycommand)"
        Write-Verbose "Defining AST variables"
        New-Variable astTokens -Force
        New-Variable astErr -Force
    }

    Process {
        Write-Verbose "Parsing $path"
        $null = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$astTokens, [ref]$astErr)

        $h = $astTokens | Group-Object tokenflags -AsHashTable -AsString

        $commandData = $h.CommandName | Where-Object { $_.text -notmatch "-TargetResource$" } |
            ForEach-Object {
                Write-Verbose "Processing $($_.text)"
                Try {
                    $cmd = $_.Text
                    $resolved = $cmd | Get-Command -ErrorAction Stop
                    if ($resolved.CommandType -eq 'Alias') {
                        Write-Verbose "Resolving an alias"

                        Write-Verbose "Detected the Where-Object alias '?'"
                        if ($cmd -eq '?') {
                            Get-Command Where-Object
                        }
                        else {

                            $Resolved = $resolved.ResolvedCommandName | Get-Command

                            [PSCustomobject]@{
                                CommandType = $resolved.CommandType
                                Name        = $resolved.Name
                                ModuleName  = $resolved.ModuleName
                                Source      = $resolved.Source
                            }
                        }
                    }
                    else {

                        [PSCustomobject]@{
                            CommandType = $resolved.CommandType
                            Name        = $resolved.Name
                            ModuleName  = $resolved.ModuleName
                            Source      = $resolved.Source
                        }
                    }
                }
                Catch {
                    Write-Verbose "Command is not recognized"

                    [PSCustomobject]@{
                        CommandType = "Unknown"
                        Name        = $cmd
                        ModuleName  = "Unknown"
                        Source      = "Unknown"
                    }
                }
            }

        $CommandData
    }

    End {
        Write-Verbose -Message "Ending $($MyInvocation.Mycommand)"
    }
}
function Test-ScriptModule {
    [cmdletbinding()]
    param(
        [string] $ModuleName,
        [ValidateSet('Name', 'CommandType', 'ModuleName', 'Source')] $SortName,
        [switch] $Unique
    )
    $Module = Get-Module -ListAvailable $ModuleName
    $Path = Join-Path -Path $Module.ModuleBase -ChildPath $Module.RootModule
    $Output = Test-ScriptFile -Path $Path
    if ($Unique) {
        $Output = $Output | Sort-Object -Property 'Name' -Unique:$Unique
    }
    if ($SortName) {
        $Output | Sort-Object -Property $SortName
    }
    else {
        $Output
    }
}
if ($PSVersionTable.PSEdition -eq 'Desktop' -and (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full").Release -lt 379893) {
    Write-Warning "This module requires .NET Framework 4.5.2 or later."; return 
} 

Export-ModuleMember -Function @('Convert-CommandsToList', 'Get-MissingFunctions', 'Initialize-PortableModule', 'Initialize-PortableScript', 'Initialize-ProjectManager', 'Invoke-ModuleBuild', 'New-ConfigurationArtefact', 'New-ConfigurationBuild', 'New-ConfigurationCommand', 'New-ConfigurationDocumentation', 'New-ConfigurationExecute', 'New-ConfigurationFormat', 'New-ConfigurationImportModule', 'New-ConfigurationInformation', 'New-ConfigurationManifest', 'New-ConfigurationModule', 'New-ConfigurationModuleSkip', 'New-ConfigurationPublish', 'New-ConfigurationTest', 'Register-Certificate', 'Remove-Comments', 'Send-GitHubRelease', 'Test-BasicModule', 'Test-ScriptFile', 'Test-ScriptModule') -Alias @('Build-Module', 'Invoke-ModuleBuilder', 'New-PrepareModule')
# SIG # Begin signature block
# MIItsQYJKoZIhvcNAQcCoIItojCCLZ4CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCQRxeZa49MZI7l
# BYCGfMdFJhKGqJAuzwX/leOnF0S6f6CCJrQwggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG
# SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw
# aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK
# EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm
# dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu
# d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD
# eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1
# XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld
# QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS
# YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm
# M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT
# QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx
# fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD
# VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq
# hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4
# XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ
# aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg
# X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk
# apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL
# FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy
# 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u
# KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54
# zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8
# 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8
# aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w
# ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1
# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG
# SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS
# g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9
# /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn
# HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0
# VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f
# sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj
# gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0
# QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv
# mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T
# /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk
# 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r
# mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E
# FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n
# P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG
# CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu
# Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln
# aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v
# Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV
# HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB
# AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp
# wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl
# zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ
# cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe
# Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j
# Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh
# IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6
# OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw
# N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR
# 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2
# VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGsDCCBJigAwIBAgIQ
# CK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEV
# MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t
# MSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAw
# MDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT
# aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+k
# jmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9
# NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9
# URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegY
# E2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS
# 4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJa
# wv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+w
# c86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eR
# Gv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF2
# 3r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCK
# ZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhEC
# AwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2
# O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P
# MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcB
# AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr
# BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1
# c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAH
# BgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6
# mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/
# SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzY
# gBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9
# kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ
# 9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAew
# Q3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5Lm
# Tl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HA
# SIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xr
# y7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhR
# ILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFu
# v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGwjCCBKqgAwIBAgIQBUSv85SdCDmmv9s/
# X+VhFjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5
# NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIzMDcxNDAwMDAwMFoXDTM0MTAx
# MzIzNTk1OVowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu
# MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMzCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBAKNTRYcdg45brD5UsyPgz5/X5dLnXaEOCdwvSKOX
# ejsqnGfcYhVYwamTEafNqrJq3RApih5iY2nTWJw1cb86l+uUUI8cIOrHmjsvlmbj
# aedp/lvD1isgHMGXlLSlUIHyz8sHpjBoyoNC2vx/CSSUpIIa2mq62DvKXd4ZGIX7
# ReoNYWyd/nFexAaaPPDFLnkPG2ZS48jWPl/aQ9OE9dDH9kgtXkV1lnX+3RChG4PB
# uOZSlbVH13gpOWvgeFmX40QrStWVzu8IF+qCZE3/I+PKhu60pCFkcOvV5aDaY7Mu
# 6QXuqvYk9R28mxyyt1/f8O52fTGZZUdVnUokL6wrl76f5P17cz4y7lI0+9S769Sg
# LDSb495uZBkHNwGRDxy1Uc2qTGaDiGhiu7xBG3gZbeTZD+BYQfvYsSzhUa+0rRUG
# FOpiCBPTaR58ZE2dD9/O0V6MqqtQFcmzyrzXxDtoRKOlO0L9c33u3Qr/eTQQfqZc
# ClhMAD6FaXXHg2TWdc2PEnZWpST618RrIbroHzSYLzrqawGw9/sqhux7UjipmAmh
# cbJsca8+uG+W1eEQE/5hRwqM/vC2x9XH3mwk8L9CgsqgcT2ckpMEtGlwJw1Pt7U2
# 0clfCKRwo+wK8REuZODLIivK8SgTIUlRfgZm0zu++uuRONhRB8qUt+JQofM604qD
# y0B7AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAW
# BgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglg
# hkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0O
# BBYEFKW27xPn783QZKHVVqllMaPe1eNJMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6
# Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEy
# NTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZT
# SEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAIEa1t6g
# qbWYF7xwjU+KPGic2CX/yyzkzepdIpLsjCICqbjPgKjZ5+PF7SaCinEvGN1Ott5s
# 1+FgnCvt7T1IjrhrunxdvcJhN2hJd6PrkKoS1yeF844ektrCQDifXcigLiV4JZ0q
# BXqEKZi2V3mP2yZWK7Dzp703DNiYdk9WuVLCtp04qYHnbUFcjGnRuSvExnvPnPp4
# 4pMadqJpddNQ5EQSviANnqlE0PjlSXcIWiHFtM+YlRpUurm8wWkZus8W8oM3NG6w
# QSbd3lqXTzON1I13fXVFoaVYJmoDRd7ZULVQjK9WvUzF4UbFKNOt50MAcN7MmJ4Z
# iQPq1JE3701S88lgIcRWR+3aEUuMMsOI5ljitts++V+wQtaP4xeR0arAVeOGv6wn
# LEHQmjNKqDbUuXKWfpd5OEhfysLcPTLfddY2Z1qJ+Panx+VPNTwAvb6cKmx5Adza
# ROY63jg7B145WPR8czFVoIARyxQMfq68/qTreWWqaNYiyjvrmoI1VygWy2nyMpqy
# 0tg6uLFGhmu6F/3Ed2wVbK6rr3M66ElGt9V/zLY4wNjsHPW2obhDLN9OTH0eaHDA
# dwrUAuBcYLso/zjlUlrWrBciI0707NMX+1Br/wd3H3GXREHJuEbTbDJ8WC9nR2Xl
# G3O2mflrLAZG70Ee8PBf4NvZrZCARK+AEEGKMIIHXzCCBUegAwIBAgIQB8JSdCgU
# otar/iTqF+XdLjANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UE
# ChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQg
# Q29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIzMDQxNjAw
# MDAwMFoXDTI2MDcwNjIzNTk1OVowZzELMAkGA1UEBhMCUEwxEjAQBgNVBAcMCU1p
# a2/FgsOzdzEhMB8GA1UECgwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMSEwHwYD
# VQQDDBhQcnplbXlzxYJhdyBLxYJ5cyBFVk9URUMwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQCUmgeXMQtIaKaSkKvbAt8GFZJ1ywOH8SwxlTus4McyrWmV
# OrRBVRQA8ApF9FaeobwmkZxvkxQTFLHKm+8knwomEUslca8CqSOI0YwELv5EwTVE
# h0C/Daehvxo6tkmNPF9/SP1KC3c0l1vO+M7vdNVGKQIQrhxq7EG0iezBZOAiukNd
# GVXRYOLn47V3qL5PwG/ou2alJ/vifIDad81qFb+QkUh02Jo24SMjWdKDytdrMXi0
# 235CN4RrW+8gjfRJ+fKKjgMImbuceCsi9Iv1a66bUc9anAemObT4mF5U/yQBgAuA
# o3+jVB8wiUd87kUQO0zJCF8vq2YrVOz8OJmMX8ggIsEEUZ3CZKD0hVc3dm7cWSAw
# 8/FNzGNPlAaIxzXX9qeD0EgaCLRkItA3t3eQW+IAXyS/9ZnnpFUoDvQGbK+Q4/bP
# 0ib98XLfQpxVGRu0cCV0Ng77DIkRF+IyR1PcwVAq+OzVU3vKeo25v/rntiXCmCxi
# W4oHYO28eSQ/eIAcnii+3uKDNZrI15P7VxDrkUIc6FtiSvOhwc3AzY+vEfivUkFK
# RqwvSSr4fCrrkk7z2Qe72Zwlw2EDRVHyy0fUVGO9QMuh6E3RwnJL96ip0alcmhKA
# BGoIqSW05nXdCUbkXmhPCTT5naQDuZ1UkAXbZPShKjbPwzdXP2b8I9nQ89VSgQID
# AQABo4ICAzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYD
# VR0OBBYEFHrxaiVZuDJxxEk15bLoMuFI5233MA4GA1UdDwEB/wQEAwIHgDATBgNV
# HSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3Js
# My5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQw
# OTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAy
# MUNBMS5jcmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0
# cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggr
# BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBo
# dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2Rl
# U2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqG
# SIb3DQEBCwUAA4ICAQC3EeHXUPhpe31K2DL43Hfh6qkvBHyR1RlD9lVIklcRCR50
# ZHzoWs6EBlTFyohvkpclVCuRdQW33tS6vtKPOucpDDv4wsA+6zkJYI8fHouW6Tqa
# 1W47YSrc5AOShIcJ9+NpNbKNGih3doSlcio2mUKCX5I/ZrzJBkQpJ0kYha/pUST2
# CbE3JroJf2vQWGUiI+J3LdiPNHmhO1l+zaQkSxv0cVDETMfQGZKKRVESZ6Fg61b0
# djvQSx510MdbxtKMjvS3ZtAytqnQHk1ipP+Rg+M5lFHrSkUlnpGa+f3nuQhxDb7N
# 9E8hUVevxALTrFifg8zhslVRH5/Df/CxlMKXC7op30/AyQsOQxHW1uNx3tG1DMgi
# zpwBasrxh6wa7iaA+Lp07q1I92eLhrYbtw3xC2vNIGdMdN7nd76yMIjdYnAn7r38
# wwtaJ3KYD0QTl77EB8u/5cCs3ShZdDdyg4K7NoJl8iEHrbqtooAHOMLiJpiL2i9Y
# n8kQMB6/Q6RMO3IUPLuycB9o6DNiwQHf6Jt5oW7P09k5NxxBEmksxwNbmZvNQ65Z
# n3exUAKqG+x31Egz5IZ4U/jPzRalElEIpS0rgrVg8R8pEOhd95mEzp5WERKFyXhe
# 6nB6bSYHv8clLAV0iMku308rpfjMiQkqS3LLzfUJ5OHqtKKQNMLxz9z185UCszGC
# BlMwggZPAgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
# bmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBS
# U0E0MDk2IFNIQTM4NCAyMDIxIENBMQIQB8JSdCgUotar/iTqF+XdLjANBglghkgB
# ZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJ
# AzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8G
# CSqGSIb3DQEJBDEiBCAphIqWij92rGJT25BQhSBQpQu0Xgp2Q2prGkTqqcS9ZzAN
# BgkqhkiG9w0BAQEFAASCAgAuXibS3bMrnTu3B+TmuNz1CyBf7f38xKBd4QRGUils
# 1Y6dUlZTKRUsU1K1nLiZsgdfixkyYEFEiB0lOjNRt92qkAPqJhyQIFfsPH9khUi4
# KuO9PA7G3Sm9vkfBdFKMpagMqulPQVjrc70dnu10ONXQOKckhYzLgrncuWTgJu+E
# 1t/MmEVKhGou/QX0lUlzP7Mg/8rfdjqXCyP4lq8VyZT2W1yIdlPRq3NWdCjbLpJX
# c2fIq4k4QnWoZY1fKesdgKV7FgF/rPyqnWi88Y3aJIWbcKQUP0PbxG/U/3VNgWIa
# i9INN4/7LMHOWuYpbu1ZYXYAArghFHMo9hCX/Fj7O2GZHKB4Ep6j0UeGiucEpBoH
# NkvYyUN8zLUVMyucB81/YeonQtUHdU9xSf59QaoPPHxIbl0BzXULn1viFRnFhDja
# eJoLPCE5EBuXAuIvJM9VaOtXuXMEJZQk4SViOJbOBdL8jSW+3Tw8cTNsuUF8Md4j
# MUDxADgTzfoPtQrU6rZKUV4emO+XwY2husLfC2BS0cKKXqa988mVbOzadvTKK+pp
# n7AeemSTTn5A/ZximmDsTCNlPwxRl3O0fBqf+fSfQ9pPWy0TB0U82r4Alm3FuR+h
# vk76/Kd8XeYrJD4JrqHZxqRvKhkFao3fUvR2X/35QhoheNfMMChwboq18HBgeM86
# 9KGCAyAwggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1
# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAVEr/OUnQg5
# pr/bP1/lYRYwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN
# AQcBMBwGCSqGSIb3DQEJBTEPFw0yMzExMTUxNTQ4NDBaMC8GCSqGSIb3DQEJBDEi
# BCD1+4iJhjQ1gwVqS+MGaB4WthofN+dhnfEQVb5fe5lKdTANBgkqhkiG9w0BAQEF
# AASCAgAuXrNVtVK/4uRosbvNNCUlOdoiifJ4Zhf7zd2+va/eEFDLNNenw35SHZ/9
# WwRdGdtgSF+TuqUaZDkn9M86YQZl34IEeEW5YNFLa6Bzm5aKPwqi7LKLFa5vGxaX
# s0y/ON/0W4iZ9s2v8fsjXCLX9Ibf0QxfHpGbuHiDR6VbTG+XBr3/cw47tj/mtcMa
# oWYe23bvdVf+K5E8SROEoj4McTqNZz6bSBFPocFBk520OO6wqfEtns0m+J0kDRDV
# FMlIGh2/S9ok2XmUjVmWEBA35an24vKVKvXwsqJSdosWSUzQNfAIzP/Aq6lmvrMv
# OzOxKKSNesT/tgG/pqRVJ91rdokZu9oINsxP4Ue9I1XRxY47jztBDsODaDGlR+zQ
# zEbUm45bTTlIoG40K79vdbMZ/RHpP9UnogFDhg7QSLw9bBdr3xOJueaEoJj2EgA7
# cyqQhDkSICxkxqPgAsiP95dCST4mUv7QieMz+AW14Hr7RDpsX2E7tWjSWJhVZBJb
# MoZSIVQo42PhObuvKZ2z7Q4N3XJxaB+wiBHrzdPA7plsUG5c6/ohAp1zeKMU+uy0
# LRbk81J9wQqQjBDyD8Q9Gj+yd7jKitwoETJySluyKyWYQe8lf6JfWIdCgE13IFDl
# HBTjPGVet2CpuGVJPo+UBh3bt6G8fcLwgIegCPSh1ZBJYISxtw==
# SIG # End signature block