PSPublishModule.psm1

function Add-Directory {
    [CmdletBinding()]
    param(
        $dir
    )

    $exists = Test-Path -Path $dir
    if ($exists -eq $false) {
        #Write-Color 'Creating directory ', $dir -Color White, Yellow
        $createdDirectory = mkdir $dir
    } else {
        #Write-Color 'Creating directory ', $dir, ' skipped.' -Color White, Yellow, Red
    }
}
function Add-FilesWithFolders {
    [CmdletBinding()]
    param ($file, $FullProjectPath, $directory)
    $LinkPrivatePublicFiles = @()
    #$path = $file.FullName.Replace("$FullProjectPath\", '')
    $path = $file
    foreach ($dir in $directory) {
        if ($path -like "$dir*") {
            $LinkPrivatePublicFiles += $path
            Write-Verbose "Adding file to linking list of files $path"
            # Write-Color 'Adding file to ', 'linking list', ' of files ', $path -Color White, Yellow, White, Yellow

        }
    }
    return $LinkPrivatePublicFiles
}
function Add-FilesWithFoldersNew {
    [CmdletBinding()]
    param($File, $FullProjectPath, $directory)

    <#
    $LinkPrivatePublicFiles = @()
    $path = $file.FullName.Replace("$FullProjectPath\", '')
    foreach ($dir in $directory) {
        if ($path.StartsWith($dir)) {
            $LinkPrivatePublicFiles += $path
            Write-Color 'Adding file to ', 'linking list', ' of files ', $path -Color White, Yellow, White, Yellow
 
        }
    }
    return $LinkPrivatePublicFiles
 
    #>

}
function Add-ObjectTo {
    [CmdletBinding()]
    param($Object, $Type)
    #Write-Color 'Adding ', $Object.Name, ' to ', $Type -Color White, Green, White, Yellow
    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(
    # 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
    )
    begin {
        $AllObjects = New-Object Collections.ArrayList
    }

    process {
        $null = $AllObjects.AddRange($InputObject)
    }

    end {
        #region Convert to Hashtables and export
        $text = $AllObjects |
            Write-PowerShellHashtable

        $text |
            Set-Content -Path $DataFile
        Get-Item -Path $DataFile
        #endregion Convert to Hashtables and export
    }

}
function Find-EnumsList {
    [CmdletBinding()]
    param (
        [string] $ProjectPath
    )

    $Enums = @( Get-ChildItem -Path $ProjectPath\Enums\*.ps1 -ErrorAction SilentlyContinue )
    #Write-Verbose "Find-EnumsList - $ProjectPath\Enums"

    $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
}
#using Namespace System.Management.Automation.Language

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
            }
        }
    }
}

#Get-AliasTarget -Path 'C:\Support\GitHub\PSSharedGoods\Public\Objects\Format-Stream.ps1' | Select-Object -ExpandProperty Alias


#Get-FunctionAliases -Path 'C:\Support\GitHub\PSSharedGoods\Public\Objects\Format-Stream.ps1'
#$Path = 'C:\Support\GitHub\PSSharedGoods\Public\Objects\Format-Stream.ps1'
#Get-FunctionAliases -Path $path
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}
    }
    #$MyAliases = foreach ($Alias in $Aliases) {
    # if ($Alias -ne '') {
    # $Alias.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-FunctionAliases -Path $File.FullName
            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 {
        #Begin scriptblock
        Write-Verbose -Message "Starting $($MyInvocation.Mycommand)"
        #initialization commands
        #explicitly define some AST variables
        New-Variable astTokens -force
        New-Variable astErr -force
    } #close begin

    Process {
        #Process scriptblock
        #convert each path to a nice filesystem path
        $Path = Convert-Path -Path $Path

        Write-Verbose -Message "Parsing $Path"
        #Parse the file
        $ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$astTokens, [ref]$astErr)

        #filter tokens for comments and display text
        $asttokens.where( {$_.kind -eq 'comment'}) | Select-Object -ExpandProperty Text
        $ast
    } #close process

    End {
        #end scriptblock

        #ending the function
        Write-Verbose -Message "Ending $($MyInvocation.Mycommand)"
    } #close end

} #close function
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
    )
    $ScriptFunctions = @( Get-ChildItem -Path $ModulePathSource\*.ps1 -ErrorAction SilentlyContinue -Recurse )
    # $ModulePSM = @( Get-ChildItem -Path $ModulePathSource\*.psm1 -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
        $Content = $Content.Replace('$PSScriptRoot\', '$PSScriptRoot\')
        $Content = $Content.Replace('$PSScriptRoot\', '$PSScriptRoot\')
        $Content | Add-Content $ModulePathTarget\$ModuleName.psm1
    }

    New-PSMFile -Path $ModulePathTarget\$ModuleName.psm1 `
        -FunctionNames $FunctionsToExport `
        -FunctionAliaes $AliasesToExport `
        -LibrariesCore $LibrariesCore `
        -LibrariesDefault $LibrariesDefault

    <#
    foreach ($FilePath in $ScriptFunctions) {
        $Results = [System.Management.Automation.Language.Parser]::ParseFile($FilePath, [ref]$null, [ref]$null)
        $Functions = $Results.EndBlock.Extent.Text
        $Functions | Add-Content -Path "$ModulePathTarget\$ModuleName.psm1"
    }
 
    foreach ($FilePath in $ModulePSM) {
        $Content = Get-Content $FilePath
        $Content | Add-Content -Path "$ModulePathTarget\$ModuleName.psm1"
    }
    #>

    #Copy-Item -Path "$ModulePathSource\$ModuleName.psd1" "$ModulePathTarget\$ModuleName.psd1"
    New-PersonalManifest -Configuration $Configuration -ManifestPath "$ModulePathTarget\$ModuleName.psd1"
}
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
    #Update-ModuleManifest @Manifest

    if ($Configuration.Information.Versioning.Prerelease -ne '') {
        #$FilePathPSD1 = Get-Item -Path $Configuration.Information.Manifest.Path
        $Data = Import-PowerShellDataFile -Path $Configuration.Information.Manifest.Path
        $Data.PrivateData.PSData.Prerelease = $Configuration.Versioning.Prerelease
        $Data | Export-PSData -DataFile $Configuration.Information.Manifest.Path

    }

    Write-Verbose "Converting $($Configuration.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 (
        [string] $ProjectName,
        [string] $ProjectPath,
        [string] $ModulePath,
        [string] $DeleteModulePath,
        [System.Collections.IDictionary] $Configuration
    )
    Begin {

        if ($Configuration) {
            $FullModulePath = [IO.path]::Combine($Configuration.Information.DirectoryModules, $Configuration.Information.ModuleName)
            $FullModulePathDelete = [IO.path]::Combine($Configuration.Information.DirectoryModules, $Configuration.Information.ModuleName)
            $FullTemporaryPath = [IO.path]::GetTempPath() + '' + $Configuration.Information.ModuleName
            $FullProjectPath = [IO.Path]::Combine($Configuration.Information.DirectoryProjects, $Configuration.Information.ModuleName)
            $ProjectName = $Configuration.Information.ModuleName
        } else {
            $FullModulePath = "$modulePath\$projectName"
            $FullProjectPath = "$projectPath\$projectName"
            $FullModulePathDelete = "$DeleteModulePath\$projectName"
            $FullTemporaryPath = [IO.path]::GetTempPath() + '' + $ProjectName
        }
        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 = @()

    }
    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) {
                    #Write-Verbose "New-PrepareModule - RelativeDirectoryPath: $RelativeDirectoryPath LookupDir: $LookupDir\"
                    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 = @()
            # Link only files in Root Directory
            foreach ($File in $RootFiles) {
                switch -Wildcard ($file) {
                    '*.psd1' {
                        #Write-Color $File -Color Red
                        $LinkFilesRoot += Add-ObjectTo -Object $File -Type 'Root Files List'
                    }
                    '*.psm1' {
                        # Write-Color $File.FulllName -Color Red
                        $LinkFilesRoot += Add-ObjectTo -Object $File -Type 'Root Files List'
                    }
                    'License*' {
                        $LinkFilesRoot += Add-ObjectTo -Object $File -Type 'Root Files List'
                    }
                }
            }

            # Link only files from subfolers
            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.Use) {
                        $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
                        }
                    }
                }

                New-PersonalManifest -Configuration $Configuration -ManifestPath $FullProjectPath\$ProjectName.psd1 -AddScriptsToProcess
            }
       
            if ($Configuration.Options.Merge.Use) {
                foreach ($Directory in $LinkDirectories) {
                    $Dir = "$FullTemporaryPath\$Directory"
                    Add-Directory $Dir
                }
                # Workaround to link files that are not ps1/psd1
                $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

                # Workaround to link files that are not ps1/psd1
                $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) }
                    #$FilesLibrariesCore
                }
                if (-not [string]::IsNullOrWhiteSpace($Configuration.Information.LibrariesDefault)) {
                    $StartsWithDefault = "$($Configuration.Information.LibrariesDefault)\"
                    $FilesLibrariesDefault = $LinkPrivatePublicFiles | Where-Object { ($_).StartsWith($StartsWithDefault) }
                    #$FilesLibrariesDefault
                }

                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

            } 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
                #Set-LinkedFiles -LinkFiles $LinkFilesSpecial -FullModulePath $PrivateProjectPath -FullProjectPath $AddPrivate -Delete
                #Set-LinkedFiles -LinkFiles $LinkFiles -FullModulePath $FullModulePath -FullProjectPath $FullProjectPath
            }
        }
        if ($Configuration.Steps.PublishModule) {
            if ($Configuration.Options.PowerShellGallery.FromFile) {
                $ApiKey = Get-Content -Path $Configuration.Options.PowerShellGallery.ApiKey
                New-PublishModule -ProjectName $Configuration.Information.ModuleName -ApiKey $ApiKey
            } else {
                New-PublishModule -ProjectName $Configuration.Information.ModuleName -ApiKey $Configuration.Options.PowerShellGallery.ApiKey
            }
        }

        if ($Configuration.Publish.Use) {
            New-PublishModule -ProjectName $Configuration.Information.ModuleName -ApiKey $Configuration.Publish.ApiKey
        }
    }
    end {
        # Revers Path to current locatikon
        Set-Location -Path $CurrentLocation

        # Import Modules Section
        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)) {
                    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 #-Verbose
                } else {
                    $null = New-MarkdownHelp -Module $ProjectName -WithModulePage -OutputFolder $DocumentationPath #-ModuleName $ProjectName #-ModulePagePath $ReadMePath
                    $null = Move-Item -Path "$DocumentationPath\$ProjectName.md" -Destination $ReadMePath
                    #Start-Sleep -Seconds 1
                    # this is temporary workaround - due to diff output on update
                    if ($Configuration.Options.Documentation.UpdateWhenNew) {
                        $null = Update-MarkdownHelpModule $DocumentationPath -RefreshModulePage -ModulePagePath $ReadMePath #-Verbose
                    }
                    #
                }
            }
        }

    }
}
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($projectName, $apikey) {
    Publish-Module -Name $projectName -Repository PSGallery -NuGetApiKey $apikey -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

    # Make sure that the .NET framework sees the same working dir. as PS
    # and resolve the input path to a full path.
    [System.IO.Directory]::SetCurrentDirectory($PWD) # Caveat: .NET Core doesn't support [Environment]::CurrentDirectory
    $LiteralPath = [IO.Path]::GetFullPath($LiteralPath)

    # If -NoClobber was specified, throw an exception if the target file already
    # exists.
    if ($NoClobber -and (Test-Path $LiteralPath)) {
      Throw [IO.IOException] "The file '$LiteralPath' already exists."
    }

    # Create a StreamWriter object.
    # Note that we take advantage of the fact that the StreamWriter class by default:
    # - uses UTF-8 encoding
    # - without a BOM.
    $sw = New-Object IO.StreamWriter $LiteralPath, $Append

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

    # Note: By not using begin / process / end blocks, we're effectively running
    # in the end block, which means that all pipeline input has already
    # been collected in automatic variable $Input.
    # We must use this approach, because using | Out-String individually
    # in each iteration of a process block would format each input object
    # with an indvidual header.
    try {
      $Input | Out-String -Stream @htOutStringArgs | % { $sw.WriteLine($_) }
    } finally {
      $sw.Dispose()
    }

  }
function Remove-Directory {
    [CmdletBinding()]
    param (
        [string] $Dir
    )
    if (-not [string]::IsNullOrWhiteSpace($Dir)) {
        $exists = Test-Path -Path $Dir
        if ($exists) {
            #Write-Color 'Removing directory ', $dir -Color White, Yellow
            Remove-Item $dir -Confirm:$false -Recurse
        } else {
            #Write-Color 'Removing directory ', $dir, ' skipped.' -Color White, Yellow, Red
        }
    }
}
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-Color 'Removing symlink first ', $path -Color White, Yellow
                Write-Verbose "Removing symlink first $path"
                Remove-Item $Path -Confirm:$false
            }

        }
        Write-Verbose "Creating symlink from $path2 (source) to $path (target)"
        #Write-Color 'Creating symlink from ', $path2, ' (source) to ', $path, ' (target)' -Color White, Yellow, White, Yellow, White
        $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,

    # 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 [Hashtable]) {

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

        if ($inputObject -is [Hashtable]) {
            #region Indent
            $scriptString = ""
            $indent = $depth * 4
            $scriptString+= "@{
"

            #endregion Indent
            #region Include
            $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
                # Write-Verbose "$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
            }
            #endregion Include
        }
   }
}


Export-ModuleMember `
    -Function @('New-PrepareManifest','New-PrepareModule') `
    -Alias @()