PSBuild.psm1

#region Private Functions

function priv_Export-PSArtefact
{
    [CmdletBinding()]
    [OutputType([void])]
    param
    (
        #SourcePath
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_Default')]
        [ValidateScript( {
                if (-not (test-path $_))
                {
                    throw "$_ does not exist"
                }
                else
                {
                    $true
                }
            })]
        [string]$SourcePath,

        #DestinationPath
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_Default')]
        [string]$DestinationPath,

        #ModuleVersion
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [string]$Version,

        #Type
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_Default')]
        [ValidateSet('Script', 'Module')]
        [string]$Type
    )

    Process
    {
        switch ($Type)
        {
            'Script'
            {
                #Check if Source and Destination are the same
                $SourceFolder = Split-Path -Path $SourcePath -Parent -ErrorAction Stop
                if ($SourceFolder -eq $DestinationPath)
                {
                    #Do nothing
                }
                else
                {
                    if (-not (Test-Path $DestinationPath))
                    {
                        $null = New-Item -Path $DestinationPath -ItemType Directory -ErrorAction Stop
                    }
                    $null = Copy-Item -Path $SourcePath -Destination $DestinationPath -Force -ErrorAction Stop
                }

                break
            }

            'Module'
            {
                $ModuleFolder = New-Object -TypeName System.IO.DirectoryInfo -ArgumentList $SourcePath
                $ObjectName = $ModuleFolder.BaseName
                #Calculcate DestinationFolder
                if ($PSBoundParameters.ContainsKey('Version'))
                {
                    $ObjectDestinationFolderTemp = Join-Path -Path $DestinationPath -ChildPath $ObjectName -ErrorAction Stop
                    $ObjectDestinationFolder = Join-Path -Path $ObjectDestinationFolderTemp -ChildPath $Version -ErrorAction Stop
                }
                else
                {
                    $ObjectDestinationFolder = Join-Path -Path $DestinationPath -ChildPath $ObjectName -ErrorAction Stop
                }

                #Check if Source and Destination are the same
                $SourceFolder = Split-Path -Path $SourcePath -Parent -ErrorAction Stop
                if ($SourceFolder -eq $ObjectDestinationFolder)
                {
                    #Do nothing
                }
                else
                {
                    #Validate DestinationFolder
                    if (Test-Path -Path $ObjectDestinationFolder)
                    {
                        Remove-Item -Path $ObjectDestinationFolder -ErrorAction Stop -Force -Confirm:$false -Recurse
                    }
                    $null = New-Item -Path $ObjectDestinationFolder -ItemType Directory -ErrorAction Stop

                    #Copy Module content
                    $ModuleFilesToExclude = @(
                        'obj'
                        'bin'
                        'CSharpAssemblies'
                        '*.pssproj'
                        '*.pssproj.user'
                        '*.csproj'
                        '*.csproj.user'
                        '*.vspscc'
                        '*.pdb'
                        '*.cs'
                        '*.tests.ps1'
                        'packages.config'
                        'Tests'
                        'System.Management.Automation.dll'
                    )
                    $null = Copy-Item -Path "$SourcePath\*" -Destination $ObjectDestinationFolder -Recurse -Exclude $ModuleFilesToExclude
                }
                break
            }

            default
            {
                throw "Unknown Type: $Type"
            }
        }
    }
}

function priv_Publish-PSModule
{
    [CmdletBinding()]
    [OutputType([void])]
    param
    (
        #ModuleInfo
        [Parameter(Mandatory = $true)]
        [PSModuleInfo]$ModuleInfo,

        #Credential
        [Parameter(Mandatory = $false)]
        [PScredential]$Credential,

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

        #NuGetApiKey
        [Parameter(Mandatory = $false)]
        [string]$NuGetApiKey,

        #Force
        [Parameter(Mandatory = $false)]
        [switch]$Force,

        #PublishDependantModules
        [Parameter(Mandatory = $false)]
        [switch]$PublishDependantModules = $true,

        #VerbosePrefix
        [Parameter(Mandatory = $false)]
        [string]$VerbosePrefix,
        
        #Proxy
        [Parameter(Mandatory = $false)]
        [uri]$Proxy
    )

    Process
    {
        #Resolve ModuleRootFolder
        try
        {
            $ModuleLeafFolder = Split-Path -Path $ModuleInfo.ModuleBase -Leaf
            $ModuleVersion = New-Object System.Version
            if ([System.Version]::TryParse($ModuleLeafFolder, [ref]$ModuleVersion))
            {
                $ModuleVersionFolder = Split-Path -Path $ModuleInfo.ModuleBase -Parent
                $ModuleRootFolder = Split-Path -Path $ModuleVersionFolder -Parent
            }
            else
            {
                $ModuleRootFolder = Split-Path -Path $ModuleInfo.ModuleBase -Parent
            }
        }
        catch
        {
            Write-Error "Resolve ModuleRootFolder failed. Details: $_" -ErrorAction 'Stop'
        }
        
        #Publish RequiredModules
        $ModsToPublish = $ModuleInfo.RequiredModules | Where-Object { $ModuleInfo.PrivateData.PSData.ExternalModuleDependencies -notcontains $_.Name }
        foreach ($ReqModule in $ModsToPublish)
        {
            $ReqModuleFound = $false
            #Check if Required Module is present in the same folder as the current module
            try
            {
                $ReqModuleInfo = Get-Module -ListAvailable -FullyQualifiedName "$ModuleRootFolder\$($ReqModule.Name)" -Refresh -ErrorAction Stop
                if ($ReqModuleInfo)
                {
                    #If multiple versions are available, select latest one.
                    $ReqModuleInfo = $ReqModuleInfo | Sort-Object -Property Version | select -Last 1
                    if ($ReqModule.Version -and ($ReqModule.Version -le $ReqModuleInfo.Version))
                    {
                        $ReqModuleFound = $true
                    }
                    else
                    {
                        $ReqModuleFound = $true
                    }
                }
            }
            catch
            {

            }

            #Check if Required Module is present on the machine
            if (-not $ReqModuleFound)
            {
                try
                {
                    $ReqModuleInfo = Get-Module -Name $ReqModule.Name -ErrorAction Stop
                    if ($ReqModuleInfo)
                    {
                        #If multiple versions are available, select latest one.
                        $ReqModuleInfo = $ReqModuleInfo | Sort-Object -Property Version | select -Last 1
                        if ($ReqModule.Version -and ($ReqModule.Version -le $ReqModuleInfo.Version))
                        {
                            $ReqModuleFound = $true
                        }
                        else
                        {
                            $ReqModuleFound = $true
                        }
                    }
                }
                catch
                {

                }
            }

            if ($ReqModuleFound)
            {
                $PublishModuleAndDependacies_Params = @{ } + $PSBoundParameters
                $PublishModuleAndDependacies_Params['ModuleInfo'] = $ReqModuleInfo
                priv_Publish-PSModule @PublishModuleAndDependacies_Params
            }
            else
            {
                throw "Unable to find Required Module: $($ReqModule.Name)"
            }
        }

        #Publish Module
        try
        {
            $PublishModule_CommonParams = @{
                Repository = $Repository
            }

            if ($PSBoundParameters.ContainsKey('Credential'))
            {
                $PublishModule_CommonParams.Add('Credential', $Credential)
            }
            if ($PSBoundParameters.ContainsKey('Proxy'))
            {
                $PublishModule_CommonParams.Add('Proxy', $Proxy)
            }

            #Check if module already exist on the Repository
            try
            {
                Remove-Variable -Name ModExist -ErrorAction SilentlyContinue
                $private:ModExist = Find-Module @PublishModule_CommonParams -Name $ModuleInfo.Name -RequiredVersion $ModuleInfo.Version -ErrorAction Stop
            }
            catch
            {

            }
            
            if ($ModExist)
            {
                Write-Verbose "$VerbosePrefix`Module already exist on the PSGetRepo"
            }
            else
            {
                $PublishModule_Params = @{
                    Path = $ModuleInfo.ModuleBase
                } + $PublishModule_CommonParams
                if ($PSBoundParameters.ContainsKey('NuGetApiKey'))
                {
                    $PublishModule_Params.Add('NuGetApiKey', $NuGetApiKey)
                }
                Write-Verbose "$VerbosePrefix`Publishing new version"
                Publish-Module @PublishModule_Params -Force -ErrorAction Stop
            }

        }
        catch
        {
            Write-Error "Publish Module failed. Details: $_" -ErrorAction 'Stop'
        }
    }
}

function priv_Analyse-ItemDependancies
{
    [CmdletBinding()]
    param
    (
        #ScriptBlock
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_ScriptBlock')]
        [scriptblock]$ScriptBlock,

        #ModulePath
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_Module')]
        [string]$ModulePath,

        #ScriptPath
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_Script')]
        [string]$ScriptPath,

        #GlobalCommandAnalysis
        [Parameter(Mandatory = $true)]
        [ref]$GlobalCommandAnalysis,

        #PSGetRepository
        [Parameter(Mandatory = $false)]
        [hashtable[]]$PSGetRepository,

        #CurrentDependancies
        [Parameter(Mandatory = $false)]
        [string[]]$CurrentDependancies,

        #Proxy
        [Parameter(Mandatory = $false)]
        [uri]$Proxy
    )
    
    Process
    {
        #Construct JobParams
        $JobParams = @{
            GlobalCommandAnalysisAsJson = $GlobalCommandAnalysis.Value.ToJson()
            CurrentDependancies         = $CurrentDependancies
            PSGetRepository             = $PSGetRepository
            PSBuildDllPath              = [System.AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq 'PSBuildEntities' } | select -ExpandProperty Location
        }
        if ($PSBoundParameters.ContainsKey('Proxy'))
        {
            $JobParams.Add('Proxy', $Proxy)
        }
        Switch ($PSCmdlet.ParameterSetName)
        {
            'NoRemoting_ScriptBlock'
            {
                $JobParams.Add('ScriptBlock', $ScriptBlock)
                break
            }
            'NoRemoting_Module'
            {
                $JobParams.Add('ModulePath', $ModulePath)
                break
            }
            'NoRemoting_Script'
            {
                $JobParams.Add('ScriptPath', $ScriptPath)
                break
            }
            default
            {
                throw "Unknown ParameterSet: $_"
            }
        }

        try
        {
            $Job = Start-Job -ScriptBlock {
                #Wait-Debugger
                $JobParams = $Using:JobParams
                Add-Type -Path $JobParams['PSBuildDllPath'] -ErrorAction Stop
                $GlobalCommandAnalysis = [PSBuildEntities.AnalysisResultCollection]::FromJson($JobParams['GlobalCommandAnalysisAsJson'])
                $LocalCommandAnalysis = [PSBuildEntities.AnalysisResultCollection]::New()
            
                #Get Module ScriptBlockAst
                if ($JobParams.ContainsKey('ModulePath'))
                {
                    $OldErrorActionPreference = $ErrorActionPreference
                    $OldVerbosePreference = $VerbosePreference
                    $OldWarningPreference = $WarningPreference
                    $ErrorActionPreference = 'Continue'
                    $VerbosePreference = 'SilentlyContinue'
                    $WarningPreference = 'SilentlyContinue'
                    $ModFresh = Import-Module -FullyQualifiedName $JobParams['ModulePath'] -PassThru -ErrorAction Stop -WarningAction SilentlyContinue -Verbose:$false -ErrorVariable er
                    $ErrorActionPreference = $OldErrorActionPreference
                    $VerbosePreference = $OldVerbosePreference
                    $WarningPreference = $OldWarningPreference
                    if (-not $ModFresh)
                    {
                        throw "Unable to Import Module: $($ModuleValidationCache.Value[$moduleName].ModuleInfo.ModuleBase). Details: $er"
                    }
                    $ModuleSb = [scriptblock]::Create($ModFresh.Definition)
                    $ScriptBlockAst = $ModuleSb.Ast
                }
                #Get Script ScriptBlockAst
                elseif ($JobParams.ContainsKey('ScriptPath'))
                {
                    $ScriptContent = Get-Command -Name $JobParams['ScriptPath'] -ErrorAction Stop -Verbose:$false
                    $ScriptBlockAst = $ScriptContent.ScriptBlock.Ast
                }
                #GetScriptBlockAst
                elseif ($JobParams.ContainsKey('ScriptBlock'))
                {
                    $ScriptBlockAst = $ScriptBlock.Ast
                }
                else
                {
                    throw "Unknown ParameterSet"
                }

                #Identify NonLocal commands
                try
                {
                    $LocalCommands = Get-AstStatement -Ast $ScriptBlockAst -Type FunctionDefinitionAst | Select-Object -ExpandProperty Name
                    $NonLocalCommands = Get-AstStatement -Ast $ScriptBlockAst -Type CommandAst | ForEach-Object { $_.GetCommandName() } | Group-Object -NoElement | Select-Object -ExpandProperty Name | Where-Object { $LocalCommands -notcontains $_ }
                }
                catch
                {
                    Write-Error "Identify NonLocal commands failed. Details: $_" -ErrorAction 'Stop'
                }

                #Resolve NonLocal commands against GlobalCommandAnalysis
                try
                {
                    foreach ($cmd in $NonLocalCommands)
                    {
                        $CmdResult = [PSBuildEntities.AnalysisResult]::new()
                        #Determine CmdName and Module
                        if ($cmd.Contains('\'))
                        {
                            $tempcmd = $cmd -split '\\'
                            $CmdResult.CommandSource = $tempcmd[0]
                            $CmdResult.CommandName = $tempcmd[1]
                        }
                        else
                        {
                            $CmdResult.CommandName = $cmd
                        }

                        #Check if command is already analyzed
                        if (-not $LocalCommandAnalysis.Contains($CmdResult.CommandName))
                        {
                            #Resolve command Source
                            if ($GlobalCommandAnalysis.Contains($CmdResult.CommandName))
                            {
                                #Source already resolved
                                $CmdResult = $GlobalCommandAnalysis[$CmdResult.CommandName]
                                $CmdResult.IsReferenced = $false
                                $CmdResult.IsFound = $true
                            }
                            else
                            {
                                #Check if command is local
                                if (-not $CmdResult.IsFound)
                                {
                                    try
                                    {
                                        $GetCommandParams = @{
                                            Name = $CmdResult.CommandName 
                                        }
                                        if ($CmdResult.CommandSource)
                                        {
                                            $SourceCandidate = $CmdResult.CommandSource
                                        }
                                        else
                                        {
                                            if ($JobParams.ContainsKey('CurrentDependancies'))
                                            {
                                                $SourceCandidate = $JobParams['CurrentDependancies']
                                            }
                                            else
                                            {
                                                Remove-Variable -Name SourceCandidate -ErrorAction SilentlyContinue
                                            }
                                        }
                                        if (-not [string]::IsNullOrEmpty($CmdResult.CommandSource))
                                        {
                                            $GetCommandParams.Add('Module', $CmdResult.CommandSource)
                                        }
                                        try
                                        {
                                            Remove-Variable -Name TempFindng -ErrorAction SilentlyContinue
                                            $TempFindng = Get-Command @GetCommandParams -ErrorAction Stop -Verbose:$false
                                            if ((-not $TempFindng) -and ($SourceCandidate))
                                            {
                                                $TempFindng = Get-Command @GetCommandParams -Module $SourceCandidate -ErrorAction Stop -Verbose:$false
                                            }
                                        }
                                        catch
                                        {
                                        }
                                        if ($TempFindng)
                                        {
                                            $CmdResult.CommandType = $TempFindng.CommandType
                                            if ($TempFindng.CommandType -eq 'Alias')
                                            {
                                                $TempFinding2 = Get-Command -Name $TempFindng.Name -ErrorAction Stop -Verbose:$false
                                                $CmdResult.CommandSource = $TempFinding2.Source
                                            }
                                            else
                                            {
                                                $CmdResult.CommandSource = $TempFindng.Source
                                            }

                                            $CmdResult.IsFound = $true
                                        }
                                    }
                                    catch
                                    {
                                        Write-Error "Check if command is local failed. Details: $_" -ErrorAction 'Stop'
                                    }
                                }
                            }

                            $null = $LocalCommandAnalysis.Add($CmdResult)
                        }
                    }
                }
                catch
                {
                    Write-Error "Resolve NonLocal Commands against CommandToModuleMapping failed. Details: $_" -ErrorAction Stop
                }

                #Resolve Missing commands against Proget
                if ($JobParams.ContainsKey('PSGetRepository'))
                {
                    try
                    {
                        $MissingCommandsAnalysis = $LocalCommandAnalysis | Where-Object { $_.IsFound -eq $false }
                        if ($MissingCommandsAnalysis)
                        {
                            $AssertPSRepository_Params = @{
                                PSGetRepository=$JobParams['PSGetRepository']
                            }
                            if ($JobParams.ContainsKey('Proxy'))
                            {
                                $AssertPSRepository_Params.Add('Proxy',$JobParams['Proxy'])
                            }
                            Assert-PSRepository @AssertPSRepository_Params -ErrorAction Stop

                            foreach ($Repo in $JobParams['PSGetRepository'])
                            {
                                #Refresh MissingCommandsAnalysis
                                $MissingCommandsAnalysis = $LocalCommandAnalysis | Where-Object { $_.IsFound -eq $false }
                                if ($MissingCommandsAnalysis)
                                {
                                    #Search for All MissingCommands
                                    $FindModule_Params = @{
                                        Command    = $MissingCommandsAnalysis.CommandName
                                        Repository = $Repo.Name
                                    }
                                    if ($Repo.ContainsKey('Credential'))
                                    {
                                        $FindModule_Params.Add('Credential', $Repo.Credential)
                                    }
                                    if ($JobParams.ContainsKey('Proxy'))
                                    {
                                        $FindModule_Params.Add('Proxy', $JobParams['Proxy'])
                                    }
                                    Remove-Variable -Name TempNugetResult -ErrorAction SilentlyContinue
                                    $private:TempNugetResult = Find-Module @FindModule_Params -ErrorAction Stop

                                    #Map search results to MissingCommands
                                    if ($TempNugetResult)
                                    {
                                        foreach ($MissingCmd in $MissingCommandsAnalysis)
                                        {
                                            $MissingCmdModule = $TempNugetResult | Where-Object { $_.Includes.Command -contains $MissingCmd.CommandName }
                                            if ($MissingCmdModule)
                                            {
                                                $LocalCommandAnalysis[$MissingCmd.CommandName].CommandSource = $MissingCmdModule.Name -join ','
                                                $LocalCommandAnalysis[$MissingCmd.CommandName].CommandType = 'Command'
                                                $LocalCommandAnalysis[$MissingCmd.CommandName].SourceLocation = [PSBuildEntities.AnalysisResultSourceLocation]::ProGet
                                                $LocalCommandAnalysis[$MissingCmd.CommandName].IsFound = $true
                                            }
                                        }
                                    }
                                }
                            }

                        }
                    }
                    catch
                    {
                        Write-Error "Resolve Missing commands against Proget failed. Details: $_" -ErrorAction Stop
                    }
                }

                #Check commands references
                try
                {
                    foreach ($Cmd in ($LocalCommandAnalysis | Where-Object { $_.IsFound }))
                    {
                        if ($JobParams['CurrentDependancies'] -contains $cmd.CommandSource)
                        {
                            $cmd.IsReferenced = $true
                        }
                    }
                }
                catch
                {
                    Write-Error "Check commands references failed. Details: $_" -ErrorAction Stop
                }

                #Update GlobalCommandAnalysis
                try
                {
                    $FoundCommandsAnalysis = $LocalCommandAnalysis | Where-Object { $_.IsFound -eq $true }
                    foreach ($FoundCmd in $FoundCommandsAnalysis)
                    {
                        if (-not $GlobalCommandAnalysis.Contains($FoundCmd.CommandName))
                        {
                            $null = $GlobalCommandAnalysis.Add($FoundCmd)
                        }
                    }
                }
                catch
                {
                    Write-Error "Update GlobalCommandAnalysis failed. Details: $_" -ErrorAction Stop
                }

                #Return result
                [pscustomobject]@{
                    LocalCommandAnalysisAsJson  = $LocalCommandAnalysis.ToJson()
                    GlobalCommandAnalysisAsJson = $GlobalCommandAnalysis.ToJson()
                }
            }
            $null = Wait-Job -Job $Job
            $JobResult = Receive-Job -Job $Job
        }
        finally
        {
            Remove-Job -Job $Job -Force
        }

        #Update GlobalCommandAnalysis
        $GlobalCommandAnalysis.Value = [PSBuildEntities.AnalysisResultCollection]::FromJson($JobResult.GlobalCommandAnalysisAsJson)
        [PSBuildEntities.AnalysisResultCollection]::FromJson($JobResult.LocalCommandAnalysisAsJson)
    }
}

function priv_Validate-Module
{
    [CmdletBinding()]
    param
    (
        #SourcePath
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_Default')]
        [System.IO.DirectoryInfo[]]$SourcePath,

        #ModuleValidation
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_Default')]
        [ref]$ModuleValidationCache
    )
    
    Process
    {
        foreach ($Module in $SourcePath)
        {
            $moduleName = $Module.Name
            if (-not $ModuleValidationCache.Value.ContainsKey($moduleName))
            {
                $null = $ModuleValidationCache.Value.Add($moduleName, (Test-PSModule -ModulePath $Module.FullName -ErrorAction Stop))
            }
        }
    }
}

#endregion

#region Public Functions

function Build-PSModule
{
    [CmdletBinding()]
    [OutputType([void])]
    param
    (
        #SourcePath
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_Default')]
        [System.IO.DirectoryInfo[]]$SourcePath,

        #DestinationPath
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_Default')]
        [System.IO.DirectoryInfo]$DestinationPath,

        #ResolveDependancies
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [switch]$ResolveDependancies,

        #CheckCommandReferences
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [switch]$CheckCommandReferences,

        #CheckCommandReferencesConfiguration
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [psobject]$CheckCommandReferencesConfiguration,

        #CheckDuplicateCommandNames
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [switch]$CheckDuplicateCommandNames,

        #UpdateModuleReferences
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [switch]$UpdateModuleReferences,

        #PSGetRepository
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [hashtable[]]$PSGetRepository,

        #ModuleValidation
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [ref]$ModuleValidationCache,

        #PsGetModuleValidation
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [ref]$PsGetModuleValidationCache,

        #Proxy
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [uri]$Proxy
    )
    
    Process
    {
        #Add DestinationPath to Process PSModulePath
        try
        {
            Add-PSModulePathEntry -Path $DestinationPath -Scope Process -Force
        }
        catch
        {
            Write-Error "Add DestinationPath to Process PSModulePath failed. Details: $_" -ErrorAction 'Stop'
        }

        #Assert PSRepositories
        if ($PSBoundParameters.ContainsKey('PSGetRepository'))
        {
            #Register PSGetRepo
            $AssertPSRepository_Params = @{
                PSGetRepository = $PSGetRepository
            }
            if ($PSBoundParameters.ContainsKey('Proxy'))
            {
                $AssertPSRepository_Params.Add('Proxy', $Proxy)
            }
            Assert-PSRepository @AssertPSRepository_Params -ErrorAction Stop
        }

        #Validate All Modules
        try
        {
            if (-not $PSBoundParameters.ContainsKey('ModuleValidationCache'))
            {
                [ref]$ModuleValidationCache = @{ }
            }
            priv_Validate-Module -SourcePath $SourcePath -ModuleValidationCache $ModuleValidationCache
        }
        catch
        {
            Write-Error "Unable to validate $moduleName. Details: $_" -ErrorAction 'Stop'
        }

        #Validate All Commands
        try
        {
            [ref]$CommandsToModuleMapping = [PSBuildEntities.AnalysisResultCollection]::new()

            #Add BuildIn Comands and Aliases to CommandsToModuleMapping (from module 'Microsoft.PowerShell.Core' as they are buildin to PowerShell)
            Get-Command -Module $PSNativeModules -Verbose:$false | ForEach-Object {

                #Add Command
                $Command = [PSBuildEntities.AnalysisResult]::new()
                $Command.CommandName = "$($_.Name)"
                $Command.CommandType = $_.CommandType
                if ($Command.CommandType -eq 'Alias')
                {
                    try
                    {
                        $TempFinding2 = Get-Command -Name $Command.CommandName -ErrorAction Stop -Verbose:$false
                        $Command.CommandSource = $TempFinding2.Source
                    }
                    catch
                    {
                    }
                }
                else
                {
                    $Command.CommandSource = $_.Source
                }
                $Command.SourceLocation = [PSBuildEntities.AnalysisResultSourceLocation]::BuildIn
                if (-not $CommandsToModuleMapping.Value.Contains($Command.CommandName))
                {
                    $CommandsToModuleMapping.Value.add($Command)
                }

                #Add Alias if exists
                Get-Alias -Definition $_.Name -ErrorAction SilentlyContinue | foreach {
                    $Alias = [PSBuildEntities.AnalysisResult]::new()
                    $Alias.CommandName = "$($_.Name)"
                    $Alias.CommandType = 'Alias'
                    $Alias.CommandSource = $Command.CommandSource
                    $Alias.SourceLocation = [PSBuildEntities.AnalysisResultSourceLocation]::BuildIn
                    if (-not $CommandsToModuleMapping.Value.Contains($Alias.CommandName))
                    {
                        $CommandsToModuleMapping.Value.add($Alias)
                    }
                }
            }

            #Add Solution Commands to CommandsToModuleMapping (from all modules in the solution)
            foreach ($Mod in $ModuleValidationCache.Value.Keys)
            {
                foreach ($cmd in $ModuleValidationCache.Value[$Mod].ModuleInfo.ExportedCommands.Values)
                {
                    $Command = [PSBuildEntities.AnalysisResult]::new()
                    $Command.CommandName = $cmd.Name
                    $Command.CommandType = $cmd.CommandType
                    $Command.CommandSource = $cmd.Source
                    $Command.SourceLocation = [PSBuildEntities.AnalysisResultSourceLocation]::Solution
                    if (-not $CommandsToModuleMapping.Value.Contains($cmd.Name))
                    {
                        $CommandsToModuleMapping.Value.Add($Command)
                    }
                    elseif ($CommandsToModuleMapping.Value[$Command.CommandName].CommandSource -ne $Command.CommandSource)
                    {
                        if ($CheckDuplicateCommandNames.IsPresent)
                        {
                            Write-Error "Command with name: $($Command.CommandName) is present in multiple modules: $($CommandsToModuleMapping.Value[$Command.CommandName].CommandSource),$($Command.CommandSource)" -ErrorAction Stop
                        }
                    }
                }
            }
        }
        catch
        {
            Write-Error "Validate All Commands failed. Details: $_" -ErrorAction 'Stop'
        }

        #Initialize PSGetModuleValidationCache
        if (-not $PSBoundParameters.ContainsKey('PsGetModuleValidationCache'))
        {
            [ref]$PsGetModuleValidationCache = @{ }
        }

        #Build Module
        foreach ($Module in $SourcePath)
        {
            $moduleName = $Module.Name
            if ($ModuleValidationCache.Value[$moduleName].ModuleInfo)
            {
                $moduleVersion = $ModuleValidationCache.Value[$moduleName].ModuleInfo.Version

                Write-Verbose "Build PSModule:$moduleName/$moduleVersion started"

                #Check if Module is already built
                try
                {
                    Remove-Variable -Name ModAlreadyBuildTest -ErrorAction SilentlyContinue
                    $ModuleAlreadyBuild = $false
                    $ModuleDependanciesValid = $true
                    $ModuleVersionBuilded = $false
                    $ModuleBuildDestinationPath = Join-Path -Path $DestinationPath -ChildPath $moduleName -ErrorAction Stop
                    if (Test-Path -Path $ModuleBuildDestinationPath)
                    {
                        $ModAlreadyBuildTest = Test-PSModule -ModulePath $ModuleBuildDestinationPath -ErrorAction Stop
                    }
                
                    #Check if Module with the same version is already builded
                    if ($ModuleValidationCache.Value[$moduleName].IsValid -and $ModAlreadyBuildTest.IsValid -and ($ModuleValidationCache.Value[$moduleName].ModuleInfo.Version -eq $ModuleValidationCache.Value[$moduleName].ModuleInfo.Version))
                    {
                        $ModuleVersionBuilded = $true
                    }

                    #Check if Module Dependancies are valid versions
                    foreach ($DepModule in $ModuleValidationCache.Value[$moduleName].ModuleInfo.RequiredModules)
                    {
                        $depModuleName = $DepModule.Name
                        $depModuleVersion = $DepModule.Version
                        $DepModuleDestinationPath = Join-Path -Path $DestinationPath -ChildPath $depModuleName -ErrorAction Stop

                        #Check if DepModule is marked as ExternalModuleDependency
                        if ($ModuleValidationCache.Value[$moduleName].ModuleInfo.PrivateData.PSData.ExternalModuleDependencies -contains $depModuleName)
                        {
                            #Skip this DepModule from validation, as it is marked as external
                        }
                        #Check if DepModule is in the same Solution
                        elseif (($ModuleValidationCache.Value).ContainsKey($depModuleName))
                        {
                            if ($ModuleValidationCache.Value[$depModuleName].ModuleInfo.Version -gt $depModuleVersion -or (-not $ModuleValidationCache.Value[$depModuleName].IsValid))
                            {
                                $ModuleDependanciesValid = $false
                            }
                        }
                        #Check if DepModule is in PSGetRepositories
                        elseif ($PSBoundParameters.ContainsKey('PSGetRepository'))
                        {
                            #Check for Module in all Nuget Repos
                            Remove-Variable -Name NugetDependencyList -ErrorAction SilentlyContinue
                            $NugetDependencyList = New-Object -TypeName System.Collections.ArrayList
                            foreach ($item in $PSGetRepository)
                            {
                                #Search for module
                                if (-not $PsGetModuleValidationCache.Value.ContainsKey($depModuleName))
                                {
                                    $PSGet_Params = @{
                                        Name       = $depModuleName
                                        Repository = $item.Name
                                    }
                                    if (-not $UpdateModuleReferences.IsPresent)
                                    {
                                        $PSGet_Params.Add('RequiredVersion', $depModuleVersion)
                                    }
                                    if ($item.ContainsKey('Credential'))
                                    {
                                        $PSGet_Params.Add('Credential', $item.Credential)
                                    }
                                    try
                                    {
                                        Remove-Variable -Name NugetDependency -ErrorAction SilentlyContinue
                                        $private:NugetDependency = Find-Module @PSGet_Params -ErrorAction Stop
                                    }
                                    catch
                                    {

                                    }
                                    #Add module to PsGetModuleValidationCache
                                    if ($private:NugetDependency)
                                    {
                                        $AddMember_Params = @{
                                            InputObject = $private:NugetDependency
                                            MemberType  = 'NoteProperty'
                                            Name        = 'PSGetRepoPriority'
                                        }
                                        if ($item.Priority)
                                        {
                                            $AddMember_Params.Add('Value', $item.Priority)
                                        }
                                        else
                                        {
                                            $AddMember_Params.Add('Value', 0)
                                        }
                                        $null = Add-Member @AddMember_Params -ErrorAction Stop
                                        $null = $NugetDependencyList.Add($private:NugetDependency)
                                    }
                                }
                            }
                            #Get Latest Version if multiple are present
                            if ($NugetDependencyList)
                            {
                                Remove-Variable -Name NugetDepToADD -ErrorAction SilentlyContinue
                                $NugetDepToADD = $NugetDependencyList | Sort-Object -Property Version -Descending | Sort-Object -Property PSGetRepoPriority | select -First 1
                                $null = $PsGetModuleValidationCache.Value.Add($depModuleName, $NugetDepToADD)
                            }
                                
                            #Check if DepModule ref version is the latest
                            if ($PsGetModuleValidationCache.Value.ContainsKey($depModuleName) -and ($PsGetModuleValidationCache.Value[$depModuleName].Version -gt $depModuleVersion))
                            {
                                Write-Warning "Build PSModule:$moduleName/$moduleVersion in progress. PsGet Dependancy: $depModuleName/$depModuleVersion is not the latest version"
                                if ($UpdateModuleReferences.IsPresent)
                                {
                                    $ModuleDependanciesValid = $false
                                }
                            }
                        }
                        #Check If Module Dependency is already builded
                        elseif (Test-Path -Path $DepModuleDestinationPath)
                        {
                            try
                            {
                                $Mod = Test-PSModule -ModulePath $DepModuleDestinationPath -ErrorAction Stop
                                if ($Mod.ModuleInfo.Version -lt $depModuleVersion)
                                {
                                    $ModuleDependanciesValid = $false
                                }
                            }
                            catch
                            {

                            }
                        }
                        else
                        {
                            $ModuleDependanciesValid = $false
                        }
                    }

                    #Determine if module should be built
                    if ($ModuleDependanciesValid -and $ModuleVersionBuilded)
                    {
                        $ModuleAlreadyBuild = $true
                    }
                }
                catch
                {

                }

                #Build Module if not already built
                if ($ModuleAlreadyBuild)
                {
                    Write-Verbose "Build PSModule:$moduleName/$moduleVersion skipped, already built"
                }
                else
                {
                    #Build Module Dependancies
                    try
                    {
                        #Resolve Dependancies
                        if ($ResolveDependancies.IsPresent)
                        {
                            $Dependancies = $ModuleValidationCache.Value[$moduleName].ModuleInfo.RequiredModules | Where-Object { $ModuleValidationCache.Value[$moduleName].ModuleInfo.PrivateData.PSData.ExternalModuleDependencies -notcontains $_.Name }
                            foreach ($ModDependency in $Dependancies)
                            {
                                $dependantModuleName = $ModDependency.Name
                                $dependantModuleVersion = $ModDependency.Version

                                Write-Verbose "Build PSModule:$moduleName/$moduleVersion in progress. Build dependant module:$dependantModuleName/$dependantModuleVersion started"
                                $ModDependencyFound = $false
                                #Search for module in the Solution
                                if (-not $ModDependencyFound)
                                {
                                    if (($ModuleValidationCache.Value).ContainsKey($dependantModuleName) -and ($ModuleValidationCache.Value[$dependantModuleName].ModuleInfo.Version -ge $dependantModuleVersion))
                                    {
                                        $BuildPSModule_Params = @{
                                            SourcePath            = $ModuleValidationCache.Value[$dependantModuleName].ModuleInfo.ModuleBase
                                            DestinationPath       = $DestinationPath
                                            ResolveDependancies   = $ResolveDependancies 
                                            ModuleValidationCache = $ModuleValidationCache
                                        }
                                        if ($PSBoundParameters.ContainsKey('PSGetRepository'))
                                        {
                                            $BuildPSModule_Params.Add('PSGetRepository', $PSGetRepository)
                                        }
                                        if ($PSBoundParameters.ContainsKey('Proxy'))
                                        {
                                            $BuildPSModule_Params.Add('Proxy', $Proxy)
                                        }

                                        Build-PSModule @BuildPSModule_Params -ErrorAction Stop
                                        $ModDependencyFound = $true
                                    }
                                }

                                #Search for module in Solution PSGetRepositories
                                if ((-not $ModDependencyFound) -and ($PsGetModuleValidationCache.Value.ContainsKey($dependantModuleName)) -and ($PSBoundParameters.ContainsKey('PSGetRepository')))
                                {
                                    $NuGetDependancyHandle = $PsGetModuleValidationCache.Value[$dependantModuleName]
                                    #Check if NugetPackage is already downloaded
                                    try
                                    {
                                        $ModDependencyExcepctedPath = Join-Path -Path $DestinationPath -ChildPath $dependantModuleName -ErrorAction Stop
                                        Remove-Variable -Name ModDependencyExist -ErrorAction SilentlyContinue
                                        $ModDependencyExist = Get-Module -ListAvailable -FullyQualifiedName $ModDependencyExcepctedPath -Refresh -ErrorAction Stop -Verbose:$false
                                    }
                                    catch
                                    {

                                    }
                                    if (($ModDependencyExist) -and ($ModDependencyExist.Version -eq $NuGetDependancyHandle.Version))
                                    {
                                        #NugetPackage already downloaded
                                    }
                                    else
                                    {
                                        #Determine from which repo to download the module
                                        Remove-Variable -Name ModuleRepo -ErrorAction SilentlyContinue
                                        $ModuleRepo = $PSGetRepository | Where-Object { $_.Name -eq ($NuGetDependancyHandle.Repository) }

                                        #Downloading NugetPackage
                                        Write-Verbose "Build PSModule:$moduleName/$moduleVersion in progress. Build dependant module:$dependantModuleName/$dependantModuleVersion in progress. Downloading PSGetPackage: $($NuGetDependancyHandle.Name)/$($NuGetDependancyHandle.Version)"
                                        if (-not (Test-Path $DestinationPath))
                                        {
                                            $null = New-Item -Path $DestinationPath -ItemType Directory -ErrorAction Stop
                                        }
                                        $PSGet_Params = @{
                                            Name            = $dependantModuleName
                                            Repository      = $ModuleRepo.Name
                                            RequiredVersion = $NuGetDependancyHandle.Version
                                            Path            = $DestinationPath
                                        }
                                        if ($ModuleRepo.ContainsKey('Credential'))
                                        {
                                            $PSGet_Params.Add('Credential', $ModuleRepo.Credential)
                                        }
                                        if ($ModuleRepo.ContainsKey('Proxy'))
                                        {
                                            $PSGet_Params.Add('Proxy', $Proxy)
                                        }
                                        Save-Module @PSGet_Params -ErrorAction Stop -Verbose:$false
                                    }
                                    $ModDependencyFound = $true
                                }

                                #Throw Not Found
                                if ($ModDependencyFound)
                                {
                                    Write-Verbose "Build PSModule:$moduleName/$moduleVersion in progress. Build dependant module:$dependantModuleName/$dependantModuleVersion completed"
                                }
                                else 
                                {
                                    throw "Dependand module: $dependantModuleName/$dependantModuleVersion not found"
                                }
                            }
                        }
                    }
                    catch
                    {
                        Write-Error "Build PSModule:$moduleName/$moduleVersion dependancies failed. Details: $_"
                    }

                    #Build Module
                    try
                    {
                        #Update Module Dependancies definition
                        if (-not $ModuleDependanciesValid)
                        {
                            Write-Warning "Build PSModule:$moduleName/$moduleVersion in progress. RequiredModules specification not valid, updating it..."
                            $ModuleDependanciesDefinition = New-Object -TypeName system.collections.arraylist
                            foreach ($DepModule in $ModuleValidationCache.Value[$moduleName].ModuleInfo.RequiredModules)
                            {
                                $depModuleName = $DepModule.Name
                                $depModuleVersion = $DepModule.Version

                                if (($ModuleValidationCache.Value).ContainsKey($depModuleName))
                                {
                                    $ModSpec = [Microsoft.PowerShell.Commands.ModuleSpecification]::new(@{
                                            ModuleName    = $ModuleValidationCache.Value[$depModuleName].ModuleInfo.Name
                                            ModuleVersion = $ModuleValidationCache.Value[$depModuleName].ModuleInfo.Version
                                        })
                                    $null = $ModuleDependanciesDefinition.Add($ModSpec)
                                }
                                elseif ($PsGetModuleValidationCache.Value.ContainsKey($depModuleName))
                                {
                                    $ModSpec = [Microsoft.PowerShell.Commands.ModuleSpecification]::new(@{
                                            ModuleName    = $PsGetModuleValidationCache.Value[$depModuleName].Name
                                            ModuleVersion = $PsGetModuleValidationCache.Value[$depModuleName].Version
                                        })
                                    $null = $ModuleDependanciesDefinition.Add($ModSpec)
                                }
                                else
                                {
                                    if (Test-Path $DepModule.Path)
                                    {
                                        $tempDepModuleName = $DepModule.Path
                                    }
                                    else
                                    {
                                        $tempDepModuleName = $DepModule.Name
                                    }
                                    
                                    $ModSpec = [Microsoft.PowerShell.Commands.ModuleSpecification]::new(@{
                                            ModuleName    = $tempDepModuleName
                                            ModuleVersion = $DepModule.Version
                                        })
                                    $null = $ModuleDependanciesDefinition.Add($ModSpec)
                                }
                            }
                            if ($ModuleDependanciesDefinition.Count -gt 0)
                            {
                                Update-ModuleManifest -Path $ModuleValidationCache.Value[$moduleName].ModuleInfo.Path -RequiredModules $ModuleDependanciesDefinition -ErrorAction Stop
                            }
                        }

                        #Check Module Dependancies
                        if ($CheckCommandReferences.IsPresent -and (-not $ModuleValidationCache.Value[$moduleName].IsVersionValid)) 
                        {
                            #if Module is Excluded for CheckCommandReferences
                            if ($PSBoundParameters.ContainsKey('CheckCommandReferencesConfiguration') -and ($CheckCommandReferencesConfiguration.ExcludedSources -contains $moduleName))
                            {
                                Write-Warning "Build PSModule:$moduleName/$moduleVersion in progress. Skipping CommandReference validation"
                            }
                            #if Module is not Excluded for CheckCommandReferences
                            else
                            {
                                #Analyze Command references
                                $CurrentRequiredModules = $PSNativeModules + $ModuleValidationCache.Value[$moduleName].ModuleInfo.RequiredModules.Name
                                $priv_AnalyseItemDependancies_Params = @{
                                    ModulePath            = $ModuleValidationCache.Value[$moduleName].ModuleInfo.ModuleBase
                                    GlobalCommandAnalysis = $CommandsToModuleMapping
                                    CurrentDependancies   = $CurrentRequiredModules
                                }
                                if ($PSBoundParameters.ContainsKey('PSGetRepository'))
                                {
                                    $priv_AnalyseItemDependancies_Params.Add('PSGetRepository', $PSGetRepository)
                                }
                                if ($PSBoundParameters.ContainsKey('Proxy'))
                                {
                                    $priv_AnalyseItemDependancies_Params.Add('Proxy', $Proxy)
                                }
                                $LocalCommandAnalysis = priv_Analyse-ItemDependancies @priv_AnalyseItemDependancies_Params -ErrorAction Stop
                                $CommandNotReferenced = $LocalCommandAnalysis | Where-Object { $_.IsReferenced -eq $false -and $_.CommandType -ne 'Application' }

                                #Check if command is in CheckCommandReferencesConfiguration.ExcludedCommands list
                                if ($PSBoundParameters.ContainsKey('CheckCommandReferencesConfiguration'))
                                {
                                    $CommandNotReferenced = $CommandNotReferenced | Where-Object { $CheckCommandReferencesConfiguration.ExcludedCommands -notcontains $_.CommandName }
                                }

                                if ($CommandNotReferenced)
                                {
                                    throw "Missing RequiredModule reference for [Module\Command]: $($CommandNotReferenced.GetCommandFQDN() -join ', ')"
                                }
                            }
                        }

                        #Check Module Integrity
                        if (-not $ModuleValidationCache.Value[$moduleName].IsReadyForPackaging)
                        {
                            throw "Not ready for packaging. Missing either Author or Description."
                        }
                        if (-not $ModuleValidationCache.Value[$moduleName].IsValid)
                        {
                            Write-Warning "Build PSModule:$moduleName/$moduleVersion in progress. Not valid, updating version..."
                            Update-PSModuleVersion -ModulePath $ModuleValidationCache.Value[$moduleName].ModuleInfo.ModuleBase -ErrorAction Stop
                        
                            #Refresh ModuleValidation
                            $ModuleValidationCache.Value[$moduleName] = Test-PSModule -ModulePath $ModuleValidationCache.Value[$moduleName].ModuleInfo.ModuleBase -ErrorAction Stop
                        }

                        #Export Module to DestinationPath
                        try
                        {
                            priv_Export-PSArtefact -Type Module -SourcePath $ModuleValidationCache.Value[$moduleName].ModuleInfo.ModuleBase -Version $ModuleValidationCache.Value[$moduleName].ModuleInfo.Version -DestinationPath $DestinationPath -Verbose:$false
                        }
                        catch
                        {
                            throw "failed to copy module to $DestinationPath. details: $_"
                        }
                    }
                    catch
                    {
                        Write-Error "Build PSModule:$moduleName/$moduleVersion failed. Details: $_" -ErrorAction 'Stop'
                    }
                }

                Write-Verbose "Build PSModule:$moduleName/$moduleVersion completed"
            }
            else
            {
                Write-Error "Build PSModule:$moduleName failed. Missing ModuleInfo." -ErrorAction Stop
            }
        }
    }
}

function Build-PSScript
{
    [CmdletBinding()]
    [OutputType([void])]
    param
    (
        #SourcePath
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_Default')]
        [System.IO.FileInfo[]]$SourcePath,

        #DestinationPath
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_Default')]
        [System.IO.DirectoryInfo]$DestinationPath,

        #DependencyDestinationPath
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [System.IO.DirectoryInfo]$DependencyDestinationPath,

        #ResolveDependancies
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [switch]$ResolveDependancies,

        #CheckCommandReferences
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [switch]$CheckCommandReferences,

        #CheckCommandReferencesConfiguration
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [psobject]$CheckCommandReferencesConfiguration,

        #UpdateModuleReferences
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [switch]$UpdateModuleReferences,

        #PSGetRepository
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [hashtable[]]$PSGetRepository,

        #ModuleValidation
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [ref]$ModuleValidationCache,

        #PsGetModuleValidation
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [ref]$PsGetModuleValidationCache,

        #Proxy
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [uri]$Proxy
    )
    
    Process
    {
        if (-not $PSBoundParameters.ContainsKey('DependencyDestinationPath'))
        {
            $DependencyDestinationPath = $DestinationPath
        }

        #Add DependencyDestinationPath to Process PSModulePath
        try
        {
            Add-PSModulePathEntry -Path $DependencyDestinationPath -Scope Process -Force
        }
        catch
        {
            Write-Error "Add DependencyDestinationPath to Process PSModulePath failed. Details: $_" -ErrorAction 'Stop'
        }

        #Assert PSRepositories
        if ($PSBoundParameters.ContainsKey('PSGetRepository'))
        {
            #Register PSGetRepo
            $AssertPSRepository_Params = @{
                PSGetRepository = $PSGetRepository
            }
            if ($PSBoundParameters.ContainsKey('Proxy'))
            {
                $AssertPSRepository_Params.Add('Proxy', $Proxy)
            }
            Assert-PSRepository @AssertPSRepository_Params -ErrorAction Stop
        }

        #Validate All Scripts
        try
        {
            if (-not ($AllScriptValidation))
            {
                $AllScriptValidation = @{ }
                foreach ($Script in $SourcePath)
                {
                    $scriptName = $Script.BaseName
                    if (-not $AllScriptValidation.ContainsKey($scriptName))
                    {
                        $null = $AllScriptValidation.Add($scriptName, (Test-PSScript -ScriptPath $Script.FullName -ErrorAction Stop))
                    }
                }
            }
        }
        catch
        {
            Write-Error "Unable to validate $scriptName. Details: $_" -ErrorAction 'Stop'
        }

        #Validate All Commands
        try
        {
            [ref]$CommandsToModuleMapping = [PSBuildEntities.AnalysisResultCollection]::new()

            #Add BuildIn Comands and Aliases to CommandsToModuleMapping (from module 'Microsoft.PowerShell.Core' as they are buildin to PowerShell)
            Get-Command -Module $PSNativeModules -Verbose:$false | ForEach-Object {

                #Add Command
                $Command = [PSBuildEntities.AnalysisResult]::new()
                $Command.CommandName = "$($_.Name)"
                $Command.CommandType = $_.CommandType
                if ($Command.CommandType -eq 'Alias')
                {
                    try
                    {
                        $TempFinding2 = Get-Command -Name $Command.CommandName -ErrorAction Stop -Verbose:$false
                        $Command.CommandSource = $TempFinding2.Source
                    }
                    catch
                    {
                    }
                }
                else
                {
                    $Command.CommandSource = $_.Source
                }
                $Command.SourceLocation = [PSBuildEntities.AnalysisResultSourceLocation]::BuildIn
                if (-not $CommandsToModuleMapping.Value.Contains($Command.CommandName))
                {
                    $CommandsToModuleMapping.Value.add($Command)
                }

                #Add Alias if exists
                Get-Alias -Definition $_.Name -ErrorAction SilentlyContinue | foreach {
                    $Alias = [PSBuildEntities.AnalysisResult]::new()
                    $Alias.CommandName = "$($_.Name)"
                    $Alias.CommandType = 'Alias'
                    $Alias.CommandSource = $Command.CommandSource
                    $Alias.SourceLocation = [PSBuildEntities.AnalysisResultSourceLocation]::BuildIn
                    if (-not $CommandsToModuleMapping.Value.Contains($Alias.CommandName))
                    {
                        $CommandsToModuleMapping.Value.add($Alias)
                    }
                }
            }

            #Add Solution Commands to CommandsToModuleMapping (from all modules in the solution)
            if ($PSBoundParameters.ContainsKey('ModuleValidationCache'))
            {
                foreach ($Mod in $ModuleValidationCache.Value.Keys)
                {
                    foreach ($cmd in $ModuleValidationCache.Value[$Mod].ModuleInfo.ExportedCommands.Values)
                    {
                        $Command = [PSBuildEntities.AnalysisResult]::new()
                        $Command.CommandName = $cmd.Name
                        $Command.CommandType = $cmd.CommandType
                        $Command.CommandSource = $cmd.Source
                        $Command.SourceLocation = [PSBuildEntities.AnalysisResultSourceLocation]::Solution
                        if (-not $CommandsToModuleMapping.Value.Contains($cmd.Name))
                        {
                            $CommandsToModuleMapping.Value.Add($Command)
                        }
                        elseif ($CommandsToModuleMapping.Value[$Command.CommandName].CommandSource -ne $Command.CommandSource)
                        {
                            if ($CheckDuplicateCommandNames.IsPresent)
                            {
                                Write-Error "Command with name: $($Command.CommandName) is present in multiple modules: $($CommandsToModuleMapping.Value[$Command.CommandName].CommandSource),$($Command.CommandSource)" -ErrorAction Stop
                            }
                        }
                    }
                }
            }
        }
        catch
        {
            Write-Error "Validate All Commands failed. Details: $_" -ErrorAction 'Stop'
        }

        #Initialize PSGetModuleValidationCache
        if (-not $PSBoundParameters.ContainsKey('PsGetModuleValidationCache'))
        {
            [ref]$PsGetModuleValidationCache = @{ }
        }

        #Build Script
        foreach ($Script in $SourcePath)
        {
            $scriptName = $Script.BaseName
            if ($AllScriptValidation[$scriptName].ScriptInfo)
            {
                $scriptVersion = $AllScriptValidation[$scriptName].ScriptInfo.Version

                Write-Verbose "Build Script:$scriptName/$scriptVersion started"

                #Check if Script is already built
                try
                {
                    $ScriptAlreadyBuild = $false
                    $ScriptDependanciesValid = $true
                    $ScriptVersionBuilded = $false
                    $ScriptBuildDestinationPath = Join-Path -Path $DestinationPath -ChildPath $Script.Name -ErrorAction Stop
                    if (Test-Path -Path $ScriptBuildDestinationPath)
                    {
                        $ScriptAlreadyBuildTest = Test-PSScript -ScriptPath $ScriptBuildDestinationPath -ErrorAction Stop
                    }
                
                    #Check if Script with the same version is already builded
                    if ($AllScriptValidation[$scriptName].IsValid -and $ScriptAlreadyBuildTest.IsValid -and ($AllScriptValidation[$scriptName].ScriptInfo.Version -eq $ScriptAlreadyBuildTest.ScriptInfo.Version))
                    {
                        $ScriptVersionBuilded = $true
                    }

                    #Check if Module Dependancies are valid versions
                    foreach ($DepModule in $AllScriptValidation[$scriptName].ScriptInfo.RequiredModules)
                    {
                        $depModuleName = $DepModule.Name
                        $depModuleVersion = $DepModule.Version
                        $DepModuleDestinationPath = Join-Path -Path $DependencyDestinationPath -ChildPath $depModuleName -ErrorAction Stop

                        #Check if DepModule is marked as ExternalModuleDependency
                        if ($AllScriptValidation[$scriptName].ScriptInfo.ExternalModuleDependencies -contains $depModuleName)
                        {
                            #Skip this DepModule from validation, as it is marked as external
                        }
                        #Check if DepModule is in the same Solution
                        elseif (($ModuleValidationCache.Value).ContainsKey($depModuleName))
                        {
                            if ($ModuleValidationCache.Value[$depModuleName].ModuleInfo.Version -gt $depModuleVersion -or (-not $ModuleValidationCache.Value[$depModuleName].IsValid))
                            {
                                $ScriptDependanciesValid = $false
                            }
                        }
                        #Check if DepModule is in PSGetRepositories
                        elseif ($PSBoundParameters.ContainsKey('PSGetRepository'))
                        {
                            #Check for Module in all Nuget Repos
                            Remove-Variable -Name NugetDependencyList -ErrorAction SilentlyContinue
                            $NugetDependencyList = New-Object -TypeName System.Collections.ArrayList
                            foreach ($item in $PSGetRepository)
                            {
                                #Search for module
                                if (-not $PsGetModuleValidationCache.Value.ContainsKey($depModuleName))
                                {
                                    $PSGet_Params = @{
                                        Name       = $depModuleName
                                        Repository = $item.Name
                                    }
                                    if (-not $UpdateModuleReferences.IsPresent)
                                    {
                                        $PSGet_Params.Add('RequiredVersion', $depModuleVersion)
                                    }
                                    if ($item.ContainsKey('Credential'))
                                    {
                                        $PSGet_Params.Add('Credential', $item.Credential)
                                    }
                                    if ($PSBoundParameters.ContainsKey('Proxy'))
                                    {
                                        $PSGet_Params.Add('Proxy', $Proxy)
                                    }    
                                    try
                                    {
                                        Remove-Variable -Name NugetDependency -ErrorAction SilentlyContinue
                                        $private:NugetDependency = Find-Module @PSGet_Params -ErrorAction Stop
                                    }
                                    catch
                                    {

                                    }
                                    #Add module to PsGetModuleValidationCache
                                    if ($private:NugetDependency)
                                    {
                                        $AddMember_Params = @{
                                            InputObject = $private:NugetDependency
                                            MemberType  = 'NoteProperty'
                                            Name        = 'PSGetRepoPriority'
                                        }
                                        if ($item.Priority)
                                        {
                                            $AddMember_Params.Add('Value', $item.Priority)
                                        }
                                        else
                                        {
                                            $AddMember_Params.Add('Value', 0)
                                        }
                                        $null = Add-Member @AddMember_Params -ErrorAction Stop
                                        $null = $NugetDependencyList.Add($private:NugetDependency)
                                    }
                                }
                            }
                            #Get Latest Version if multiple are present
                            if ($NugetDependencyList)
                            {
                                Remove-Variable -Name NugetDepToADD -ErrorAction SilentlyContinue
                                $NugetDepToADD = $NugetDependencyList | Sort-Object -Property Version -Descending | Sort-Object -Property PSGetRepoPriority | select -First 1
                                $null = $PsGetModuleValidationCache.Value.Add($depModuleName, $NugetDepToADD)
                            }
                                
                            #Check if DepModule ref version is the latest
                            if ($PsGetModuleValidationCache.Value.ContainsKey($depModuleName) -and ($PsGetModuleValidationCache.Value[$depModuleName].Version -gt $depModuleVersion))
                            {
                                Write-Warning "Build Script:$scriptName/$scriptVersion in progress. PsGet Dependancy: $depModuleName/$depModuleVersion is not the latest version"
                                if ($UpdateModuleReferences.IsPresent)
                                {
                                    $ScriptDependanciesValid = $false
                                }
                            }
                        }
                        #Check If Module Dependency is already builded
                        elseif (Test-Path -Path $DepModuleDestinationPath)
                        {
                            try
                            {
                                $Mod = Test-PSModule -ModulePath $DepModuleDestinationPath -ErrorAction Stop
                                if ($Mod.ModuleInfo.Version -lt $depModuleVersion)
                                {
                                    $ScriptDependanciesValid = $false
                                }
                            }
                            catch
                            {

                            }
                        }
                        else
                        {
                            $ScriptDependanciesValid = $false
                        }
                    }

                    #Determine if script should be built
                    if ($ScriptVersionBuilded -and $ScriptDependanciesValid)
                    {
                        $ScriptAlreadyBuild = $true
                    }
                }
                catch
                {

                }

                #Build Script if not already built
                if ($ScriptAlreadyBuild)
                {
                    Write-Verbose "Build Script:$scriptName/$scriptVersion skipped, already built"
                }
                else
                {
                    #Build Script Dependancies
                    try
                    {
                        #Resolve Dependancies
                        if ($ResolveDependancies.IsPresent)
                        {
                            $Dependancies = $AllScriptValidation[$scriptName].ScriptInfo.RequiredModules | Where-Object { $AllScriptValidation[$scriptName].ScriptInfo.ExternalModuleDependencies -notcontains $_.Name }
                            foreach ($ModDependency in $Dependancies)
                            {
                                $dependantModuleName = $ModDependency.Name
                                $dependantModuleVersion = $ModDependency.Version

                                Write-Verbose "Build Script:$scriptName/$scriptVersion in progress. Build dependant module:$dependantModuleName/$dependantModuleVersion started"
                                $ModDependencyFound = $false
                                #Search for module in the Solution
                                if ((-not $ModDependencyFound) -and ($ModuleValidationCache.Value).ContainsKey($dependantModuleName))
                                {
                                    $BuildPSModule_Params = @{
                                        SourcePath=$ModuleValidationCache.Value[$dependantModuleName].ModuleInfo.ModuleBase
                                        DestinationPath=$DependencyDestinationPath
                                        ResolveDependancies=$ResolveDependancies.IsPresent
                                        ModuleValidationCache=$ModuleValidationCache
                                    }
                                    if ($PSBoundParameters.ContainsKey('Proxy'))
                                    {
                                        $BuildPSModule_Params.Add('Proxy',$Proxy)
                                    }
                                    Build-PSModule @BuildPSModule_Params -ErrorAction Stop
                                    $ModDependencyFound = $true
                                }

                                #Search for module in Solution PSGetRepositories
                                if ((-not $ModDependencyFound) -and ($PsGetModuleValidationCache.Value.ContainsKey($dependantModuleName)) -and ($PSBoundParameters.ContainsKey('PSGetRepository')))
                                {
                                    $NuGetDependancyHandle = $PsGetModuleValidationCache.Value[$dependantModuleName]
                                    #Check if NugetPackage is already downloaded
                                    try
                                    {
                                        $ModDependencyExcepctedPath = Join-Path -Path $DependencyDestinationPath -ChildPath $dependantModuleName -ErrorAction Stop
                                        Remove-Variable -Name ModDependencyExist -ErrorAction SilentlyContinue
                                        $ModDependencyExist = Get-Module -ListAvailable -FullyQualifiedName $ModDependencyExcepctedPath -Refresh -ErrorAction Stop -Verbose:$false
                                    }
                                    catch
                                    {

                                    }
                                    if (($ModDependencyExist) -and ($ModDependencyExist.Version -eq $NuGetDependancyHandle.Version))
                                    {
                                        #NugetPackage already downloaded
                                    }
                                    else
                                    {
                                        #Determine from which repo to download the module
                                        Remove-Variable -Name ModuleRepo -ErrorAction SilentlyContinue
                                        $ModuleRepo = $PSGetRepository | Where-Object { $_.Name -eq ($NuGetDependancyHandle.Repository) }

                                        #Downloading NugetPackage
                                        Write-Verbose "Build Script:$scriptName/$scriptVersion in progress. Build dependant module:$dependantModuleName/$dependantModuleVersion in progress. Downloading PSGetPackage: $($NuGetDependancyHandle.Name)/$($NuGetDependancyHandle.Version)"
                                        if (-not (Test-Path $DependencyDestinationPath))
                                        {
                                            $null = New-Item -Path $DependencyDestinationPath -ItemType Directory -ErrorAction Stop
                                        }
                                        $PSGet_Params = @{
                                            Name            = $dependantModuleName
                                            Repository      = $ModuleRepo.Name
                                            RequiredVersion = $NuGetDependancyHandle.Version
                                            Path            = $DependencyDestinationPath
                                        }
                                        if ($ModuleRepo.ContainsKey('Credential'))
                                        {
                                            $PSGet_Params.Add('Credential', $ModuleRepo.Credential)
                                        }
                                        if ($PSBoundParameters.ContainsKey('Proxy'))
                                        {
                                            $PSGet_Params.Add('Proxy', $Proxy)
                                        }
                                        Save-Module @PSGet_Params -ErrorAction Stop -Verbose:$false
                                    }
                                    $ModDependencyFound = $true
                                }

                                #Throw Not Found
                                if ($ModDependencyFound)
                                {
                                    Write-Verbose "Build Script:$scriptName/$scriptVersion in progress. Build dependant module:$dependantModuleName/$dependantModuleVersion completed"
                                }
                                else 
                                {
                                    throw "Dependand PSModule: $dependantModuleName/$dependantModuleVersion not found"
                                }
                            }
                        }
                    }
                    catch
                    {
                        Write-Error "Build PSModule:$moduleName/$moduleVersion dependancies failed. Details: $_"
                    }

                    #Build Script
                    try
                    {
                        #Update Script Dependancies definition
                        if (-not $ScriptDependanciesValid)
                        {
                            Write-Warning "Build Script:$scriptName/$scriptVersion in progress. RequiredModules specification not valid, updating it..."
                            $ScriptDependanciesDefinition = New-Object -TypeName system.collections.arraylist
                            foreach ($DepModule in $AllScriptValidation[$scriptName].ScriptInfo.RequiredModules)
                            {
                                $depModuleName = $DepModule.Name
                                $depModuleVersion = $DepModule.Version

                                if (($ModuleValidationCache.Value).ContainsKey($depModuleName))
                                {
                                    $ModSpec = [Microsoft.PowerShell.Commands.ModuleSpecification]::new(@{
                                            ModuleName    = $ModuleValidationCache.Value[$depModuleName].ModuleInfo.Name
                                            ModuleVersion = $ModuleValidationCache.Value[$depModuleName].ModuleInfo.Version
                                        })
                                    $null = $ScriptDependanciesDefinition.Add($ModSpec)
                                }
                                elseif ($PsGetModuleValidationCache.Value.ContainsKey($depModuleName))
                                {
                                    $ModSpec = [Microsoft.PowerShell.Commands.ModuleSpecification]::new(@{
                                            ModuleName    = $PsGetModuleValidationCache.Value[$depModuleName].Name
                                            ModuleVersion = $PsGetModuleValidationCache.Value[$depModuleName].Version
                                        })
                                    $null = $ScriptDependanciesDefinition.Add($ModSpec)
                                }
                                else
                                {
                                    $ModSpec = [Microsoft.PowerShell.Commands.ModuleSpecification]::new(@{
                                            ModuleName    = $DepModule.Name
                                            ModuleVersion = $DepModule.Version
                                        })
                                    $null = $ScriptDependanciesDefinition.Add($ModSpec)
                                }
                            }
                            if ($ScriptDependanciesDefinition.Count -gt 0)
                            {
                                Update-ScriptFileInfo -Path $AllScriptValidation[$scriptName].ScriptInfo.Path -RequiredModules $ScriptDependanciesDefinition -ErrorAction Stop
                            }
                        }

                        #Check Script Dependancies
                        if ($CheckCommandReferences.IsPresent -and (-not $AllScriptValidation[$scriptName].IsVersionValid))
                        {
                            #if Script is Excluded for CheckCommandReferences
                            if ($PSBoundParameters.ContainsKey('CheckCommandReferencesConfiguration') -and ($CheckCommandReferencesConfiguration.ExcludedSources -contains $moduleName))
                            {
                                Write-Warning "Build Script:$scriptName/$scriptVersion in progress. Skipping CommandReference validation"
                            }
                            #if Module is not Excluded for CheckCommandReferences
                            else
                            {
                                #Analyze Command references
                                $CurrentRequiredModules = $PSNativeModules + $AllScriptValidation[$scriptName].ScriptInfo.RequiredModules.Name
                                $priv_AnalyseItemDependancies_Params = @{
                                    ScriptPath            = $AllScriptValidation[$scriptName].ScriptInfo.Path
                                    GlobalCommandAnalysis = $CommandsToModuleMapping
                                    CurrentDependancies   = $CurrentRequiredModules
                                }
                                if ($PSBoundParameters.ContainsKey('PSGetRepository'))
                                {
                                    $priv_AnalyseItemDependancies_Params.Add('PSGetRepository', $PSGetRepository)
                                }
                                if ($PSBoundParameters.ContainsKey('Proxy'))
                                {
                                    $priv_AnalyseItemDependancies_Params.Add('Proxy', $Proxy)
                                }
                                $LocalCommandAnalysis = priv_Analyse-ItemDependancies @priv_AnalyseItemDependancies_Params -ErrorAction Stop
                                $CommandNotReferenced = $LocalCommandAnalysis | Where-Object { ($_.IsReferenced -eq $false) -and ($_.CommandType -ne 'Application') }

                                #Check if command is in CheckCommandReferencesConfiguration.ExcludedCommands list
                                if ($PSBoundParameters.ContainsKey('CheckCommandReferencesConfiguration'))
                                {
                                    $CommandNotReferenced = $CommandNotReferenced | Where-Object { $CheckCommandReferencesConfiguration.ExcludedCommands -notcontains $_.CommandName }
                                }

                                if ($CommandNotReferenced)
                                {
                                    throw "Missing RequiredModule reference for [Module\Command]: $($CommandNotReferenced.GetCommandFQDN() -join ', ')"
                                }
                            }
                        }

                        #Check Script Integrity
                        if (-not $AllScriptValidation[$scriptName].IsValid)
                        {
                            Write-Warning "Build Script:$scriptName/$scriptVersion in progress. Not valid, updating version..."
                            Update-PSScriptVersion -ScriptPath $AllScriptValidation[$scriptName].ScriptInfo.Path -ErrorAction Stop
                        
                            #Refresh ScriptValidation
                            $AllScriptValidation[$scriptName] = Test-PSScript -ScriptPath $AllScriptValidation[$scriptName].ScriptInfo.Path -ErrorAction Stop
                        }
                        elseif (-not $AllScriptValidation[$scriptName].IsReadyForPackaging)
                        {
                            throw "Not ready for packaging. Missing either Author or Description."
                        }

                        #Export Script to DestinationPath
                        try
                        {
                            priv_Export-PSArtefact -Type Script -SourcePath $AllScriptValidation[$scriptName].ScriptInfo.Path -DestinationPath $DestinationPath -Verbose:$false
                        }
                        catch
                        {
                            throw "Unable to copy script to $DestinationPath. Details: $_"
                        }
                    }
                    catch
                    {
                        Write-Error "Build Script:$scriptName/$scriptVersion failed. Details: $_" -ErrorAction 'Stop'
                    }
                }

                Write-Verbose "Build Script:$scriptName/$scriptVersion completed"
            }
            else
            {
                $ErrorMsg = "Build Script:$scriptName failed."
                if ($AllScriptValidation[$scriptName].ValidationErrors)
                {
                    $ErrorDetails = $AllScriptValidation[$scriptName].ValidationErrors -join "$([System.Environment]::NewLine) $([System.Environment]::NewLine)"
                    $ErrorMsg += @"
 Script Parse Errors:
$ErrorDetails
"@

                }
                else
                {
                    $ErrorMsg += 'Missing ScriptInfo.'
                }
                Write-Error $ErrorMsg -ErrorAction Stop    
            }
        }
    }
}

function Publish-PSModule
{
    [CmdletBinding()]
    param
    (
        #ModulesFolder
        [Parameter(Mandatory = $true)]
        [System.IO.DirectoryInfo[]]$ModulePath,

        #PSGetRepository
        [Parameter(Mandatory = $false)]
        [hashtable]$PSGetRepository,

        #SkipVersionValidation
        [Parameter(Mandatory = $false)]
        [switch]$SkipVersionValidation,
        
        #Proxy
        [Parameter(Mandatory = $false)]
        [uri]$Proxy
    )

    Process
    {
        #Check if Repository is already registered
        try
        {
            Write-Verbose "Check if Repository is already registered started"
            
            $AssertPSRepository_Params = @{
                PSGetRepository=$PSGetRepository
            }
            if ($PSBoundParameters.ContainsKey('Proxy'))
            {
                $AssertPSRepository_Params.Add('Proxy',$Proxy)
            }
            Assert-PSRepository @AssertPSRepository_Params -ErrorAction Stop
            
            Write-Verbose "Check if Repository is already registered completed"
        }
        catch
        {
            Write-Error "Check if Repository is already registered failed. Details: $_" -ErrorAction 'Stop'
        }

        #Publish Module
        foreach ($Module in $ModulePath)
        {
            try
            {
                $moduleName = $Module.Name
                Write-Verbose "Publish Module:$moduleName started"

                #Check if Module is already built
                $ModInfo = Test-PSModule -ModulePath $Module -ErrorAction Stop
                if (-not $ModInfo.IsVersionValid -and (-not $SkipVersionValidation.IsPresent))
                {
                    throw 'not builded'
                }

                #Publish Module
                $PublishModuleAndDependacies_Params = @{
                    ModuleInfo              = $ModInfo.ModuleInfo
                    Repository              = $PSGetRepository.Name
                    PublishDependantModules = $true
                    Force                   = $true
                }
                if ($PSGetRepository.ContainsKey('Credential'))
                {
                    $PublishModuleAndDependacies_Params.Add('Credential', $PSGetRepository.Credential)
                }
                if ($PSGetRepository.ContainsKey('NuGetApiKey'))
                {
                    $PublishModuleAndDependacies_Params.Add('NuGetApiKey', $PSGetRepository.NuGetApiKey)
                }
                if ($PSBoundParameters.ContainsKey('Proxy'))
                {
                    $PublishModuleAndDependacies_Params.Add('Proxy',$Proxy)
                }
                priv_Publish-PSModule @PublishModuleAndDependacies_Params -ErrorAction Stop -Verbose -VerbosePrefix "Publish Module:$moduleName in progress. "

                Write-Verbose "Publish Module:$moduleName completed"
            }
            catch
            {
                Write-Error "Publish Module:$moduleName failed. Details: $_" -ErrorAction Stop
            }
        }
    }
}

function Build-PSSolution
{
    [CmdletBinding()]
    [OutputType([void])]
    param
    (
        #SolutionConfigPath
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_SolutionByPath')]
        [System.IO.DirectoryInfo]$SolutionConfigPath,

        #SolutionConfig
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_SolutionByObject')]
        [psobject]$SolutionConfigObject
    )

    Process
    {
        #Initialize SolutionConfiguration
        try
        {
            Write-Verbose "Initialize SolutionConfiguration started"
            
            switch ($PSCmdlet.ParameterSetName)
            {
                'NoRemoting_SolutionByPath'
                {
                    $SolutionConfig = Get-PSSolutionConfiguration -Path $SolutionConfigPath.FullName -ErrorAction Stop
                    break
                }
                'NoRemoting_SolutionByObject'
                {
                    $SolutionConfig = $SolutionConfigObject
                    break
                }
                default
                {
                    throw "Unknown ParameterSetName: $($PSCmdlet.ParameterSetName)"
                }
            }

            Write-Verbose "Initialize SolutionConfiguration completed"
        }
        catch
        {
            Write-Error "Initialize SolutionConfiguration failed. Details: $_" -ErrorAction 'Stop'
        }

        #Configure PS Environment
        try
        {
            Write-Verbose "Configure PS Environment started"

            if ($SolutionConfig.SolutionStructure.ModulesPath.BuildPath)
            {
                $TempModulePaths = $SolutionConfig.SolutionStructure.ModulesPath.BuildPath
                if ($SolutionConfig.Build.AutoloadbuiltModulesForUser)
                {

                    Write-Verbose "Configure PS Environment in progress. Enable BuildPath($($TempModulePaths -join ',')) Module Autoloading"
                    Add-PSModulePathEntry -Path $TempModulePaths -Scope User, Process -ErrorAction Stop -Force
                }
                else
                {
                    Write-Verbose "Configure PS Environment in progress. Disable BuildPath($($TempModulePaths -join ',')) Module Autoloading"
                    Remove-PSModulePathEntry -Path $TempModulePaths -Scope User, Process -ErrorAction Stop -WarningAction SilentlyContinue
                }
            }
            Write-Verbose "Configure PS Environment completed"
        }
        catch
        {
            Write-Error "Configure PS Environment failed. Details: $_" -ErrorAction 'Stop'
        }

        #Build Solution Modules
        try
        {
            [ref]$ModuleValidationCache = @{ }
            [ref]$PsGetModuleValidationCache = @{ }

            if ($SolutionConfig.SolutionStructure.ModulesPath)
            {
                Write-Verbose "Build PSModules started"

                #Validate All Modules
                $AllModulesPath = New-Object System.Collections.ArrayList -ErrorAction Stop
                foreach ($ModulePath in $SolutionConfig.SolutionStructure.ModulesPath)
                {
                    $null = Get-ChildItem -Path $ModulePath.SourcePath -Directory -ErrorAction Stop | foreach { $AllModulesPath.Add($_) }
                }
                priv_Validate-Module -SourcePath $AllModulesPath -ModuleValidationCache $ModuleValidationCache

                foreach ($ModulePath in $SolutionConfig.SolutionStructure.ModulesPath)
                {
                    $Modules = Get-ChildItem -Path $ModulePath.SourcePath -Directory -ErrorAction Stop
                    $BuildPSSolutionModule_Params = @{
                        SourcePath                          = $Modules
                        DestinationPath                     = $ModulePath.BuildPath
                        ResolveDependancies                 = $SolutionConfig.Build.AutoResolveDependantModules
                        PSGetRepository                     = $SolutionConfig.Packaging.PSGetSearchRepositories
                        CheckCommandReferences              = $SolutionConfig.Build.CheckCommandReferences.Enabled
                        CheckCommandReferencesConfiguration = $SolutionConfig.Build.CheckCommandReferences
                        ModuleValidationCache               = $ModuleValidationCache
                        UpdateModuleReferences              = $SolutionConfig.Build.UpdateModuleReferences
                        PsGetModuleValidationCache          = $PsGetModuleValidationCache
                    }
                    if ($SolutionConfig.GlobalSettings.Proxy.Uri)
                    {
                        $BuildPSSolutionModule_Params.Add('Proxy',$SolutionConfig.GlobalSettings.Proxy.Uri)
                    }
                    Build-PSModule @BuildPSSolutionModule_Params -ErrorAction Stop
                }
        
                Write-Verbose "Build PSModules completed"
            }
            else
            {
                Write-Verbose "Build PSModules skipped"
            }
        }
        catch
        {
            Write-Error "Build Solution Modules failed. Details: $_" -ErrorAction 'Stop'
        }

        #Build Solution Scripts
        try
        {
            Write-Verbose "Build Solution Scripts started"

            foreach ($ScriptPath in $SolutionConfig.SolutionStructure.ScriptPath)
            {
                $Scripts = Get-ChildItem -Path $ScriptPath.SourcePath -Filter *.ps1  -ErrorAction Stop
                if ($ScriptPath.ContainsKey('Exclude'))
                {
                    $Scripts = $Scripts | where-object { $ScriptPath.Exclude -notcontains $_.Name }
                }
                $BuildPSScript_Params = @{
                    SourcePath                          = $Scripts
                    DestinationPath                     = $ScriptPath.BuildPath
                    ResolveDependancies                 = $SolutionConfig.Build.AutoResolveDependantModules
                    PSGetRepository                     = $SolutionConfig.Packaging.PSGetSearchRepositories
                    CheckCommandReferences              = $SolutionConfig.Build.CheckCommandReferences.Enabled
                    CheckCommandReferencesConfiguration = $SolutionConfig.Build.CheckCommandReferences
                    ModuleValidationCache               = $ModuleValidationCache
                    UpdateModuleReferences              = $SolutionConfig.Build.UpdateModuleReferences
                    PsGetModuleValidationCache          = $PsGetModuleValidationCache
                }
                if ($ScriptPath.ContainsKey('DependencyDestinationPath'))
                {
                    $BuildPSScript_Params.Add('DependencyDestinationPath', $ScriptPath.DependencyDestinationPath)
                }
                if ($SolutionConfig.GlobalSettings.Proxy.Uri -and (-not [string]::IsNullOrEmpty($SolutionConfig.GlobalSettings.Proxy.Uri)))
                {
                    $BuildPSSolutionModule_Params.Add('Proxy',$SolutionConfig.GlobalSettings.Proxy.Uri)
                }
                Build-PSScript @BuildPSScript_Params -ErrorAction Stop
            }
      
            Write-Verbose "Build Solution Scripts completed"
        }
        catch
        {
            Write-Error "Build Solution Scripts failed. Details: $_" -ErrorAction 'Stop'
        }

        #Run PostBuild Actions
        try
        {
            Write-Verbose "Run PostBuild Actions started"
            
            Foreach ($Action in $SolutionConfig.BuildActions.PostBuild)
            {
                try
                {
                    Write-Verbose "Run PostBuild Actions in progress. Action: $($Action['Name']) starting."
                    $Null = Invoke-Command -ScriptBlock $Action['ScriptBlock'] -ErrorAction Stop -NoNewScope
                    Write-Verbose "Run PostBuild Actions in progress. Action: $($Action['Name']) completed."
                }
                catch
                {
                    Write-Warning "Run PostBuild Actions in progress. Action: $($Action['Name']) failed."
                    throw $_
                }
            }
      
            Write-Verbose "Run PostBuild Actions completed"
        }
        catch
        {
            Write-Error "Run PostBuild Actions failed. Details: $_" -ErrorAction 'Stop'
        }
    }
}

function Publish-PSSolution
{
    [CmdletBinding()]
    [OutputType([void])]
    param
    (
        #SolutionConfigPath
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_SolutionByPath')]
        [System.IO.DirectoryInfo]$SolutionConfigPath,

        #SolutionConfig
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_SolutionByObject')]
        [psobject]$SolutionConfigObject
    )

    Process
    {
        #Initialize SolutionConfiguration
        try
        {
            Write-Verbose "Initialize SolutionConfiguration started"
            
            switch ($PSCmdlet.ParameterSetName)
            {
                'NoRemoting_SolutionByPath'
                {
                    $SolutionConfig = Get-PSSolutionConfiguration -Path $SolutionConfigPath.FullName -ErrorAction Stop
                    break
                }
                'NoRemoting_SolutionByObject'
                {
                    $SolutionConfig = $SolutionConfigObject
                    break
                }
                default
                {
                    throw "Unknown ParameterSetName: $($PSCmdlet.ParameterSetName)"
                }
            }

            Write-Verbose "Initialize SolutionConfiguration completed"
        }
        catch
        {
            Write-Error "Initialize SolutionConfiguration failed. Details: $_" -ErrorAction 'Stop'
        }

        #Publish Solution Modules
        try
        {
            Write-Verbose "Publish Solution Modules started"

            #Get All Modules
            $Modules = New-Object -TypeName system.collections.arraylist
            foreach ($modPath in $SolutionConfig.SolutionStructure.ModulesPath)
            {
                Get-ChildItem -Path $modPath.SourcePath -Directory -ErrorAction Stop | ForEach-Object {
                    $ModuleToPathMapping = @{
                        Module     = $_
                        SourcePath = Join-Path -Path $modPath.SourcePath -ChildPath $_.Name
                        BuildPath  = Join-Path -Path $modPath.BuildPath -ChildPath $_.Name
                    }
                    $null = $Modules.Add($ModuleToPathMapping)
                }
            }

            #Determine which modules should be published
            if ((-not $SolutionConfig.Packaging.PublishAllModules) -and ($SolutionConfig.Packaging.PublishSpecificModules.Count -gt 0))
            {
                $Modules = $Modules | Where-Object { $SolutionConfig.Packaging.PublishSpecificModules -contains $_.Module.Name }
            }
            elseif ($SolutionConfig.Packaging.PublishAllModules -and ($SolutionConfig.Packaging.PublishSpecificModules.Count -gt 0))
            {
                Write-Warning "Publish Solution Modules in progress. No Modules are configured to be published"
            }
            if ($SolutionConfig.Packaging.PublishExcludeModules.Count -gt 0)
            {
                $Modules = $Modules | Where-Object { $SolutionConfig.Packaging.PublishExcludeModules -ne $_.Module.Name }
            }

            #Determine if there are PSGetRepositories specified for publishing
            if ($Modules)
            {
                if ($SolutionConfig.Packaging.PSGetPublishRepositories.Count -eq 0)
                {
                    Write-Warning "Publish Solution Modules in progress. There are modules for publishing, but no PSGetPublishRepositories are specified"
                }
            }
            else
            {
                Write-Warning "Publish Solution Modules in progress. No Modules for publishing"
            }

            #Install Nuget PackageProvider
            try
            {
                $NuGetProvider = Get-PackageProvider -Name nuget -ErrorAction Stop | Where-Object { [version]$_.Version -ge [version]'2.8.5.208' }
            }
            catch
            {

            }
            if (-not $NuGetProvider)
            {
                $null = Install-PackageProvider -Name Nuget -Force -Confirm:$false -Verbose:$false -Scope CurrentUser
            }


            #Publish Modules to each PSGetPublishRepositories
            foreach ($Repo in $SolutionConfig.Packaging.PSGetPublishRepositories)
            {
                Add-PSModulePathEntry -Scope Process -Path $Modules.BuildPath -ErrorAction Stop
                Publish-PSModule -ModulePath $Modules.BuildPath -PSGetRepository $Repo -ErrorAction Stop
            }

            Write-Verbose "Publish Solution Modules completed"
        }
        catch
        {
            Write-Error "Publish Solution Modules failed. Details: $_" -ErrorAction 'Stop'
        }
    }
}

function Get-PSSolutionConfiguration
{
    [CmdletBinding()]
    [OutputType([void])]
    param
    (
        #Path
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_Default')]
        [System.IO.FileInfo]$Path,

        #UserVariables
        [Parameter(Mandatory = $false, ParameterSetName = 'NoRemoting_Default')]
        [hashtable]$UserVariables
    )

    Process
    {
        try
        {
            $SolutionConfigRaw = Get-Content -Path $Path.FullName -Raw -ErrorAction Stop
            $SolutionRootFolder = Split-Path -Path $Path -Parent -ErrorAction Stop
            if ($PSBoundParameters.ContainsKey('UserVariables'))
            {
                Set-Variable -Name UserVariables -Value $UserVariables -Scope global -ErrorAction Stop
            }
            $r = "`$env:ScriptRoot = '$SolutionRootFolder'" + "`n" + $SolutionConfigRaw
            New-DynamicConfiguration -Definition ([scriptblock]::Create($r)) -ErrorAction Stop
        }
        catch
        {
            Write-Error "Unable to load SolutionConfiguration: $($Path.FullName). Details: $_"
        }
    }
}

function Clear-PSSolution
{
    [CmdletBinding()]
    [OutputType([void])]
    param
    (
        #SolutionConfigPath
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_SolutionByPath')]
        [System.IO.DirectoryInfo]$SolutionConfigPath,

        #SolutionConfig
        [Parameter(Mandatory = $true, ParameterSetName = 'NoRemoting_SolutionByObject')]
        [psobject]$SolutionConfigObject
    )
    
    Process
    {
        #Initialize SolutionConfiguration
        try
        {
            Write-Verbose "Initialize SolutionConfiguration started"
            
            switch ($PSCmdlet.ParameterSetName)
            {
                'NoRemoting_SolutionByPath'
                {
                    $SolutionConfig = Get-PSSolutionConfiguration -Path $SolutionConfigPath.FullName -ErrorAction Stop
                    break
                }
                'NoRemoting_SolutionByObject'
                {
                    $SolutionConfig = $SolutionConfigObject
                    break
                }
                default
                {
                    throw "Unknown ParameterSetName: $($PSCmdlet.ParameterSetName)"
                }
            }

            Write-Verbose "Initialize SolutionConfiguration completed"
        }
        catch
        {
            Write-Error "Initialize SolutionConfiguration failed. Details: $_" -ErrorAction 'Stop'
        }

        #Clear Modules
        try
        {
            Write-Verbose "Clear Modules started"
        
            foreach ($Entry in $SolutionConfig.SolutionStructure.ModulesPath)
            {
                if ($Entry.BuildPath -and (Test-Path -Path $Entry.BuildPath))
                {
                    Write-Verbose "Clear Modules in proress. Deleting PSModuleBuildPath: $($Entry.BuildPath)"
                    Remove-Item -Path $Entry.BuildPath -Force -Recurse -ErrorAction Stop
                }            
            }
      
            Write-Verbose "Clear Modules completed"
        }
        catch
        {
            Write-Error "Clear Modules failed. Details: $_" -ErrorAction 'Stop'
        }

        #Clear Scripts
        try
        {
            Write-Verbose "Clear Scripts started"
            
            foreach ($Entry in $SolutionConfig.SolutionStructure.ScriptPath)
            {
                if ($Entry.BuildPath -and (Test-Path -Path $Entry.BuildPath))
                {
                    Write-Verbose "Clear Scripts in proress. Deleting PSScriptBuildPath: $($Entry.BuildPath)"
                    Remove-Item -Path $Entry.BuildPath -Force -Recurse -ErrorAction Stop
                }
                
                if ($Entry.DependencyDestinationPath -and (Test-Path -Path $Entry.DependencyDestinationPath))
                {
                    Write-Verbose "Clear Scripts in proress. Deleting PSScriptDependencyDestinationPath: $($Entry.DependencyDestinationPath)"
                    Remove-Item -Path $Entry.DependencyDestinationPath -Force -Recurse -ErrorAction Stop
                }
            }
                  
            Write-Verbose "Clear Scripts completed"
        }
        catch
        {
            Write-Error "Clear Scripts failed. Details: $_" -ErrorAction 'Stop'
        }
    }
}

function Assert-PSRepository
{
    [CmdletBinding()]
    [OutputType([void])]
    param
    (
        #PSGetRepository
        [Parameter(Mandatory = $true)]
        [hashtable[]]$PSGetRepository,
        
        #Proxy
        [Parameter(Mandatory = $false)]
        [uri]$Proxy
    )

    Process
    {
        foreach ($Repo in $PSGetRepository)
        {
            #Check if Repository is already registered
            $RepoFound = $false
            try
            {
                $RepoCheck = Get-PSRepository -Name $Repo.Name -ErrorAction Stop
                if ($RepoCheck)
                {
                    if ($Repo.ContainsKey('SourceLocation') -and ($RepoCheck.SourceLocation -ne $Repo.SourceLocation))
                    {
                        $SetPSRepository_Params = @{
                            Name           = $Repo.Name
                            SourceLocation = $Repo.SourceLocation
                        }
                        if ($PSBoundParameters.ContainsKey('Proxy'))
                        {
                            $SetPSRepository_Params.Add('Proxy', $Proxy)
                        }
                        Set-PSRepository @SetPSRepository_Params -ErrorAction Stop
                    }
                    if ($Repo.ContainsKey('PublishLocation') -and ($RepoCheck.PublishLocation -ne $Repo.PublishLocation))
                    {
                        $SetPSRepository_Params = @{
                            Name           = $Repo.Name
                            SourceLocation = $Repo.PublishLocation
                        }
                        if ($PSBoundParameters.ContainsKey('Proxy'))
                        {
                            $SetPSRepository_Params.Add('Proxy', $Proxy)
                        }
                        Set-PSRepository @SetPSRepository_Params -ErrorAction Stop
                    }

                    $RepoFound = $true
                }
            }
            catch
            {

            }
            
            if (-not $RepoFound)
            {
                $RegisterPSRepository_Params = @{ } + $Repo
                if ($PSBoundParameters.ContainsKey('Proxy'))
                {
                    $RegisterPSRepository_Params.Add('Proxy', $Proxy)
                }
                $null = Register-PSRepository @RegisterPSRepository_Params -ErrorAction Stop
            }
        }
    }
}

#endregion

#region private Variables

$PSNativeModules = @(
    'Microsoft.PowerShell.Management'
    'Microsoft.PowerShell.Core'
    'Microsoft.PowerShell.Utility'
    'Microsoft.PowerShell.Security'
    'Microsoft.PowerShell.Archive'
)

#endregion