PSPublishModule.psm1

function Add-Directory {
    [CmdletBinding()]
    param($dir)
    $exists = Test-Path -Path $dir
    if ($exists -eq $false) {$createdDirectory = mkdir $dir} else {}
}
function Add-FilesWithFolders {
    [CmdletBinding()]
    param ($file, $FullProjectPath, $directory)
    $LinkPrivatePublicFiles = @()
    $path = $file
    foreach ($dir in $directory) {
        if ($path -like "$dir*") {
            $LinkPrivatePublicFiles += $path
            Write-Verbose "Adding file to linking list of files $path"
        }
    }
    return $LinkPrivatePublicFiles
}
function Add-FilesWithFoldersNew {
    [CmdletBinding()]
    param($File, $FullProjectPath, $directory)
}
function Add-ObjectTo {
    [CmdletBinding()]
    param($Object, $Type)
    Write-Verbose "Adding $($Object) to $Type"
    return $Object
}
function Copy-File {
    [CmdletBinding()]
    param ($Source,
        $Destination)
    if ((Test-Path $Source) -and !(Test-Path $Destination)) {Copy-Item -Path $Source -Destination $Destination}
}
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])]
    param([Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [PSObject[]]
        $InputObject,
        [Parameter(Mandatory = $true, Position = 0)]
        [string]
        $DataFile)
    begin {$AllObjects = New-Object Collections.ArrayList}
    process {$null = $AllObjects.AddRange($InputObject)}
    end {
        $text = $AllObjects |
            Write-PowerShellHashtable
        $text |
            Set-Content -Path $DataFile
        Get-Item -Path $DataFile
    }
}
function Find-EnumsList {
    [CmdletBinding()]
    param ([string] $ProjectPath)
    $Enums = @(Get-ChildItem -Path $ProjectPath\Enums\*.ps1 -ErrorAction SilentlyContinue)
    $Opening = '@('
    $Closing = ')'
    $Adding = ','
    $EnumsList = New-ArrayList
    Add-ToArray -List $EnumsList -Element $Opening
    Foreach ($import in @($Enums)) {
        $Entry = "'Enums\$($import.Name)'"
        Add-ToArray -List $EnumsList -Element $Entry
        Add-ToArray -List $EnumsList -Element $Adding
    }
    Remove-FromArray -List $EnumsList -LastElement
    Add-ToArray -List $EnumsList -Element $Closing
    return [string] $EnumsList
}
function Format-PSD1 {
    param([string] $PSD1FilePath,
        $FormatCode)
    if ($FormatCode.Enabled) {
        $Output = Get-Content -LiteralPath $PSD1FilePath -Raw
        if ($FormatCode.RemoveComments) {
            Write-Verbose "Removing Comments - $PSD1FilePath"
            $Output = Remove-Comments -ScriptContent $Output
        }
        Write-Verbose "Formatting - $PSD1FilePath"
        if ($null -eq $FormatCode.FormatterSettings) {$FormatCode.FormatterSettings = $Script:FormatterSettings}
        $Output = Invoke-Formatter -ScriptDefinition $Output -Settings $FormatCode.FormatterSettings
        $Output | Out-File -LiteralPath $PSD1FilePath -NoNewline
    }
}
function Format-Code {
    param([string] $FilePath,
        $FormatCode)
    if ($FormatCode.Enabled) {
        if ($FormatCode.RemoveComments) {
            Write-Verbose "Removing Comments - $FilePath"
            $Output = Remove-Comments -FilePath $FilePath
        } else {$Output = Get-Content -LiteralPath $FilePath -Raw}
        if ($null -eq $FormatCode.FormatterSettings) {$FormatCode.FormatterSettings = $Script:FormatterSettings}
        Write-Verbose "Formatting - $FilePath"
        $Output = Invoke-Formatter -ScriptDefinition $Output -Settings $FormatCode.FormatterSettings
        $Output = foreach ($O in $Output) {if ($O.Trim() -ne '') {$O.Trim()}}
        $Output | Set-Content -LiteralPath $FilePath -NoNewline -Encoding utf8
    }
}
Function Get-AliasTarget {
    [cmdletbinding()]
    param ([Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('PSPath', 'FullName')]
        [string[]]$Path)
    process {
        foreach ($File in $Path) {
            $FileAst = [System.Management.Automation.Language.Parser]::ParseFile($File, [ref]$null, [ref]$null)
            $FunctionName = $FileAst.FindAll( {param ($ast)
                    $ast -is [System.Management.Automation.Language.FunctionDefinitionAst]}, $true).Name
            $AliasDefinitions = $FileAst.FindAll( {param ($ast)
                    $ast -is [System.Management.Automation.Language.StringConstantExpressionAst] -And $ast.Value -match '(New|Set)-Alias'}, $true)
            $AliasTarget = $AliasDefinitions.Parent.CommandElements.Where( {$_.StringConstantType -eq 'BareWord' -and
                    $_.Value -notin ('New-Alias', 'Set-Alias', $FunctionName)}).Value
            $Attributes = $FileAst.FindAll( {param ($ast)
                    $ast -is [System.Management.Automation.Language.AttributeAst]}, $true)
            $AliasDefinitions = $Attributes.Where( {$_.TypeName.Name -eq 'Alias' -and $_.Parent -is [System.Management.Automation.Language.ParamBlockAst]})
            $AliasTarget += $AliasDefinitions.PositionalArguments.Value
            [PsCustomObject]@{Function = $FunctionName
                Alias = $AliasTarget
            }
        }
    }
}
function Get-FunctionAliases {
    param([string] $Path)
    Import-Module $Path -Force -Verbose:$False
    $Names = Get-FunctionNames -Path $Path
    $Aliases = foreach ($Name in $Names) {Get-Alias | Where-Object {$_.Definition -eq $Name}}
    return $Aliases
}
function Get-FunctionAliasesFromFolder {
    param([string] $FullProjectPath,
        [string[]] $Folder)
    foreach ($F in $Folder) {
        $Path = [IO.Path]::Combine($FullProjectPath, $F)
        $Files = Get-ChildItem -Path $Path -File -Recurse
        $AliasesToExport = foreach ($file in $Files) {Get-AliasTarget -Path $File.FullName | Select-Object -ExpandProperty Alias}
        $AliasesToExport
    }
}
function Get-FunctionNames {
    param([string] $Path,
        [switch] $Recurse)
    [Management.Automation.Language.Parser]::ParseFile((Resolve-Path $Path),
        [ref]$null,
        [ref]$null).FindAll( {param($c)$c -is [Management.Automation.Language.FunctionDefinitionAst]}, $Recurse).Name
}
function Get-FunctionNamesFromFolder {
    param([string] $FullProjectPath,
        [string[]] $Folder)
    foreach ($F in $Folder) {
        $Path = [IO.Path]::Combine($FullProjectPath, $F)
        $Files = Get-ChildItem -Path $Path -File -Recurse
        $FunctionToExport = foreach ($file in $Files) {Get-FunctionNames -Path $File.FullName}
        $FunctionToExport
    }
}
Function Get-ScriptComments {
    <#
    .Synopsis
    Get comments from a PowerShell script file.
    .Description
    This command will use the AST parser to go through a PowerShell script, either a .ps1 or .psm1 file, and display only the comments.
    .Example
    PS C:\> get-scriptcomments c:\scripts\MyScript.ps1
    #>

    [cmdletbinding()]
    Param([Parameter(Position = 0, Mandatory, HelpMessage = "Enter the path of a PS1 file",
            ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias("PSPath", "Name")]
        [ValidateScript( {Test-Path $_})]
        [ValidatePattern("\.ps(1|m1)$")]
        [string]$Path)
    Begin {
        Write-Verbose -Message "Starting $($MyInvocation.Mycommand)"
        New-Variable astTokens -force
        New-Variable astErr -force
    }
    Process {
        $Path = Convert-Path -Path $Path
        Write-Verbose -Message "Parsing $Path"
        $ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$astTokens, [ref]$astErr)
        $asttokens.where( {$_.kind -eq 'comment'}) | Select-Object -ExpandProperty Text
        $ast
    }
    End {Write-Verbose -Message "Ending $($MyInvocation.Mycommand)"}
}
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,
        [Array] $LibrariesCore,
        [Array] $LibrariesDefault,
        $FormatCodePSM1,
        $FormatCodePSD1)
    $PSM1FilePath = "$ModulePathTarget\$ModuleName.psm1"
    $PSD1FilePath = "$ModulePathTarget\$ModuleName.psd1"
    $ScriptFunctions = @(Get-ChildItem -Path $ModulePathSource\*.ps1 -ErrorAction SilentlyContinue -Recurse)
    if ($Sort -eq 'ASC') {$ScriptFunctions = $ScriptFunctions | Sort-Object -Property Name} elseif ($Sort -eq 'DESC') {$ScriptFunctions = $ScriptFunctions | Sort-Object -Descending -Property Name}
    foreach ($FilePath in $ScriptFunctions) {
        $Content = Get-Content -Path $FilePath -Raw
        $Content = $Content.Replace('$PSScriptRoot\', '$PSScriptRoot\')
        $Content = $Content.Replace('$PSScriptRoot\', '$PSScriptRoot\')
        $Content | Out-File -Append -LiteralPath $PSM1FilePath -Encoding utf8
    }
    New-PSMFile -Path $PSM1FilePath -FunctionNames $FunctionsToExport -FunctionAliaes $AliasesToExport -LibrariesCore $LibrariesCore -LibrariesDefault $LibrariesDefault
    Format-Code -FilePath $PSM1FilePath -FormatCode $FormatCodePSM1
    New-PersonalManifest -Configuration $Configuration -ManifestPath $PSD1FilePath
    Format-Code -FilePath $PSD1FilePath -FormatCode $FormatCodePSD1
}
function New-CreateModule {
    [CmdletBinding()]
    param ([string] $ProjectName,
        $ModulePath,
        $ProjectPath)
    $FullProjectPath = "$projectPath\$projectName"
    $Folders = 'Private', 'Public', 'Examples', 'Ignore', 'Publish', 'Enums', 'Data'
    Add-Directory $FullProjectPath
    foreach ($folder in $Folders) {Add-Directory "$FullProjectPath\$folder"}
    Copy-File -Source "$PSScriptRoot\Data\Example-Gitignore.txt" -Destination "$FullProjectPath\.gitignore"
    Copy-File -Source "$PSScriptRoot\Data\Example-LicenseMIT.txt" -Destination "$FullProjectPath\License"
    Copy-File -Source "$PSScriptRoot\Data\Example-ModuleStarter.ps1" -Destination "$FullProjectPath\$ProjectName.psm1"
}
function New-PersonalManifest {
    [CmdletBinding()]
    param([System.Collections.IDictionary] $Configuration,
        [string] $ManifestPath,
        [switch] $AddScriptsToProcess)
    $Manifest = $Configuration.Information.Manifest
    $Manifest.Path = $ManifestPath
    if (-not $AddScriptsToProcess) {$Manifest.ScriptsToProcess = @()}
    New-ModuleManifest @Manifest
    if ($Configuration.Steps.PublishModule.Prerelease -ne '') {
        $Data = Import-PowerShellDataFile -Path $Configuration.Information.Manifest.Path
        if ($Data.ScriptsToProcess.Count -eq 0) {$Data.Remove('ScriptsToProcess')}
        if ($Data.CmdletsToExport.Count -eq 0) {$Data.Remove('CmdletsToExport')}
        $Data.PrivateData.PSData.Prerelease = $Configuration.Information.Versioning.Prerelease
        $Data | Export-PSData -DataFile $Configuration.Information.Manifest.Path
    }
    Write-Verbose "Converting $($Configuration.Information.Manifest.Path)"
    (Get-Content $Manifest.Path) | Out-FileUtf8NoBom $Manifest.Path
}
function New-PrepareManifest {
    [CmdletBinding()]
    param($ProjectName,
        $modulePath,
        $projectPath,
        $functionToExport,
        $projectUrl)
    Set-Location "$projectPath\$ProjectName"
    $manifest = @{Path = ".\$ProjectName.psd1"
        RootModule = "$ProjectName.psm1"
        Author = 'Przemyslaw Klys'
        CompanyName = 'Evotec'
        Copyright = 'Evotec (c) 2018. 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-PrepareModule {
    [CmdletBinding()]
    param ([System.Collections.IDictionary] $Configuration,
        [string] $FullProjectPath)
    Begin {
        if (-not $Configuration) {return}
        [string] $FullModulePath = [IO.path]::Combine($Configuration.Information.DirectoryModules, $Configuration.Information.ModuleName)
        [string] $FullModulePathDelete = [IO.path]::Combine($Configuration.Information.DirectoryModules, $Configuration.Information.ModuleName)
        [string] $FullTemporaryPath = [IO.path]::GetTempPath() + '' + $Configuration.Information.ModuleName
        if ($FullProjectPath -eq '') {$FullProjectPath = [IO.Path]::Combine($Configuration.Information.DirectoryProjects, $Configuration.Information.ModuleName)}
        [string] $ProjectName = $Configuration.Information.ModuleName
        Write-Verbose '----------------------------------------------------'
        Write-Verbose "Project Name: $ProjectName"
        Write-Verbose "Full module path: $FullModulePath"
        Write-Verbose "Full project path: $FullProjectPath"
        Write-Verbose "Full module path to delete: $FullModulePathDelete"
        Write-Verbose "Full temporary path: $FullTemporaryPath"
        Write-Verbose "PSScriptRoot: $PSScriptRoot"
        Write-Verbose '----------------------------------------------------'
        $CurrentLocation = (Get-Location).Path
        Set-Location -Path $FullProjectPath
        Remove-Directory $FullModulePathDelete
        Remove-Directory $FullModulePath
        Remove-Directory $FullTemporaryPath
        Add-Directory $FullModulePath
        Add-Directory $FullTemporaryPath
        $DirectoryTypes = 'Public', 'Private', 'Lib', 'Bin', 'Enums', 'Images', 'Templates', 'Resources'
        $LinkDirectories = @()
        $LinkPrivatePublicFiles = @()
        $Configuration.Information.Manifest.RootModule = "$($ProjectName).psm1"
        $Configuration.Information.Manifest.FunctionsToExport = @()
        $Configuration.Information.Manifest.CmdletsToExport = @()
        $Configuration.Information.Manifest.VariablesToExport = @()
        $Configuration.Information.Manifest.AliasesToExport = @()
    }
    Process {
        if ($Configuration.Steps.BuildModule) {
            $Directories = Get-ChildItem -Path $FullProjectPath -Directory -Recurse
            foreach ($directory in $Directories) {
                $RelativeDirectoryPath = (Resolve-Path -LiteralPath $directory.FullName -Relative).Replace('.\', '')
                $RelativeDirectoryPath = "$RelativeDirectoryPath\"
                foreach ($LookupDir in $DirectoryTypes) {if ($RelativeDirectoryPath -like "$LookupDir\*") {$LinkDirectories += Add-ObjectTo -Object $RelativeDirectoryPath -Type 'Directory List'}}
            }
            $Files = Get-ChildItem -Path $FullProjectPath -File -Recurse
            $AllFiles = @()
            foreach ($File in $Files) {
                $RelativeFilePath = (Resolve-Path -LiteralPath $File.FullName -Relative).Replace('.\', '')
                $AllFiles += $RelativeFilePath
            }
            $RootFiles = @()
            $Files = Get-ChildItem -Path $FullProjectPath -File
            foreach ($File in $Files) {
                $RelativeFilePath = (Resolve-Path -LiteralPath $File.FullName -Relative).Replace('.\', '')
                $RootFiles += $RelativeFilePath
            }
            $LinkFilesRoot = @()
            foreach ($File in $RootFiles) {
                switch -Wildcard ($file) {
                    '*.psd1' {$LinkFilesRoot += Add-ObjectTo -Object $File -Type 'Root Files List'}
                    '*.psm1' {$LinkFilesRoot += Add-ObjectTo -Object $File -Type 'Root Files List'}
                    'License*' {$LinkFilesRoot += Add-ObjectTo -Object $File -Type 'Root Files List'}
                }
            }
            foreach ($file in $AllFiles) {
                switch -Wildcard ($file) {
                    "*.dll" {$LinkPrivatePublicFiles += Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Lib'}
                    "*.exe" {$LinkPrivatePublicFiles += Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Bin'}
                    '*.ps1' {$LinkPrivatePublicFiles += Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Private', 'Public', 'Enums'}
                    '*license*' {$LinkPrivatePublicFiles += Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Lib', 'Resources'}
                    '*.jpg' {$LinkPrivatePublicFiles += Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Images', 'Resources'}
                    '*.png' {$LinkPrivatePublicFiles += Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Images', 'Resources'}
                    '*.xml' {$LinkPrivatePublicFiles += Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Templates'}
                    '*.docx' {$LinkPrivatePublicFiles += Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Templates'}
                    '*.js' {$LinkPrivatePublicFiles += Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Resources'}
                    '*.css' {$LinkPrivatePublicFiles += Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Resources'}
                    '*.rcs' {$LinkPrivatePublicFiles += Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Resources'}
                    '*.gif' {$LinkPrivatePublicFiles += Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Resources'}
                    '*.html' {$LinkPrivatePublicFiles += Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Resources'}
                }
            }
            if ($Configuration.Information.Manifest) {
                $Functions = Get-FunctionNamesFromFolder -FullProjectPath $FullProjectPath -Folder $Configuration.Information.FunctionsToExport
                if ($Functions) {
                    Write-Verbose "Functions export: $Functions"
                    $Configuration.Information.Manifest.FunctionsToExport = $Functions
                }
                $Aliases = Get-FunctionAliasesFromFolder -FullProjectPath $FullProjectPath -Folder $Configuration.Information.AliasesToExport
                if ($Aliases) {
                    Write-Verbose "Aliases export: $Aliases"
                    $Configuration.Information.Manifest.AliasesToExport = $Aliases
                }
                if (-not [string]::IsNullOrWhiteSpace($Configuration.Information.ScriptsToProcess)) {
                    if (-not $Configuration.Options.Merge.Enabled) {
                        $StartsWithEnums = "$($Configuration.Information.ScriptsToProcess)\"
                        $FilesEnums = $LinkPrivatePublicFiles | Where-Object {($_).StartsWith($StartsWithEnums)}
                        if ($FilesEnums.Count -gt 0) {
                            Write-Verbose "ScriptsToProcess export: $FilesEnums"
                            $Configuration.Information.Manifest.ScriptsToProcess = $FilesEnums
                        }
                    }
                }
                $PSD1FilePath = "$FullProjectPath\$ProjectName.psd1"
                New-PersonalManifest -Configuration $Configuration -ManifestPath $PSD1FilePath -AddScriptsToProcess
                Format-Code -FilePath $PSD1FilePath -FormatCode $Configuration.Options.Standard.FormatCodePSD1
            }
            if ($Configuration.Options.Merge.Enabled) {
                foreach ($Directory in $LinkDirectories) {
                    $Dir = "$FullTemporaryPath\$Directory"
                    Add-Directory $Dir
                }
                $LinkDirectoriesWithSupportFiles = $LinkDirectories | Where-Object {$_ -ne 'Public\' -and $_ -ne 'Private\'}
                foreach ($Directory in $LinkDirectoriesWithSupportFiles) {
                    $Dir = "$FullModulePath\$Directory"
                    Add-Directory $Dir
                }
                Write-Verbose '[+] Linking files from Root Dir'
                Set-LinkedFiles -LinkFiles $LinkFilesRoot -FullModulePath $FullTemporaryPath -FullProjectPath $FullProjectPath
                Write-Verbose '[+] Linking files from Sub Dir'
                Set-LinkedFiles -LinkFiles $LinkPrivatePublicFiles -FullModulePath $FullTemporaryPath -FullProjectPath $FullProjectPath
                $FilesToLink = $LinkPrivatePublicFiles | Where-Object {$_ -notlike '*.ps1' -and $_ -notlike '*.psd1'}
                Set-LinkedFiles -LinkFiles $FilesToLink -FullModulePath $FullModulePath -FullProjectPath $FullProjectPath
                if (-not [string]::IsNullOrWhiteSpace($Configuration.Information.LibrariesCore)) {
                    $StartsWithCore = "$($Configuration.Information.LibrariesCore)\"
                    $FilesLibrariesCore = $LinkPrivatePublicFiles | Where-Object {($_).StartsWith($StartsWithCore)}
                }
                if (-not [string]::IsNullOrWhiteSpace($Configuration.Information.LibrariesDefault)) {
                    $StartsWithDefault = "$($Configuration.Information.LibrariesDefault)\"
                    $FilesLibrariesDefault = $LinkPrivatePublicFiles | Where-Object {($_).StartsWith($StartsWithDefault)}
                }
                Merge-Module -ModuleName $ProjectName -ModulePathSource $FullTemporaryPath -ModulePathTarget $FullModulePath -Sort $Configuration.Options.Merge.Sort -FunctionsToExport $Configuration.Information.Manifest.FunctionsToExport -AliasesToExport $Configuration.Information.Manifest.AliasesToExport -LibrariesCore $FilesLibrariesCore -LibrariesDefault $FilesLibrariesDefault -FormatCodePSM1 $Configuration.Options.Merge.FormatCodePSM1 -FormatCodePSD1 $Configuration.Options.Merge.FormatCodePSD1
            } else {
                foreach ($Directory in $LinkDirectories) {
                    $Dir = "$FullModulePath\$Directory"
                    Add-Directory $Dir
                }
                Write-Verbose '[+] Linking files from Root Dir'
                Set-LinkedFiles -LinkFiles $LinkFilesRoot -FullModulePath $FullModulePath -FullProjectPath $FullProjectPath
                Write-Verbose '[+] Linking files from Sub Dir'
                Set-LinkedFiles -LinkFiles $LinkPrivatePublicFiles -FullModulePath $FullModulePath -FullProjectPath $FullProjectPath
            }
        }
        if ($Configuration.Steps.PublishModule.Enabled) {
            if ($Configuration.Options.PowerShellGallery.FromFile) {
                $ApiKey = Get-Content -Path $Configuration.Options.PowerShellGallery.ApiKey
                New-PublishModule -ProjectName $Configuration.Information.ModuleName -ApiKey $ApiKey -RequireForce $Configuration.Steps.PublishModule.RequireForce
            } else {New-PublishModule -ProjectName $Configuration.Information.ModuleName -ApiKey $Configuration.Options.PowerShellGallery.ApiKey -RequireForce $Configuration.Steps.PublishModule.RequireForce}
        }
    }
    end {
        Set-Location -Path $CurrentLocation
        if ($Configuration) {
            if ($Configuration.Options.ImportModules.RequiredModules) {foreach ($Module in $Configuration.Information.Manifest.RequiredModules) {Import-Module -Name $Module -Force}}
            if ($Configuration.Options.ImportModules.Self) {Import-Module -Name $ProjectName -Force}
            if ($Configuration.Steps.BuildDocumentation) {
                $DocumentationPath = "$FullProjectPath\$($Configuration.Options.Documentation.Path)"
                $ReadMePath = "$FullProjectPath\$($Configuration.Options.Documentation.PathReadme)"
                Write-Verbose "Generating documentation to $DocumentationPath with $ReadMePath"
                if (-not (Test-Path -Path $DocumentationPath)) {$null = New-Item -Path "$FullProjectPath\Docs" -ItemType Directory -Force}
                $Files = Get-ChildItem -Path $DocumentationPath
                if ($Files.Count -gt 0) {$null = Update-MarkdownHelpModule $DocumentationPath -RefreshModulePage -ModulePagePath $ReadMePath} else {
                    $null = New-MarkdownHelp -Module $ProjectName -WithModulePage -OutputFolder $DocumentationPath
                    $null = Move-Item -Path "$DocumentationPath\$ProjectName.md" -Destination $ReadMePath
                    if ($Configuration.Options.Documentation.UpdateWhenNew) {$null = Update-MarkdownHelpModule $DocumentationPath -RefreshModulePage -ModulePagePath $ReadMePath}
                }
            }
        }
    }
}
function New-PSMFile {
    param([string] $Path,
        [string[]] $FunctionNames,
        [string[]] $FunctionAliaes,
        [Array] $LibrariesCore,
        [Array] $LibrariesDefault)
    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 = @()}
    "" | Add-Content -Path $Path
    if ($LibrariesCore.Count -gt 0 -and $LibrariesDefault.Count -gt 0) {
        'if ($PSEdition -eq ''Core'') {' | Add-Content -Path $Path
        foreach ($File in $LibrariesCore) {
            $Output = 'Add-Type -Path $PSScriptRoot\' + $File
            $Output | Add-Content -Path $Path
        }
        '} else {' | Add-Content -Path $Path
        foreach ($File in $LibrariesDefault) {
            $Output = 'Add-Type -Path $PSScriptRoot\' + $File
            $Output | Add-Content -Path $Path
        }
        '}' | Add-Content -Path $Path
    } elseif ($LibrariesCore.Count -gt 0) {
        foreach ($File in $LibrariesCore) {
            $Output = 'Add-Type -Path $PSScriptRoot\' + $File
            $Output | Add-Content -Path $Path
        }
    } elseif ($LibrariesDefault.Count -gt 0) {
        foreach ($File in $LibrariesDefault) {
            $Output = 'Add-Type -Path $PSScriptRoot\' + $File
            $Output | Add-Content -Path $Path
        }
    }
    @"
 
Export-ModuleMember ``
    -Function @($Functions) ``
    -Alias @($Aliases)
"@
 | Add-Content -Path $Path
}
function New-PublishModule {
    param($projectName,
        $apikey,
        [bool] $RequireForce)
    Publish-Module -Name $projectName -Repository PSGallery -NuGetApiKey $apikey -Force:$RequireForce -verbose
}
<#
.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.
 
#>

function Out-FileUtf8NoBom {
    [CmdletBinding()]
    param([Parameter(Mandatory, Position = 0)] [string] $LiteralPath,
        [switch] $Append,
        [switch] $NoClobber,
        [AllowNull()] [int] $Width,
        [Parameter(ValueFromPipeline)] $InputObject)
    #requires -version 3
    [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 | % {$sw.WriteLine($_)}} finally {$sw.Dispose()}
}
function Remove-Comments {
    Param ([string] $FilePath,
        [parameter(ValueFromPipeline = $True)] $Scriptblock,
        [string] $ScriptContent)
    Begin {
        $Items = @()
        if ($PSBoundParameters['FilePath']) {
            $ScriptBlockString = [IO.File]::ReadAllText((Resolve-Path $FilePath))
            $ScriptBlock = [ScriptBlock]::Create($ScriptBlockString)
        } elseif ($PSBoundParameters['ScriptContent']) {$ScriptBlock = [ScriptBlock]::Create($ScriptContent)} else {}
    }
    Process {$Items += $Scriptblock}
    End {
        $OldScript = $Items -join [environment]::NewLine
        If (-not $OldScript.Trim(" `n`r`t")) {return}
        $Tokens = [System.Management.Automation.PSParser]::Tokenize($OldScript, [ref]$Null)
        $AllowedComments = @('requires'
            '.SYNOPSIS'
            '.DESCRIPTION'
            '.PARAMETER'
            '.EXAMPLE'
            '.INPUTS'
            '.OUTPUTS'
            '.NOTES'
            '.LINK'
            '.COMPONENT'
            '.ROLE'
            '.FUNCTIONALITY'
            '.FORWARDHELPCATEGORY'
            '.REMOTEHELPRUNSPACE'
            '.EXTERNALHELP')
        $Tokens = $Tokens.ForEach{If ($_.Type -ne 'Comment') {$_} Else {
                $CommentText = $_.Content.Substring($_.Content.IndexOf('#') + 1)
                $FirstInnerToken = [System.Management.Automation.PSParser]::Tokenize($CommentText, [ref]$Null) |
                    Where-Object {$_.Type -ne 'NewLine'} |
                    Select-Object -First 1
                If ($FirstInnerToken.Content -in $AllowedComments) {$_}
            }}
        $NewScriptText = ''
        $SkipNext = $False
        If ($Tokens.Count -gt 1) {
            ForEach ($i in (0..($Tokens.Count - 2))) {
                If (-not $SkipNext -and
                    $Tokens[$i ].Type -ne 'LineContinuation' -and ($Tokens[$i ].Type -notin ('NewLine', 'StatementSeparator') -or
                        $Tokens[$i + 1].Type -notin ('NewLine', 'StatementSeparator', 'GroupEnd'))) {
                    If ($Tokens[$i].Type -in ('String', 'Variable')) {$NewScriptText += $OldScript.Substring($Tokens[$i].Start, $Tokens[$i].Length)} Else {$NewScriptText += $Tokens[$i].Content}
                    If ($Tokens[$i ].Type -notin ('NewLine', 'GroupStart', 'StatementSeparator') -and
                        $Tokens[$i + 1].Type -notin ('NewLine', 'GroupEnd', 'StatementSeparator') -and
                        $Tokens[$i].EndLine -eq $Tokens[$i + 1].StartLine -and
                        $Tokens[$i + 1].StartColumn - $Tokens[$i].EndColumn -gt 0) {$NewScriptText += ' '}
                    $SkipNext = $Tokens[$i].Type -eq 'GroupStart' -and $Tokens[$i + 1].Type -in ('NewLine', 'StatementSeparator')
                } Else {$SkipNext = $SkipNext -and $Tokens[$i + 1].Type -in ('NewLine', 'StatementSeparator')}
            }
        }
        If ($Tokens) {If ($Tokens[$i].Type -in ('String', 'Variable')) {$NewScriptText += $OldScript.Substring($Tokens[-1].Start, $Tokens[-1].Length)} Else {$NewScriptText += $Tokens[-1].Content}}
        $NewScriptText = $NewScriptText.TrimStart("`n`r;")
        If ($Items.Count -eq 1) {If ($Items[0] -is [scriptblock]) {return [scriptblock]::Create($NewScriptText)} Else {return $NewScriptText}} Else {return $NewScriptText.Split("`n`r", [System.StringSplitOptions]::RemoveEmptyEntries)}
    }
}
function Remove-Directory {
    [CmdletBinding()]
    param ([string] $Dir)
    if (-not [string]::IsNullOrWhiteSpace($Dir)) {
        $exists = Test-Path -Path $Dir
        if ($exists) {Remove-Item $dir -Confirm:$false -Recurse} else {}
    }
}
$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
        }
        PSUseCorrectCasing = @{Enable = $true}
    }
}
function Set-LinkedFiles {
    [CmdletBinding()]
    param($LinkFiles,
        $FullModulePath,
        $FullProjectPath,
        [switch] $Delete)
    foreach ($file in $LinkFiles) {
        $Path = "$FullModulePath\$file"
        $Path2 = "$FullProjectPath\$file"
        if ($Delete) {
            if (Test-ReparsePoint -path $Path) {
                Write-Verbose "Removing symlink first $path"
                Remove-Item $Path -Confirm:$false
            }
        }
        Write-Verbose "Creating symlink from $path2 (source) to $path (target)"
        $linkingFiles = cmd /c mklink $path $path2
    }
}
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 scriptBlock
        Determines if a string or a scriptblock is returned
    .Example
        # Corrects the presentation of a PowerShell hashtable
        @{Foo='Bar';Baz='Bing';Boo=@{Bam='Blang'}} | Write-PowerShellHashtable
    .Outputs
        [string]
    .Outputs
        [ScriptBlock]
    .Link
        https://github.com/StartAutomating/Pipeworks
        about_hash_tables
    #>

    [OutputType([string], [ScriptBlock])]
    param([Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [PSObject]
        $InputObject,
        [Alias('ScriptBlock')]
        [switch]$AsScriptBlock,
        [Switch]$Sort)
    process {
        $callstack = @(foreach ($_ in (Get-PSCallStack)) {if ($_.Command -eq "Write-PowerShellHashtable") {$_}})
        $depth = $callStack.Count
        if ($inputObject -isnot [Hashtable]) {$newInputObject = @{PSTypeName = @($inputobject.pstypenames)[-1]}
            foreach ($prop in $inputObject.psobject.properties) {$newInputObject[$prop.Name] = $prop.Value}
            $inputObject = $newInputObject
        }
        if ($inputObject -is [Hashtable]) {
            $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 -and $value.GetType -and ($value.GetType().IsArray -or $value -is [Collections.IList])) {
                    $value = foreach ($v in $value) {if ($v -is [Hashtable]) {Write-PowerShellHashtable $v} elseif ($v -is [Object] -and $v -isnot [string]) {Write-PowerShellHashtable $v} else {("'" + "$v".Replace("'", "''").Replace("’", "’’").Replace("‘", "‘‘") + "'")}}
                    $oldOfs = $ofs
                    $ofs = ",$(' ' * ($indent + 4))"
                    $value = "$value"
                    $ofs = $oldOfs
                } elseif ($value -as [Hashtable[]]) {
                    $value = foreach ($v in $value) {Write-PowerShellHashtable $v}
                    $value = $value -join ","
                } elseif ($value -is [Hashtable]) {$value = "$(Write-PowerShellHashtable $value)"} 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} else {$value = $valueString}
                }
                $scriptString += "$value
"

            }
            $scriptString += " " * ($depth - 1) * 4
            $scriptString += "}"
            if ($AsScriptBlock) {[ScriptBlock]::Create($scriptString)} else {$scriptString}
        }
    }
}
Export-ModuleMember -Function @('New-PrepareModule', 'Remove-Comments') -Alias @()