bartender.psm1

<#
Module Mixed by BarTender
    A Framework for making PowerShell Modules
    Version: 6.1.21
    Author: Adrian.Andersson
    Copyright: 2019 Domain Group

Module Details:
    Module: bartender
    Description: A Framework for making PowerShell Modules
    Revision: 6.1.21.4
    Author: Adrian.Andersson
    Company: Domain Group

Check Manifest for more details
#>


function get-btScriptText
{
    <#
        .SYNOPSIS
            Get the text from file
             
        .DESCRIPTION
            Get the text from file
            Can do some clean-up for you based on parameters used
             
        .PARAMETER psfile
            Mandatory
            Accepts array
            Full filepath(s) to the script file to get the text from.
         
         
        .PARAMETER isFunction
           Tell the script to capture the function names as function-resources
           For building manifest
         
        .PARAMETER isDSCClass
           Tell the script to capture the class names as dsc-resources
           For building manifest
        .PARAMETER removeQuotes
           Remove single line quotes like this #quote quote quote
        .PARAMETER trimSpaces
            Get rid of horizontal space, trimming excess spaces around text and removing any spacing
        .PARAMETER RemoveEmptyLines
           Get rid of most empty lines
             
        .EXAMPLE
            $items = get-btScriptItems .\source\functions\
            $text = get-btScriptText $items.fileList.fullname -isFunction $true
             
            #### DESCRIPTION
            Use the filelist provided by get-btScriptItems
            Grab the contents of all the ps1 files
            Ensure that we capture the function names for our Manifest use later
             
             
            #### OUTPUT
               TypeName: System.Management.Automation.PSCustomObject
                Name MemberType Definition
                ---- ---------- ----------
                dscResources NoteProperty Object[] dscResources=System.Object[]
                functionResources NoteProperty Object[] functionResources=System.Object[]
                output NoteProperty string output=...
             
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2018-05-17
             
             
           Changelog:
                 
                     
                2018-04-19 - AA
                     
                    - Initial Script
                    - Added primary functionality
                 
                2018-04-24 - AA
                     
                    - Made it return object rather than just text-block
                2018-04-26 - AA
                     
                    - Fixed the help
                    - Fixed the way it grabbed file contents
                    - Improved the capture of functions and dsc modules
                2018-05-17 - AA
                     
                    - Updated help
                     
                     
        .COMPONENT
            Bartender
        .INPUTS
           null
        .OUTPUTS
            custom object
    #>

    [CmdletBinding()]
    PARAM(
        [Parameter(Mandatory=$true)]
        [string[]]$psfile,
        [bool]$isFunction = $false,
        [bool]$isDSCClass = $false,
        [bool]$removeQuotes = $true,
        [bool]$trimSpaces = $false,
        [bool]$RemoveEmptyLines = $true
        
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $$(MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $$($MyInvocation.BoundParameters|Out-String)"
        $outputObj = [pscustomobject]@{
            output = ""
            functionResources = @()
            dscResources = @()
        }
    }
    
    process{
        foreach($file in $psfile)
        {
            $content = get-content $file
            $lineNo = 1
            $content = foreach($line in $content)
            {
                if($isFunction -eq $true)
                {
                    if($line -like 'function *')
                    {
                        Write-Verbose "FOUND FUNCTION: $($file)`n`tLine:$lineNo"
                        $functionName = $($line -split 'function ')[1]
                        if($functionname -like '*{*')
                        {
                            $functionName = $($functionName -split '{')[0]
                        }
                        $functionName = $functionName.trim()
                        $outputObj.functionResources += $functionName
                        Write-Verbose "Adding $functionName to function resources"
                        remove-variable functionName -ErrorAction SilentlyContinue
                    }
                }
                if($isDSCClass -eq $true)
                {
                    if($line -like 'class *')
                    {
                        Write-Verbose "FOUND CLASS: $($file)`n`tLine:$lineNo"
                        $dscClassName = $($line -split 'class ')[1]
                        write-verbose $dscClassName
                        if($dscClassName -like '*{*')
                        {
                            $dscClassName = $($dscClassName -split '{')[0]
                        }
                        $dscClassName = $dscClassName.trim()
                        $outputObj.dscResources += $dscClassName
                        Write-Verbose "Adding $dscClassName to dscResources resources"
                        remove-variable functionName -ErrorAction SilentlyContinue
                    }
                }
                if($removeQuotes -eq $true)
                {
                    
                    if($line -like '*#*' -and (($line -notlike '*<#*') -and ($line -notlike '*#>*')))
                    {
                        Write-Verbose 'Scrubbing quotes'
                        $line = $($line -split '#')[0]
                    }
                }
                if($trimSpaces -eq $true)
                {
                    $line = $line.trim()
                }
                if($RemoveEmptyLines -eq $true)
                {
                    if($line.length -gt 0)
                    {
                        $line
                    }
                }else{
                    $line
                }
                $lineNo++
            }
            if($RemoveEmptyLines -eq $true)
            {
                $content = $content | where-object {$_ -ne "^\s+" -and $_ -ne ''}
            }
            $outputObj.output += "`n$($content|out-string)"
        }
        $outputObj
        
    }
   
    
}

function publish-btModule
{
    <#
        .SYNOPSIS
            Grab the latest version from the bin folder
            Push it to a nuget repository
             
        .DESCRIPTION
            Grab the latest version from the bin folder
            Push it to a nuget repository
            Will pull the details from the btconfig.xml if params not supplied
             
        .PARAMETER Repository
            What repository are we publishing to
        .PARAMETER token
            Nuget API token
            Will use the publishToken.txt if this is not supplied
            Will only warn if not provided in case you dont need one
             i.e. you are pushing to a fileshare repository
        .PARAMETER configFile
            The Config File to pull the details from
            Will use btconfig.xml by default
        .EXAMPLE
            publish-btModule
             
            #### DESCRIPTION
            Grab the details from btconfig.xml
            Use the nuget api token in publishtoken.txt if there is one
            Grab the latest version of the module from the bin folder
            Initiate a publish-module command with these details
             
             
            #### OUTPUT
            Hopefully, your module has been pushed to your repository
             
             
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2018-05-17
             
             
           Changelog:
                2018-04-19 - AA
                     
                    - Added token and repository details
                    - Initial Script
                    - Tested working
                     
                2018-04-26 - AA
                     
                    - Fixed the help
                2018-05-17 - AA
                     
                    - Updated help
                2019-02-04 - AA
                    - Use the saved Repository settings if available
                        - Do it in such a way that it can be overwritten
                    - Removed the secrets loader
                    - Made the repository paramater required
                    - No longer grab the repository from the config
                        - This will now be passed with a forEach in the start-btBuild
                     
        .COMPONENT
            Bartender
    #>

    [CmdletBinding()]
    PARAM(
        [Parameter(Mandatory=$true)]
        [Alias("repo")]
        [string]$Repository,
        [string]$token,
        [pscredential]$credential,
        [string]$configFile = 'btconfig.xml'
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $$(MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $$($MyInvocation.BoundParameters|Out-String)"
        $invocationPath = (Get-Item -Path ".\").FullName
        Write-Verbose "Working directory: $invocationPath"
        write-verbose 'Checking if we need to import repository credentials'
        if(($token -eq $null) -or ($credential -eq $null))
        {
            write-verbose 'Token and/Or Credential not supplied, checking saved repository settings'
            $btRepository = get-btRepository -repository $repository
            if($btRepository)
            {
                
                if((!$token) -and ($btRepository.NuGetApiKey -ne $null))
                {
                    write-verbose 'Using saved Nuget API Token'
                    $token = $btRepository.NuGetApiKey
                }else{
                    write-verbose 'Using supplied token'
                    write-verbose "Supplied Token: $token"
                    write-verbose "repo Token: $($btRepository.NuGetApiKey)"
                }
                if(($credential -eq $null) -and ($btRepository.credential -ne $null))
                {
                    write-verbose 'Using saved credential'
                    $credential = $btRepository.credential
                }else{
                    write-verbose 'Using supplied credential'
                    write-verbose "Supplied Username: $($credential.username)"
                    write-verbose "Repo Username: $($btRepository.credential.username)"
                }
            }else{
                write-verbose "Saved repository settings not found for: $repository"
                write-verbose 'You can save credentials and nuget API token with the save-btRepository cmdlet'
            }
        }
        write-verbose 'Checking token'
        
        if(!$token -or $token.length -lt 1)
        {
            Write-Warning 'Token invalid or not provided, may be ok if publishing to a fileshare'
        }
        if(!$credential -or $($credential.GetType().name) -ne 'PSCredential')
        {
            Write-Warning 'Invalid Credential, may be ok if there are no module dependancies, or your repository does not require credentials for read access'
        }
        
        $configFilePath = "$invocationPath\$($configFile)"
        $throwExceptions = @{}
        #NoConfig
        $errCat = [System.Management.Automation.ErrorCategory]::InvalidData
        $errMsg = [System.Exception]::new("Config file was not found.`nUse new-btproject for a new project.")
        $throwExceptions.noConfigError = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$configFilePath)
        #BadConfig
        $errMsg = [System.Exception]::new('Config file contents unexpected or malformed.')
        $throwExceptions.badConfigError = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$configFilePath)
        #NoRepository
        $errMsg = [System.Exception]::new('No repository specified. Unable to continue')
        $throwExceptions.noRepository = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$configFilePath)
        
        
    }
    
    process{
        write-verbose "InvocationPath: $invocationPath"
        write-verbose "configfile: $configfile"
        write-verbose 'Validating Config File'
        
        if(!$(test-path $configFilePath))
        {
            throw $throwExceptions.noConfigError
        }else{
            $config = Import-Clixml $configFilePath
        }
        
        if(!$repository)
        {
            throw 'No repository and/or ModuleName provided'
        }
        
        Write-Verbose "Repository specified as: $repository"
        #Check the repository is setup
        try{
            $rep = get-psrepository $repository -ErrorAction Stop
            $rep|out-null
        }catch{
            write-error "Repository $($repository) is not currently configured.`nPlease configure your publish repository first or specify a different one"
            return
        }
        
        Write-Verbose 'Finding the latest version'
        $version = $config.versionAsTag
        $moduleFolder = "$invocationPath\$($config.modulename)\$version"
        $manifestFile = "$moduleFolder\$($config.modulename).psd1"
        $moduleFile = "$moduleFolder\$($config.modulename).psm1"
        Write-Verbose 'Checking files/folders exist'
        Write-Verbose "Module should live in folder: $moduleFolder`n`tmanifest: $manifestFile`n`tmodule: $moduleFile"
        if(!$(test-path $moduleFolder) -or !$(test-path $manifestFile) -or !$(test-path $moduleFile))
        {
            Write-Error 'Unable to find relevant build for current version. Unable to continue'
            return
        }
        
        Write-Verbose 'Finalising the command params'
        $splat = @{
            Path = $moduleFolder
            Repository =  $Repository
        }
       
        if($token)
        {
            $splat.NugetApiKey = $token
        }
        if($credential)
        {
            $splat.credential = $credential
        }
        
        Write-Verbose $($splat|Out-String)
        Publish-Module @splat
    }
}

function clear-btRepository
{
    <#
        .SYNOPSIS
            Find and remove a saved repository
             
        .DESCRIPTION
            Removes saved repository settings
         
        .PARAMETER repository
            Name of the repository to use the credentials against
                         
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2019-03-04
             
             
            Changelog:
                2019-02-01 - AA
                     
                    - Initial Script
                2019-02-01 - AA
                     
                    - Fixed help
                        - Still had get-btRepository documentation by accident
                     
        .COMPONENT
            What cmdlet does this script live in
    #>

    [CmdletBinding()]
    PARAM(
        [Parameter(Position=0,Mandatory=$true)]
        [string]$repository,
        [switch]$force
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        
        #Where should we save the module
        $localPath = "$($env:userprofile)\AppData\local"
        $btSaveFolder = "$localPath\bartender"
        $btRepositoriesPath = "$btSaveFolder\btRepositories_$($env:computername).xml"
        
    }
    
    process{
        
        write-verbose 'Importing existing saved repositories'
        if(test-path $btRepositoriesPath)
        {
            try{
                $btRepositories = import-clixml $btRepositoriesPath -errorAction stop
            }catch{
                write-warning 'File was found but unable to import'
                return
            }
        }else{
            write-warning 'Repository not found'
            return
        }
        write-verbose 'Checking we have settings'
        if($btRepositories."$repository")
        {
            write-verbose "Repository $repository found"
            if(!$force)
            {
                $confirm = read-host "Are you sure you wish to remove the $repository repository`n(Use -force switch to suppress this message)`nEnter 'Y' to confirm:"
                if($confirm -eq 'y')
                {
                    write-warning "$repository will be removed from save state"
                    $btRepositories.remove("$repository")
                    $btRepositories|export-clixml $btRepositoriesPath -force
                }else{
                    write-warning "Leaving $repository Repository in saved state"
                }
            }else{
                write-warning "-force param used, removing repository"
                $btRepositories.remove("$repository")
                $btRepositories|export-clixml $btRepositoriesPath -force
            }
            
        }else{
            write-warning 'Repository settings not found'
            return
        }
    }
}

function get-btChangeDetails
{
    <#
        .SYNOPSIS
            Try and work out what function files changed, were created etc, from the last release
             
        .DESCRIPTION
            Gets the lastModified date of the previous release module manifest
            Check the source function folders with get-btFolderItems
            See what functions live in there
            See if the lastModified is after the previous release
            Note that the functions were potentially changed
             
        .PARAMETER modulePath
            Path to module
        .PARAMETER functionFolders
            What source folders do functions live in
        .PARAMETER configFile
            btconfig.xml
        .PARAMETER newRelease
            If this is set to true, will calculate the differences between previousrelease and lastrelease
            as configered in the config file
            By default, it will calculate the differences between the lastRelease and the last revision instead
        ------------
        .EXAMPLE
            get-btChangeDetails
             
             
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2019-03-06
             
             
            Changelog:
                2019-03-06 - AA
                     
                    - Initial Script
                2019-03-11 - AA
                    - Changed to read the lastrelease and previousrelease from the config module
                    - Broke the summary into smaller portions was getting a bit hectic
                     
        .COMPONENT
            What cmdlet does this script live in
    #>

    [CmdletBinding()]
    PARAM(
        [string]$modulePath = $(get-location).path,
        [string[]]$functionFolders = @('functions','private'),
        [string]$configFile = 'btConfig.xml',
        [switch]$newRelease
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        if($modulePath -like '*\')
        {
            Write-Verbose 'Superfluous \ found in path, removing'
            $modulePath = $modulePath.Substring(0,$($modulePath.Length-1))
            Write-Verbose "New path = $modulePath"
        }
        $sourcePath = get-item "$modulePath\source" -ErrorAction Ignore
        
    }
    
    process{
        write-verbose 'Getting config settings'
        $configSettings = import-clixml "$modulePath\$configFile"
        if(!$configSettings)
        {
            throw 'Unable to find config file'
        }
        write-verbose 'Validating Module path'
        if(!$(test-path $modulePath) -or !$(test-path $sourcePath))
        {
            throw 'modulePath invalid'
        }
        if($newRelease)
        {
            write-verbose 'Comparing last Release to previous release'
            if($configSettings.lastrelease.version -and $configSettings.lastRelease.date)
            {
                $currentRelease = $configSettings.lastrelease.version.toString(3)
                $currentReleaseDate = $configSettilgs.lastRelease.date
                $currentReleaseModulePath = $(get-item "$modulePath\$($configSettings.moduleName)\$currentRelease\$($configSettings.moduleName).psm1"-ErrorAction ignore).FullName
                
            }else{
                write-warning 'lastrelease not found, exiting'
                return
            }
            if($configSettings.previousrelease.version -and $configSettings.previousrelease.date)
            {
                write-verbose 'Using previous release as comparison'
                $previousRelease = $configSettings.previousrelease.version.toString(3)
                $previousReleaseDate = $configSettings.previousrelease.date
                $previousReleaseModulePath = $(get-item "$modulePath\$($configSettings.moduleName)\$previousRelease\$($configSettings.moduleName).psm1" -ErrorAction ignore).FullName
            }else{
                write-warning 'Previous Release not found, using empty version and old date'
                $previousRelease = [version]'0.0.0'
                $previousReleaseDate = $null
                $previousReleaseModulePath = $null
            }
             
        }else{
            write-verbose 'comparing last revision to last release'
            $currentRelease = $(get-childitem "$modulePath\rev" |where-object{$_.PsIsContainer -eq $true}|sort-object -Property lastWriteTime -Descending|select-object -first 1).basename
            $currentReleaseDate = $(get-date)
            $currentReleaseModulePath = $(get-item "$modulePath\rev\$currentRelease\$($configSettings.moduleName).psm1"-ErrorAction ignore).FullName
            if($configSettings.lastrelease.version -and $configSettings.lastRelease.date)
            {
                write-verbose 'Using lastRelease as comparison'
                $previousRelease = $configSettings.lastrelease.version.toString(3)
                $previousReleaseDate = $configSettings.lastRelease.date
                $previousReleaseModulePath = $(get-item "$modulePath\$($configSettings.moduleName)\$previousRelease\$($configSettings.moduleName).psm1" -ErrorAction ignore).FullName
            }else{
                write-warning 'Previous Release not found, using empty version and old date'
                $previousRelease = [version]'0.0.0'
                $previousReleaseDate = $null
                $previousReleaseModulePath = $null
            }
        }
        
        
        $functions = foreach($folder in $functionFolders)
        {
            write-verbose "Checking folder: $folder"
            $folderPath = "$sourcePath\$folder"
            write-verbose "FullPath: $folderPath"
            if(!(test-path $folderPath))
            {
                throw "function path for $folder folder invalid"
            }
            $folderScripts = get-btFolderItems -path $folderPath
            if($newRelease)
            {
                write-verbose 'Working out markdown path'
                $markdownPath = $(get-item "$modulePath\documentation\$currentRelease\functions" -ErrorAction Ignore).FullName
            }else{
                $markdownPath -eq $null
            }
            
            #Get the markdowns
            if($currentRelease)
            {
                if($markdownPath)
                {
                    $markdownItems = get-childitem $markdownPath -Filter *.md
                }else{
                    write-warning 'Markdown items not found'
                    $markdownItems = $null
                }
            }
            write-verbose "PreviousReleaseDate: $previousReleaseDate"
            foreach($file in $folderScripts)
            {
                write-verbose "Checking file: $($file.path)"
                $fileFunctions = get-btScriptFunctions -path $($file.path)
                write-verbose "Checking Functions Functions"
                foreach($function in $fileFunctions)
                {
                    $fileItem = get-item $($file.path)
                    $fileIsNew = if(($fileItem.CreationTime -gt $previousReleaseDate) -and ($fileItem.LastWriteTime -gt $previousReleaseDate)){$true}else{$false}
                    $fileIsModified = if(($fileItem.LastWriteTime -gt $previousReleaseDate) -and ($fileIsNew -eq $false)){$true}else{$false}
                    $markdownList = $($markdownItems|where-object{$_.length -gt 400}).basename
                    $hasMarkdown = if($function -in $markdownList)
                    {
                        $true
                    }else{
                        $false
                    }
                    [psCustomObject] @{
                        fileLastModified = $fileItem.LastWriteTime
                        fileCreated = $fileItem.CreationTime
                        filename = $fileItem.Name
                        filePath = ".\source\$folder$($file.relativePath.replace('.\','\'))"
                        relativePath = $file.relativePath
                        fileIsNew = $fileIsNew
                        fileIsModified = $fileIsModified
                        function = $function
                        folder = $folder
                        hasmarkdown = $hasMarkdown
                    }
                }
            }
        }
        $fileSelector = @(
            'name',
            'extension',
            'basename',
            'lastwritetime',
            'creationtime',
            @{
                name = 'relativepath'
                expression = {$($_.fullname).replace("$sourcePath",'.')}
            },
            @{
                name = 'fileIsNew'
                expression = {$_.CreationTime -gt $previousReleaseDate -and $_.lastWritetime -gt $previousReleaseDate}
            },
            @{
                name = 'fileIsModified'
                expression = {$_.lastWriteTime -gt $previousReleaseDate -and $_.creationTime -lt $previousReleaseDate}
            },
            @{
                name = 'sourceDirectory'
                expression = {$($_.directory.fullname).replace("$sourcePath\",'').split('\')[0]}
            },
            'length'
        )
        
        $files = get-childitem -path $sourcePath -file -Recurse -Exclude @('.btorderend','.btorderstart','.btignore','.gitignore')|select-object $fileSelector -unique
        if($currentReleaseModulePath -and $previousReleaseModulePath)
        {
            write-verbose 'Check if the module files are similar'
            $fileCompare = $(get-btStringComparison -string1 $(get-content $currentReleaseModulePath|out-string) -string2 $(get-content $previousReleaseModulePath|out-string)).DiffPercent
        }else{
            $fileCompare = 'na'
        }
        
        write-verbose 'Getting Summary Details'
        $publicFunctions = $($functions|where-object{$_.folder -ne 'private'})
        $publicFunctionsCount = $($publicFunctions |measure-object).count
        $publicFunctionsWithMarkdown = $($publicFunctions|where-object{$_.hasMarkdown -eq $true}|measure-object ).count
        if($publicFunctionsCount -ge 1)
        {
            $commentBasedHelpCoverage = [math]::round($($publicFunctionsWithMarkdown/$publicFunctionsCount)*100,0)
        }else{
            $commentBasedHelpCoverage = 0
        }
     
        $summary = [ordered]@{
            commentBasedHelpCoverage = $commentBasedHelpCoverage
            version = $currentRelease
            comparisonVersion = $previousRelease
            estimatedChangePercent = "$fileCompare %"
        }
        $fileSummary = @{
            totalFiles = $($files|measure-object).Count
            newFiles = $($files|where-object{$_.fileIsNew -eq $true}|measure-object).Count
            modifiedFiles = $($files|where-object{$_.fileIsModified -eq $true}|measure-object).Count
            totalFileSize = "$([math]::Round($($($files|measure-object -Property length -Sum).sum),2) / 1kb) kb"
        }
        $functionSummary = @{
            totalFunctions = $($functions|measure-object).Count
            newFunctions = $($functions|where-object{$_.fileIsNew -eq $true}|measure-object).Count
            modifiedFunctions = $($functions|where-object{$_.fileIsModified -eq $true}|measure-object).Count
            privateFunctions = $($functions|where-object{$_.folder -eq 'private'}|measure-object).Count
            publicFunctions = $publicFunctionsCount
        }
        [pscustomObject]@{
            summary = $summary
            files = $files
            functions = $functions
            functionSummary = $functionSummary
            fileSummary = $fileSummary
        }
    }
}

function get-btDefaultSettings
{
    <#
        .SYNOPSIS
            Retrieve the current users default BT Settings
             
        .DESCRIPTION
            Retrieve the current users default BT Settings
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: yyyy-mm-dd
             
             
            Changelog:
                2019-02-01 - AA
                     
                    - Initial Script
                        - Unsure what to do about LINUX and PSCORE
                        - Obviously the save path is less than ideal
                        - Also unsure what will happen with roaming profiles
                        - What if we include _this_ computername in the filename
                     
        .COMPONENT
            What cmdlet does this script live in
    #>

    [CmdletBinding()]
    PARAM(
        
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
      
    }
    
    process{
        #Where should we save the module
        write-verbose 'Getting Save location'
        $localPath = "$($env:userprofile)\AppData\local"
        if(test-path $localPath)
        {
            $btSaveFolder = "$localPath\bartender"
            $btDefaultsPath = "$btSaveFolder\btDefaults_$($env:computername).xml"
            if(!$(test-path $btSaveFolder))
            {
                write-verbose 'bt path not found, no settings saved'
            }else{
                write-verbose 'bt path exists'
            }
        }else{
            throw 'Local Profile unavailable'
        }
        
        
        write-verbose 'Importing existing default settings'
        if(test-path $btDefaultsPath)
        {
            try{
                
                $btDefaultSettings = import-clixml $btDefaultsPath -errorAction stop
                write-verbose 'Existing saved defaults imported'
                return $btDefaultSettings
            }catch{
                write-error $error[0]
                throw 'Unable to import defaults settings'
            }
        }else{
            write-verbose 'Previous settings not found'
        }
    }
    
}

function get-btFolderItems
{
    <#
        .SYNOPSIS
            Get a list of files from a folder - whilst processing the .btignore and .btorder files
             
        .DESCRIPTION
            Get the files out of a folder. Adds a bit of smarts to it such as:
             - Ignore anything in the .btignore file
             - Order files in the .btorderStart folder first
             - Order any files in the .btOrderEnd folder
             - Randomly add any files that are not in either in between
             - Filter out anything that isn't a PS1 file by default if required
             - Copy the items to a new location
             
            Notes:
              A filename can be in both .btOrderStart and .btOrderEnd
              This can be useful if you would like to process a file twice, once at the start of a workflow and once at the end
              Will always return a full path name
             
        .PARAMETER path
            The path of your bartender module
            Defaults to current working directory
         
        .PARAMETER psScriptsOnly
            Filter out any file that is not a ps1 file
        .PARAMETER copy
            If specified, will copy any found files to the location specified in Destination
         
        .PARAMETER Destination
            If specified with the copy switch, will copy any found files to this location
         
         
             
        .EXAMPLE
            get-btFolderItems -path .\source\functions
             
            ##### DESCRIPTION
            Get all files in the path .\source\functions, that are not in the .btIgnorefile, order by .btOrderStart and .btOrderEnd respectively
        .EXAMPLE
            get-btFolderItems -path .\source\functions -psScriptsOnly
             
            ##### DESCRIPTION
            Get PS1 files in the path .\source\functions, that are not in the .btIgnorefile order by .btOrderStart and .btOrderEnd respectively
        .EXAMPLE
            get-btFolderItems -path .\source\functions -psScriptsOnly -copy -destination 'c:\temp\functions'
             
            ##### DESCRIPTION
            Get PS1 files in the path .\source\functions, that are not in the .btIgnorefile order by .btOrderStart and .btOrderEnd respectively
            Copy them (inclusive of directory structure) to 'c:\temp\functions'
         
        .OUTPUTS
          Should return an object with a list of files in their ordered version.
          By default will have values of:
           - Path : Full path to file
           - relativePath : dot Source notatio for file
          If Copy is used, it will also have values for:
           - NewPath : The full path to where it was copied
           - NewFolder : New folder path
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2018-05-17
             
             
            Changelog:
                2018-04-19 - AA
                     
                    - Initial Script
                    - Added primary functionality
                 2018-04-24- AA
                     
                    - Added .btOrder file
                    - Added ability to copy files
                 2018-05-16 - AA
                     
                    - Added inline help
                    - Included in pester tests
                2018-05-17 - AA
                     
                    - Improved inline help
                    - Added ability to process .btorderEnd and .btOrderStart
                    - Removed .btOrder processing
                    - Fixed a bug with empty lines in the .btOrder* files
                2019-03-11 - AA
                    - Changed a warning to a write-verbose
                     
                     
        .COMPONENT
            Bartender
        .INPUTS
           null
        .OUTPUTS
            custom object
         
    #>

    [CmdletBinding(DefaultParameterSetName='Default')]
    PARAM(
        [Parameter(ParameterSetName='Default',Mandatory=$true,Position=1)]
        [Parameter(ParameterSetName='SetDestination',Mandatory=$true,Position=1)]
        [string]$Path,
        [Parameter(ParameterSetName='Default',Position=2)]
        [Parameter(ParameterSetName='SetDestination',Position=2)]
        [switch]$psScriptsOnly,
        [Parameter(Mandatory=$false)]
        [Parameter(ParameterSetName='Default',Position=3)]
        [Parameter(ParameterSetName='SetDestination',Mandatory=$true,Position=3)]
        [string]$Destination,
        [Parameter(ParameterSetName='SetDestination',DontShow=$true,Position=6)]
        [switch]$SetDestination = $(if($destination){$true}else{$false}),
        [Parameter(ParameterSetName='SetDestination',Position=4)]
        [switch]$copy
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        if($path[-1] -eq '\')
        {
            write-verbose 'Removing extra \ from path'
            $path = $path.Substring(0,$($path.length-1))
            write-verbose "New Path $path"
        }
        if($Destination[-1] -eq '\')
        {
            write-verbose 'Removing extra \ from path'
            $Destination = $Destination.Substring(0,$($Destination.length-1))
            write-verbose "New destination: $destination"
        }
    }
    
    process{
        try{
            $folder = get-item $path -erroraction stop
        }catch{
            Write-Error 'Unable to retrieve folder'
            return
        }
        if($Destination)
        {
            try{
                $destinationFolder = get-item $Destination -erroraction stop
                $destinationFolder = $destinationFolder.FullName
            }catch{
                Write-verbose 'Unable to verify destination folder - using assigned variable as path '
                $destinationFolder = $Destination
            }
            
            Write-Verbose "Destination set to $destination"
        }
            
        $folderPath = $folder.FullName
        $exclude = @('.btignore','.btorderStart','.btorderEnd','.gitignore')
        if(test-path "$folderPath\.btignore")
        {
            write-verbose 'Adding the exclude content'
            $exclude += get-content "$folderPath\.btignore"|where-object {$_.length -gt 1}
            
        }else{
            if($psScriptsOnly)
            {
                write-warning 'NO .btignore found. All PS1 scripts will be included'
            }else{
                write-warning 'NO .btignore found. All files will be included'
            }
            
        }
        write-verbose "Exclude:`n`n $($($exclude|format-list|Out-String))"
    
        Write-Verbose 'Getting all files'
        if($psScriptsOnly)
        {
            $filelist = get-childitem -Path $folderPath -Recurse -Filter *.ps1|Where-Object{$_.PSIsContainer -eq $false }
        }else{
            $filelist = get-childitem -Path $folderPath -Recurse|Where-Object{$_.PSIsContainer -eq $false}
        }
        write-verbose "Checking: $($($filelist|measure-object).Count) files"
        #Crack this today. Fix the destination, give it a new folder path
        
        write-verbose "FolderFullname`n$("$($folder.FullName)\")"
        foreach($file in $fileList)
        {
            if($SetDestination)
            {
                $file|Add-Member -Name 'newPath' -MemberType NoteProperty -Value $($file.fullname.ToString()).replace($folder.FullName,$destinationFolder)
                $file|Add-Member -name 'newFolder' -memberType NoteProperty -value $($file.directory.ToString()).replace($folder.FullName,$destinationFolder)
            } 
            $file|Add-Member -Name 'relativePath' -MemberType NoteProperty -Value $($file.fullname.ToString()).replace("$($folder.FullName)\",'.\')
        }
        Write-Verbose 'Checking File Order'
        $orderedList = [ordered]@{}
        $i=0
        if(test-path "$folderPath\.btorderStart")
        {
            $order = get-content "$folderPath\.btorderStart"|where-object {$_.length -gt 1}
            
            foreach($file in $order)
            {
                $listItem = $fileList |Where-Object {($_.name -eq $file -or $_.BaseName -eq $file -or $_.relativePath -eq $file)-and($_.name -notin $exclude -and $_.BaseName -notin $exclude -and $_.relativePath -notin $exclude)}|select-object -first 1
                if($listItem)
                {
                    $orderedList."$i" = $listItem
                    $i++
                }
            }
        }else{
            write-warning 'NO .btorderStart found. Start Order will be random'
        }
        if(test-path "$folderpath\.btOrderEnd")
        {
            $orderEnd = get-content "$folderPath\.btOrderEnd"|where-object {$_.length -gt 1}
        }else{
            write-warning 'NO .btorderEnd found. End Order will be random'
        }
        Write-Verbose 'Excluding any items and adding to list'
        foreach($listItem in $($fileList|where-object {($_.name -notin $order -and $_.BaseName -notin $order -and $_.relativePath -notin $order)-and($_.name -notin $orderEnd -and $_.BaseName -notin $orderEnd -and $_.relativePath -notin $orderEnd)-and($_.name -notin $exclude -and $_.BaseName -notin $exclude -and $_.relativePath -notin $exclude)}|sort-object))
        {
            $orderedList."$i" = $listItem
            $i++
        }
        foreach($file in $orderEnd)
        {
            $listItem = $fileList |Where-Object {($_.name -eq $file -or $_.BaseName -eq $file -or $_.relativePath -eq $file)-and($_.name -notin $exclude -and $_.BaseName -notin $exclude -and $_.relativePath -notin $exclude)}|select-object -first 1
            if($listItem)
            {
                $orderedList."$i" = $listItem
                $i++
            }
        }
        $fileListValues = if($SetDestination)
        {
            $orderedList.Values|Select-Object @{Name = 'Path'; Expression = {$_.FullName} },relativePath,newPath,newFolder
        }else{
            $orderedList.Values|Select-Object @{Name = 'Path'; Expression = {$_.FullName} },relativePath
        }
        if($copy)
        {
            Write-Verbose 'Copying new files'
            foreach($file in $fileListValues)
            {
                write-verbose "Checking file $($file.relativepath)"
                write-verbose "Destination Folder: $($file.newFolder)"
                if(!(test-path $file.newFolder))
                {
                    write-verbose 'Destination folder does not exist'
                    try{
                        new-item -ItemType Directory -Path $file.newFolder -Force |Out-Null
                        write-verbose "Made new directory at: `n`t$($file.newFolder)"
                    }catch{
                        write-error "Unable to make directory at: `n`t$($file.newFolder)"
                        return
                    }
                }
                copy-item -Path $file.Path -Destination $file.newPath -Force|Out-Null
            }
            Write-Verbose 'Copy Complete'
        }
        return $fileListValues
        
        
    }
}

function get-btGitDetails
{
    <#
        .SYNOPSIS
            Simple description
             
        .DESCRIPTION
            Detailed Description
             
        .PARAMETER modulePath
            Where does the module live?
             
        ------------
        .EXAMPLE
            verb-noun param1
             
            #### DESCRIPTION
            Line by line of what this example will do
             
             
            #### OUTPUT
            Copy of the output of this line
             
             
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2019-06-03
             
             
            Changelog:
                2019-03-06 - AA
                     
                    - Initial Script
                 
                2019-03-07 - AA
                    - Fixed the git commands to run as a job
                    - Trim the returned data
                     
        .COMPONENT
            What cmdlet does this script live in
    #>

    [CmdletBinding()]
    PARAM(
        [string]$modulePath = $(get-location).path
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        if($modulePath -like '*\')
        {
            Write-Verbose 'Superfluous \ found in path, removing'
            $modulePath = $modulePath.Substring(0,$($modulePath.Length-1))
            Write-Verbose "New path = $modulePath"
        }
        
        
    }
    
    process{
        
        if(!$(test-path $modulePath))
        {
            throw "Module path not found"
        }else{
            $modulePath = $(get-item $modulePath).fullname
        }
        $gitDetailsHash = @{}
        $branchSB = [scriptblock]::Create("set-location $modulePath;git branch")
        $commitSB = [scriptblock]::Create("set-location $modulePath;git rev-parse HEAD")
        $commitShortSB = [scriptblock]::Create("set-location $modulePath;git rev-parse --short HEAD")
        $originSB = [scriptblock]::create("set-location $modulePath;git config --get remote.origin.url")
        write-verbose $branchSB.ToString()
        try{
            $branch = $($($j = start-job $branchSb;wait-job $j|out-null;Receive-Job $j -ErrorAction Stop)|out-string).trim()
        }catch{
            write-warning 'Not a git folder'
            $branch = $null
        }
        
        if($branch)
        {
            $gitDetailsHash.branch = $branch
            $gitDetailsHash.commit = $($($j = start-job $commitSB;wait-job $j|out-null;Receive-Job $j)|out-string).trim()
            $gitDetailsHash.commitShort = $($($j = start-job $commitShortSB;wait-job $j|out-null;Receive-Job $j)|out-string).trim()
            $gitDetailsHash.origin = $($($j = start-job $originSB;wait-job $j|out-null;Receive-Job $j)|out-string).trim()
            [pscustomobject]$gitDetailsHash
        }
        
    }
    
}

function get-btInstalledModule
{
    <#
        .SYNOPSIS
            Get the name and version of an already installed module
             
        .DESCRIPTION
            Get the name and version of an already installed module
            If moduleVersion is specified, ensures its installed
            Otherwise, return the latest version of the module
            Return as a moduleSpecification object
             
        .PARAMETER moduleName
            Name of the module to find
        .PARAMETER moduleVersion
            If you want to find a specific version of a module
             
        ------------
             
             
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2019-02-01
             
             
            Changelog:
                2019-02-01 - AA
                     
                    - Initial Script
                 
                2019-03-04 - AA
                     
                    - Fixed the documentation
                    - Changed the returned object to a modulespecification object
                        - Ensured fed correct hashtable
                     
        .COMPONENT
            What cmdlet does this script live in
    #>

    [CmdletBinding()]
    PARAM(
        [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,Position=0)]
        [string]$moduleName,
        [version]$moduleVersion
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        
    }
    
    process{
        #Array of what we want to select
        #Easier to do this when we want custom params
        $modSelect = @(
            'GUID',
            @{
                Name='ModuleName'
                Expression = {$_.Name}
            },
            @{
                Name='ModuleVersion'
                Expression={$_.Version}
            }
        )
        $allVersions = get-module -listAvailable -name $moduleName|select-object $modSelect
        if($($allVersions|measure-object).count -ge 1)
        {
            write-verbose 'Modules found'
            if($ModuleVersion)
            {
                write-verbose 'Checking for specific version'
                $found = $allVersions|Where-Object{$_.ModuleVersion -eq $moduleVersion}|Select-Object -first 1
                if($found)
                {
                    write-verbose 'Found specific Version'
                    $selectedMod =  $found
                }else{
                    Throw "Module $moduleName was found on this machine but the version: $moduleVersion is not present"
                }
            }else{
                write-verbose 'Getting Latest Version'
                $selectedMod = $allVersions|Sort-Object ModuleVersion -Descending|Select-Object -first 1
            }
        }else{
            throw "No Modules found with the name $moduleName. Please install them first"
        }
        if($selectedMod)
        {
            Write-Debug "$($selectedMod|format-list|out-string)"
            $hashtable = @{
                guid = $selectedMod.guid
                modulename = $selectedMod.moduleName
                requiredversion = $selectedMod.moduleVersion
                #moduleVersion = $selectedMod.moduleVersion # Use moduleVersion or RequiredVersion, but not both
            }
            [Microsoft.PowerShell.Commands.ModuleSpecification]::new($hashtable)
        }
        
    }
    
}

function get-btRepository
{
    <#
        .SYNOPSIS
            Find repository settings, return as a splatable hashtable
             
        .DESCRIPTION
            Find repository settings, return as a splatable hashtable.
            If no repository is found, return null
             
            Also check the repository still exists
         
        .PARAMETER repository
            Name of the repository to use the credentials against
                         
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: yyyy-mm-dd
             
             
            Changelog:
                2019-02-01 - AA
                     
                    - Initial Script
                     
        .COMPONENT
            What cmdlet does this script live in
    #>

    [CmdletBinding(DefaultParameterSetName='single')]
    PARAM(
        
        [Parameter(Position=0,Mandatory=$true,ParameterSetName='single')]
        [string]$repository,
        [Parameter(ParameterSetName='listAvailable')]
        [switch]$listAvailable
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        
        #Where should we save the module
        $localPath = "$($env:userprofile)\AppData\local"
        $btSaveFolder = "$localPath\bartender"
        $btRepositoriesPath = "$btSaveFolder\btRepositories_$($env:computername).xml"
        
    }
    
    process{
        write-verbose 'Importing existing saved repositories'
        if(test-path $btRepositoriesPath)
        {
            try{
                $btRepositories = import-clixml $btRepositoriesPath -errorAction stop
            }catch{
                write-warning 'File was found but unable to import'
                return
            }
        }else{
            write-warning 'No repositories found'
            return
        }
        if($($PSCmdlet.ParameterSetName) -eq 'listAvailable')
        {
            write-verbose 'Getting all repositories'
            foreach($repo in $btRepositories.keys)
            {
                $hash = @{
                    Repository = $repo
                    NuGetApiKey = $($($btRepositories."$repo").token.getNetworkCredential().password)
                }
                if($($btRepositories."$repo".credential))
                {
                    $hash.credential = $btRepositories."$repo".credential
                }
                [pscustomobject]$hash
                remove-variable hash -erroraction 'silentlyContinue'
            }
        }else{
            write-verbose 'Checking we have settings'
            if($btRepositories."$repository")
            {
                write-verbose 'Converting to psget splat hashtable'
                $hash = @{
                    Repository = $repository
                    NuGetApiKey = $($($btRepositories."$repository").token.getNetworkCredential().password)
                }
                if($($btRepositories."$repository".credential))
                {
                    $hash.credential = $btRepositories."$repository".credential
                }
                return $hash
            }else{
                write-warning 'Repository settings not found'
                return
            }
        }
        
        
    }
    
}

function new-btProject
{
    <#
        .SYNOPSIS
            Start a new bartender project
            Make sure you are in a decent folder location as it uses root
             
        .DESCRIPTION
            Will build out the folder structure for a new bt project
            and make a btConfig.xml file with all the settings set in the parameters
             
        .PARAMETER moduleName
            Mandatory
            The name of your module
        .PARAMETER moduleDescription
            Mandatory
            Description of your module
         
        .PARAMETER modulePath
            Path of your module
            Will use the current path by default
        .PARAMETER moduleAuthor
            Module Author
            Will use $env:USERNAME by default
            Used in module manifest
        .PARAMETER companyName
            Company name
            Used in module manifest
        .PARAMETER majorVersion
            Major version to start at
            Default is 1
            Only required if you want to start at something other than 1 coz your special
        .PARAMETER minorVersion
            Minor version to start at
            Default is 0
             
        .PARAMETER buildVersion
            Build version to start at
            Default is 0
        .PARAMETER minimumPsVersion
            PS Version for your module
            Used in making the manifest and restricting its use
            Default is 5.0.0.0
        .PARAMETER AutoIncrementRevision
            Default True
            Increment the Revision version when running start-btBuild command
        .PARAMETER RemoveSingleLineQuotes
            Default True
            Remove any single line quotes when compiling scripts
         
        .PARAMETER RemoveEmptyLines
            Default True
            Remove any extra empty lines when compiling scripts
        .PARAMETER trimSpaces
            Default False
            Remove any extra spaces
            Breaks your function spacing, so only use if you want a flat file
        .PARAMETER publishOnBuild
            Default True
            When incrementing build version, will push to the repository specified
        .PARAMETER runPesterTests
            Default True
            Add the basic module pester tests to the pester folder
        .PARAMETER repository
            Default powershelf
            The Powershell repository to automatically publish to
        .PARAMETER RequiredModules
            Array of modules you want to include as mandatory when building the manifest
            If a string is supplied, then the version will be whatever latest version is installed
            If a hashtable is supplied it should be constructed as such:
            @{moduleName='myModule';moduleVersion='1.2.3'}
             
        .PARAMETER Tags
             
            The tags to apply in the manifest, helping when searching repositories
        .PARAMETER configFile
            The file to save the config file as
            Defaults to 'btConfig.xml'
            Don't change this, I havent really written it to deal with poor names
         
        .PARAMETER autoDocument
            Default True
            Specify that when running start-btBuild, you want to automatically run get-btDocumentation as well
        .PARAMETER useGitDetails
            Try and get the license, project URIs from GIT
        .PARAMETER licenseUri
            Override the licenseUri
        .PARAMETER iconUri
            Override the iconUri
        .PARAMETER projectUri
            Override the projectUri
        .EXAMPLE
            new-btProject -moduleName myModule -moduleDescription 'A new module'
             
            #### DESCRIPTION
            Make a new powershell module project, include the repository token, use the defaults for everything else
             
             
            #### OUTPUT
            Will make the following folder structure:
            - dist
              - documentation
              - source
                - classes
                - dscClasses
                - enums
                - filters
                - functions
                - pester
                - private
                - resource
                - bin
                - lib
             
            Will include a default .gitignore, .btOrderStart, .btOrderEnd
            Will make a new publishtoken.txt that _should_ not be tracked by GIT
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2018-05-17
             
             
           Changelog:
                2018-04-19 - AA
                     
                    - Initial Script
                    - Added primary functionality
                    - Based off the combine scripts
                 
                2018-04-23 - AA
                     
                    - Added token and repository details
                2018-04-26 - AA
                     
                    - Fixed the help
                2018-05-17 - AA
                     
                    - Updated help
                    - Ensure we error out if we already have a config
                    - Cleaned up no longer needed comments
                2018-12-03 - AA
                     
                    - Remove the folder and file creation
                    - Execute the update-btFileStructure to make them instead
                2019-02-04 - AA
                    - Changed all the boolean params
                        - They can now be null
                        - Defaults are now, as a result, in the begin block
                            - Will read from a saved config if one exists
                            - Will then fall-back to an appropriate default
                    - Made the RequiredModules an array
                        - The array will check for HashTable or String entries
                            - Strings need to be the name of an installed module
                            - Hashtable requires moduleVersion and moduleName strings
                            - Where a hashtable is supplied, the version must be installed on the machine
                        - Makes use of new get-btInstalledModules function
                        - Will lock-down the version to the latest at build time as a result
                            - This should allow updated modules to push the latest required package to the repository as a result
                    - Updated repository to allow multiple arguments
                        - Updated -start-btbuild and publish-btModule cmdlets as a result
                        - Allows you to set multiple default repositories to publish to
                2019-03-06 - AA
                     
                    - Updated to allow useGitDetails
                    - Updated to store licenseUri,projectUri,inconUri
                     
        .COMPONENT
            Bartender
        .INPUTS
           null
        .OUTPUTS
           null
    #>

    [CmdletBinding()]
    PARAM(
        [Parameter(Mandatory=$true)]
        [string]$moduleName,
        [Parameter(Mandatory=$true)]
        [string]$moduleDescription,
        [string[]]$repository,
        [string[]]$moduleAuthor,
        [string]$companyName,
        [version]$minimumPsVersion,
        [Nullable[boolean]]$AutoIncrementRevision,
        [Nullable[boolean]]$RemoveSingleLineQuotes,
        [Nullable[boolean]]$RemoveEmptyLines,
        [Nullable[boolean]]$trimSpaces,
        [Nullable[boolean]]$publishOnBuild,
        [Nullable[boolean]]$runPesterTests,
        [Nullable[boolean]]$autoDocument,
        [Nullable[boolean]]$useGitDetails,
        [array]$RequiredModules,
        [string[]]$Tags,
        [int]$majorVersion = 1,
        [int]$minorVersion = 0,
        [int]$buildVersion = 0,
        [string]$modulePath = $(get-location).path,
        [string]$configFile = 'btConfig.xml',
        [string]$licenseUri,
        [string]$projectUri,
        [string]$iconUri
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $$(MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $$($MyInvocation.BoundParameters|Out-String)"
        if($modulePath -like '*\')
        {
            Write-Verbose 'Superfluous \ found in path, removing'
            $modulePath = $modulePath.Substring(0,$($modulePath.Length-1))
            Write-Verbose "New path = $modulePath"
        }
        write-verbose 'Loading user defaults'
        $userDefaults = get-btDefaultSettings
        write-verbose 'Setting parameter defaults'
        if(!$userDefaults)
        {
            write-warning 'You can save your default module preferences by using the save-btDefaultSettings cmdlet'
        }
        write-verbose 'Populating default Params where missing'
        if(!$moduleAuthor)
        {
            if($userDefaults.author)
            {
                $moduleAuthor = $userDefaults.author
            }else{
                $moduleAuthor = $($env:USERNAME)
            }
        }
        if(!$repository)
        {
            if($userDefaults.repository)
            {
                $repository = $userDefaults.repository
            }else{
                write-warning 'Repository not configured'
            }
        }
        if(!$companyName)
        {
            if($userDefaults.company)
            {
                $companyName = $userDefaults.company
            }else{
                write-warning 'companyName not configured'
                $companyName = ' '
            }
        }
        if(!$Tags)
        {
            if($userDefaults.Tags)
            {
                $Tags = $userDefaults.Tags
            }
        }
        if(!$minimumPsVersion)
        {
            if($userDefaults.minimumPsVersion)
            {
                $minimumPsVersion = $userDefaults.minimumPsVersion
            }else{
                $minimumPsVersion = [version]::new(5,0,0,0)
            }
        }
        if($AutoIncrementRevision -eq $null)
        {
            if($userDefaults.AutoIncrementRevision -ne $null)
            {
                $AutoIncrementRevision = $userDefaults.AutoIncrementRevision
            }else{
                $AutoIncrementRevision  = $true
            }
        }
        if($RemoveSingleLineQuotes -eq $null)
        {
            if($userDefaults.RemoveSingleLineQuotes -ne $null)
            {
                $RemoveSingleLineQuotes = $userDefaults.RemoveSingleLineQuotes
            }else{
                $RemoveSingleLineQuotes  = $true
            }
        }
        if($RemoveEmptyLines -eq $null)
        {
            if($userDefaults.RemoveEmptyLines -ne $null)
            {
                $RemoveEmptyLines = $userDefaults.RemoveEmptyLines
            }else{
                $RemoveEmptyLines  = $true
            }
        }
        if($trimSpaces -eq $null)
        {
            if($userDefaults.trimSpaces -ne $null)
            {
                $trimSpaces = $userDefaults.trimSpaces
            }else{
                $trimSpaces  = $false
            }
        }
        if($publishOnBuild -eq $null)
        {
            if($userDefaults.publishOnBuild -ne $null)
            {
                $publishOnBuild = $userDefaults.publishOnBuild
            }else{
                $publishOnBuild  = $true
            }
        }
        if($runPesterTests -eq $null)
        {
            if($userDefaults.runPesterTests -ne $null)
            {
                $runPesterTests = $userDefaults.runPesterTests
            }else{
                $runPesterTests  = $true
            }
        }
        if($autoDocument -eq $null)
        {
            if($userDefaults.autoDocument -ne $null)
            {
                $autoDocument = $userDefaults.autoDocument
            }else{
                $autoDocument  = $true
            }
        }
        if($useGitDetails -eq $null)
        {
            if($userDefaults.useGitDetails -ne $null)
            {
                $useGitDetails = $userDefaults.useGitDetails
            }else{
                $useGitDetails  = $true
            }
        }
        write-verbose 'Configuring config file'
        $configPath = "$modulePath\$configFile"
        $throwExceptions = @{}
        
        $errCat = [System.Management.Automation.ErrorCategory]::InvalidData
        $errMsg = [System.Exception]::new("configfile found.`nUse update-btFileStructure to ensure folder structure is compliant with btVersion.")
        $throwExceptions.existingConfigErr = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$configPath)
        $errCat = [System.Management.Automation.ErrorCategory]::InvalidData
        $errMsg = [System.Exception]::new("Unable to validate path for $modulePath")
        $throwExceptions.invalidPathErr = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$configPath)
    }
    
    process{
        write-debug 'starting process'
        write-verbose 'Validating path'
        if(!$(test-path $modulePath))
        {
            throw $throwExceptions.invalidPathErr
        }
        if(test-path $configPath)
        {
            throw $throwExceptions.existingConfigErr
        }
        #Create the gitIgnore and the tokenFile
        #"publishtoken.txt" | Out-File "$modulePath\.gitignore" -Force -Encoding utf8
        write-verbose 'Checking repository viability'
        if($repository)
        {
            foreach($repo in $repository)
            {
                try{
                    $rep = get-psrepository $repository -ErrorAction Stop
                    $rep|out-null
                }catch{
                    write-error "Repository $($repo) is not currently configured.`nPlease configure your publish repository first or specify a different one"
                    return
                }
            }
        }
        write-verbose 'Testing the required modules'
        if($requiredModules)
        {
            foreach($reqModule in $requiredModules)
            {
                $reqModuleType = $reqModule.getType().name
                
                if($reqModuleType -eq 'String')
                {
                    write-verbose "Checking Required module: $reqModule is available on this machine"
                    try{
                        $reqModFound = get-btInstalledModule -moduleName $reqModule -errorAction 'Stop'
                        write-verbose "Module $reqModule was Found"
                    }catch{
                        $error[0]
                        throw 'Unable to complete new-btProject due to required module error (See Above)'
                    }
                }elseIf($reqModuleType -eq 'Hashtable')
                {
                    if($($reqModule.keys) -contains 'moduleName' -and $($reqModule.keys) -contains 'moduleVersion' -and $($reqModule.keys.count) -eq 2)
                    {
                        write-verbose "Hashtable well formed for $($reqModule.ModuleName) required module"
                        try{
                            $reqModFound = get-btInstalledModule @reqModule -errorAction 'Stop'
                            write-verbose "Module $($reqModule.ModuleName) was Found with version $($reqModule.ModuleVersion)"
                        }catch{
                            $error[0]
                            throw 'Unable to complete new-btProject due to required module error (See Above)'
                        }
                    }else{
                        throw "Hashtable poorly formed. Required Module Hashtables should contain 2 keys only, moduleName and moduleVersion"
                    }
                }else{
                    throw 'Required Modules should be a string or a Hashtable'
                }
            }
        }
        #Create the folder structure for support items
        write-verbose '***CREATE THE FILES AND FOLDERS***'
        add-btFilesAndFolders -path $modulePath -force
        #>
        #Create the config file
        write-verbose 'Creating Config File'
        $config = [pscustomobject] @{
            moduleAuthor = $moduleAuthor
            moduleName = $moduleName
            moduleDescription = $moduleDescription
            companyName = $companyName
            version = [version]::new($majorVersion,$minorVersion,$buildVersion,0)
            guid = $(new-guid).guid
            AutoIncrementRevision = $AutoIncrementRevision
            RemoveSingleLineQuotes = $RemoveSingleLineQuotes
            RemoveEmptyLines = $RemoveEmptyLines
            minimumPsVersion = $minimumPsVersion
            RequiredModules = [array]$RequiredModules
            Tags = $Tags
            Repository = $repository
            trimSpaces = $trimSpaces
            publishOnBuild = $publishOnBuild
            runPesterTests = $runPesterTests
            bartenderVersion = $($(get-module -name Bartender).version.tostring())
            autoDocument = $autoDocument
            useGitDetails = $useGitDetails
            licenseUri = $licenseUri
            projectUri = $projectUri
            iconUri = $iconUri
        }
        Write-Debug "Your Config Object:`n`n$($config|Out-String)"
        if(!$config.version -or !$config.moduleName -or !$config.moduleAuthor -or !$config.companyName)
        {
            Write-Error 'Invalid Config'
            return
        }else{
            Write-Verbose 'Exporting Config'
            Export-Clixml -Path $configPath -InputObject $config
        }     
    }
}

function save-btDefaultSettings
{
    <#
        .SYNOPSIS
            Save API token and, if supplied, Repository Credentials
             
        .DESCRIPTION
            Save API token and credentials, in order to provide the ability to publish/find modules without
            having to enter this stuff all the time
         
        .PARAMETER author
            Default Module Author
        .PARAMETER repository
            Default repositories to publish to
        .PARAMETER company
            Default Company to publish as
        .PARAMETER Tags
            Default Tags to use
        .PARAMETER minimumPsVersion
            The default minimumPsVersion to use
        .PARAMETER AutoIncrementRevision
            The default AutoIncrementRevision value
        .PARAMETER RemoveSingleLineQuotes
            Default RemoveSingleLineQuotes value
        .PARAMETER RemoveEmptyLines
            Default RemoveEmptylines Value
         
         
        .PARAMETER trimSpaces
            Default trimSpaces value
         
        .PARAMETER publishOnBuild
            Default publishOnBuild value
        .PARAMETER autoDocument
            Default autoDocument Value
        .PARAMETER useGitDetails
            Grab the license and project URI from Git details
            Use them on manifest creation
        .PARAMETER update
            Use to overwrite any existing saved default settings
        .EXAMPLE
            save-btDefaultSettings -author my.name -repositories @('myrepo1','psgallery')
             
            #### DESCRIPTION
            Will save the repository myRepo with the token, and prompt once for credentials.
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: yyyy-mm-dd
             
             
            Changelog:
                2019-02-01 - AA
                     
                    - Initial Script
                        - Unsure what to do about LINUX and PSCORE
                        - Obviously the save path is less than ideal
                        - Also unsure what will happen with roaming profiles
                        - What if we include _this_ computername in the filename
                2019-03-07 - AA
                     
                    - Added way to save useGitDetails
                     
        .COMPONENT
            What cmdlet does this script live in
    #>

    [CmdletBinding()]
    PARAM(
        [string]$author,
        [string]$repository,
        [string]$company,
        [string[]]$Tags,
        [version]$minimumPsVersion,
        [Nullable[boolean]]$AutoIncrementRevision,
        [Nullable[boolean]]$RemoveSingleLineQuotes,
        [Nullable[boolean]]$RemoveEmptyLines,
        [Nullable[boolean]]$trimSpaces,
        [Nullable[boolean]]$publishOnBuild,
        [Nullable[boolean]]$autoDocument,
        [Nullable[boolean]]$runPesterTests,
        [Nullable[boolean]]$useGitDetails,
        [switch]$update
        
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
      
    }
    
    process{
        #Where should we save the module
        write-verbose 'Getting Save location'
        $localPath = "$($env:userprofile)\AppData\local"
        if(test-path $localPath)
        {
            $btSaveFolder = "$localPath\bartender"
            $btDefaultsPath = "$btSaveFolder\btDefaults_$($env:computername).xml"
            if(!$(test-path $btSaveFolder))
            {
                write-verbose "Creating bartender folder at $btSaveFolder"
                new-item -itemtype directory -path $btSaveFolder
            }else{
                write-verbose 'bt path exists'
            }
        }else{
            throw 'Local Profile unavailable'
        }
        
        
        write-verbose 'Importing existing default settings'
        if(test-path $btDefaultsPath)
        {
            try{
                
                $btDefaultSettings = import-clixml $btDefaultsPath -errorAction stop
                write-verbose 'Existing saved default imported'
            }catch{
                write-error $error[0]
                throw 'Unable to import defaults settings'
            }
        }else{
            write-verbose 'Previous settings not found, creating'
            $btDefaultSettings = @{}
        }
        write-verbose 'Checking we do not already have settings'
        write-verbose "$($btDefaultSettings.keys.count)"
        if(($btDefaultSettings.keys.count -ge 1) -and ($update -ne $true))
        {
            throw 'Default settings already exist. To overwrite use the -update switch'
        }
        if($author)
        {
            write-verbose "Setting default author as $author"
            $btDefaultSettings.author = $author
        }
        if($repository)
        {
            write-verbose 'Setting default repository'
            $btDefaultSettings.repository = $repository
        }
        if($company)
        {
            write-verbose 'Setting default company'
            $btDefaultSettings.company = $company
        }
        if($Tags)
        {
            write-verbose 'Setting default Tags'
            $btDefaultSettings.Tags = $tags
        }
        if($minimumPsVersion)
        {
            write-verbose 'Setting default minimumPsVersion'
            $btDefaultSettings.minimumPsVersion = $minimumPsVersion
        }
        if($AutoIncrementRevision -ne $null)
        {
            write-verbose 'Setting default AutoIncrementRevision'
            $btDefaultSettings.AutoIncrementRevision = $AutoIncrementRevision
        }
        if($RemoveSingleLineQuotes -ne $null)
        {
            write-verbose 'Setting default RemoveSingleLineQuotes'
            $btDefaultSettings.RemoveSingleLineQuotes = $RemoveSingleLineQuotes
        }
        if($RemoveEmptyLines -ne $null)
        {
            write-verbose 'Setting default RemoveEmptyLines'
            $btDefaultSettings.RemoveEmptyLines = $RemoveEmptyLines
        }
        if($trimSpaces -ne $null)
        {
            write-verbose 'Setting default trimSpaces'
            $btDefaultSettings.trimSpaces = $trimSpaces
        }
        if($publishOnBuild -ne $null)
        {
            write-verbose 'Setting default publishOnBuild'
            $btDefaultSettings.publishOnBuild = $publishOnBuild
        }
        if($autoDocument -ne $null)
        {
            write-verbose 'Setting default autoDocument'
            $btDefaultSettings.autoDocument = $autoDocument
        }
        if($useGitDetails -ne $null)
        {
            write-verbose 'Setting default autoDocument'
            $btDefaultSettings.useGitDetails = $useGitDetails
        }
        write-debug 'Save the file'
        write-verbose 'Updating saved repositories file'
        $btDefaultSettings|export-clixml $btDefaultsPath -force
    }
    
}

function save-btRepository
{
    <#
        .SYNOPSIS
            Save API token and, if supplied, Repository Credentials
             
        .DESCRIPTION
            Save API token and credentials, in order to provide the ability to publish/find modules without
            having to enter this stuff all the time
         
        .PARAMETER repository
            Name of the repository to use the credentials against
             
            Requires repository to already be registered
        .PARAMETER token
            The Repository API Token to use to publish the module
        .PARAMETER credential
            The Repository Credential to use
            If no credentials are supplied only the token will be saved.
            If your repository requires credentials for saving/listing modules,
            Then you will need to supply the credentials here.
            They are used to verify whether any dependant modules need to be uploaded when publishing
        .PARAMETER update
            Use to overwrite any existing saved repo settings
        .EXAMPLE
            save-btRepository -repository myRepo -token MyAPIToken -credentail get-credential
             
            #### DESCRIPTION
            Will save the repository myRepo with the token, and prompt once for credentials.
             
             
            #### OUTPUT
            Copy of the output of this line
             
             
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: yyyy-mm-dd
             
             
            Changelog:
                2019-02-01 - AA
                     
                    - Initial Script
                        - Unsure what to do about LINUX and PSCORE
                        - Obviously the save path is less than ideal
                        - Also unsure what will happen with roaming profiles
                        - What if we include _this_ computername in the filename
                     
        .COMPONENT
            What cmdlet does this script live in
    #>

    [CmdletBinding()]
    PARAM(
        [Parameter(Mandatory=$true)]
        [string]$repository,
        [Parameter(Mandatory=$true)]
        [string]$token,
        [pscredential]$credential,
        [switch]$update
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
      
    }
    
    process{
        #Where should we save the module
        write-verbose 'Getting Save location'
        $localPath = "$($env:userprofile)\AppData\local"
        if(test-path $localPath)
        {
            $btSaveFolder = "$localPath\bartender"
            $btRepositoriesPath = "$btSaveFolder\btRepositories_$($env:computername).xml"
            if(!$(test-path $btSaveFolder))
            {
                write-verbose "Creating bartender folder at $btSaveFolder"
                new-item -itemtype directory -path $btSaveFolder
            }else{
                write-verbose 'bt path exists'
            }
        }else{
            throw 'Local Profile unavailable'
        }
        
        write-verbose 'Checking for valid repository'
        if($repository -notIn $(get-psrepository).name)
        {
            throw "Repository not found with name $repository!`nPlease register a repository first with the 'Register-PSRepository' cmdlet"
        }
        write-verbose 'Importing existing saved repositories'
        if(test-path $btRepositoriesPath)
        {
            try{
                write-verbose 'Importing existing repository settings'
                $btRepositories = import-clixml $btRepositoriesPath -errorAction stop
            }catch{
                write-error $error[0]
                throw 'unable to import repository settings'
            }
        }else{
            write-verbose 'Previous settings not found, creating'
            $btRepositories = @{}
        }
        write-verbose 'Checking we do not already have settings'
        if(($btRepositories."$repository") -and ($update -ne $true))
        {
            throw 'Repository settings already exist. To overwrite use the -update switch'
        }
        write-verbose "Creating new entry for $repository"
        $btRepositories."$repository" = @{}
        $btRepositories."$repository".token = $(new-object System.Management.Automation.PSCredential('apiToken',$($token|convertTo-SecureString -asPlainText -force)))
        if($credential)
        {
            $btRepositories."$repository".credential = $credential
        }else{
            write-warning "No credentials supplied.`nIf your repository requires credentials for saving, you will need to provide them here as well.`nFailure to do so will cause errors when including required/dependant modules"
        }
        
        write-debug 'Save the file'
        write-verbose 'Updating saved repositories file'
        $btRepositories|export-clixml $btRepositoriesPath -force
    }
    
}

function start-btbuild
{
    <#
        .SYNOPSIS
            Increment the version.
            Grab all the scripts.
            Compile into a single module file.
            Create a manifest.
             
        .DESCRIPTION
            - Increment the version depending on the switch used
            - Grab any scripts, dsc resources etc from the source file
            - Compile into a single module file
            - Create a preload.ps1 file
              - to ensure classes and enums are available
              - Will allow using-module and import-module to function similarly
              - Will allow user-substantiation of classes
            - Kick off start-btTestPhase
            - If enabled, kick off get-btDocumentation
            - If tests pass &
              - If incrementing build,major,minor version and autopublish in config OR
              - If publish is true with switch
                - Push to the repository specified
             
        .PARAMETER configFile
            Default 'btconfig.xml'
            The config file to use
        .PARAMETER ReleaseNotes
            Any release notes to add to the manifest
        .PARAMETER incrementMajorVersion
            Switch, increments the major version
            Will trigger a publish based on the config file
        .PARAMETER incrementMinorVersion
            Switch, increments the minor version
            Will trigger a publish based on the config file
        .PARAMETER incrementBuildVersion
            Switch, increments the build version
            Will trigger a publish based on the config file
        .PARAMETER test
            Override the config files settings
            One day, might actually trigger a test
         
        .PARAMETER publish
            Boolean value
            Override the config files settings
            Run the Publish-btmodule on complete
        .PARAMETER ignoreBtVersion
            Run even if there is a difference in bartender versions
                         
        .EXAMPLE
            start-btbuild
             
        #### DESCRIPTION
            Increment the revision version, good way to ensure everything works
             
             
        #### OUTPUT
            New module manifest and module file, or overright the existing build version.
            Test the module
            Create documentation if enabled
        .EXAMPLE
            start-btbuild -verbose -incrementbuildversion
             
        #### DESCRIPTION
            Increment the build version
            Depending on the btconfig.xml, push to a repository
             
        #### OUTPUT
            New module manifest and module file, or overright the existing build version.
            Test the module
            Create documentation if enabled
            Publish the module if required
             
             
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2018/05/17
             
             
           Changelog:
                2018-04-19 - AA
                     
                    - Initial Script
                    - Added primary functionality
                    - Based off the combine scripts
                
                2018-04-23 - AA
                     
                    - Many, Many things fixed
                2018-04-26 - AA
                     
                    - Fixed the help
                    - Changed the add-files to use get-btscripttext
                        - Improved dscresource gathering
                        - Improved function resource gathering
                2018-05-11
                    - Added publish switch
                    - Made the publish switch work
                    - Actually made the publish switch work
                2018-05-16
                    - Really Really I promise made the publish switch work
                    - Added start-btTestPhase
                    - Tested it all out
                2018-05-17
                    - Tested it all out
                    - Added switch for ignoreBtVersion
                    - Added failover if btVersions drifted
                    - Fixed the help
                    - Added get-btDocumentation
                    - Tested it again
                2018-05-18
                    - Moved the get-btDocumentation to inside the publish block
                    - Should mean we don't inadvertantly update documentation prematurely
                    - Tried to make the pester result the only output
                2018-05-22
                    - Attempted to fix the way arrays were added to the manifest
                      - Then reverted it back since it made it worse
                    - Stop adding Preload script to manifest when DSCClasses exist, causes funky stuff to happen
                    - Fixed issue where btversion was always being flagged as incorrect
                2018-05-23
                    - Moved the btVersion check to the right place, it was checking before importing, which is dumb
                    - Moved the preloadFileContents switch to the right if block as well, coz I must have been drunk when I put it in
                2018-08-13
                    - Made the folder pass section a switch from an if
                    - Segmented the preload into seperate files for ENUMS,CLASSES,VALIDATORS
                    - Added the preloads to nested-modules as well
                2018-08-30
                    - Added privateDataFile
                2018-10-08
                    - Removed nested modules for when using DSC, still not the best when dealing with Dsc
                    - Brute force added the enums to the main module when using DSC, its not ideal but I'm out of ideas
                 
                2018-10-30
                    - Added postbuildscripts
                        - Need to check if the path is ok (it is)
                        - Seems the folder does not get created on new-btproject, need to check that
                2019-01-30 - 2019-02-04 - AA
                    - Changed the version output to no longer be the dist folder
                        - Tried to simplify a bit
                        - Still need the project name as the root folder, since the parent folder is used when importing and publishing
                    - Added rev folder
                        - Keep all the non-build versions separate from our build versions
                        - For BT Projects, revisions should be considered separate for builds
                        - Have discovered that Artifactory/Nuget does some _nasty_ things when you build with a revision version
                            - Especially if that revision version is a 0
                    - Force use of platyps and configuration
                        - Made as required modules
                    - Clone Rev to Release
                        - No longer need to run tests 2 times for the same module
                        - Will then use config to update the version in the module manifest
                    - Added Pester details to module manifest
                        Under Private Data/Pester
                    - Changed the publish-btModule call
                        - Now does a forEach so you can have multiple repositories in your config
                2019-03-04
                    - Fixed bugs with update-btproject
                2019-03-06
                    - Changed code-coverage to be an int
                    - Added way to get git details for license, project, icon urls
                    - Added override where these are set in the module config
                2019-03-10
                    - Add a lastRelease hashtable to btconfig on release build complete
                        - Add version and date
                    - Also clone lastRelease to previous Release
                 
                2019-03-12
                    - Fix the icon link when generating from git
                2019-03-14
                    - Move the postBuildScript step to after release
                     
        .COMPONENT
            Bartender
    #>

    [CmdletBinding(DefaultParameterSetName='revisionVersion')]
    param(
        [Parameter(ParameterSetName='minorVersion')]
        [Parameter(ParameterSetName='majorVersion')]
        [Parameter(ParameterSetName='buildVersion')]
        [Parameter(ParameterSetName='revisionVersion')]
        [string]$configFile = 'btconfig.xml',
        [Parameter(ParameterSetName='minorVersion')]
        [Parameter(ParameterSetName='majorVersion')]
        [Parameter(ParameterSetName='buildVersion')]
        [Parameter(ParameterSetName='revisionVersion')]
        [string]$ReleaseNotes,
        [Parameter(ParameterSetName='majorVersion')]
        [Alias("majorver")]
        [switch]$incrementMajorVersion,
        [Parameter(ParameterSetName='minorVersion')]
        [Alias("minorver")]
        [switch]$incrementMinorVersion,
        [Parameter(ParameterSetName='buildVersion')]
        [Alias("buildver")]
        [switch]$incrementBuildVersion,
        [Parameter(ParameterSetName='minorVersion')]
        [Parameter(ParameterSetName='majorVersion')]
        [Parameter(ParameterSetName='buildVersion')]
        [Parameter(ParameterSetName='revisionVersion')]
        [switch]$ignoreBtVersion,
        [Parameter(ParameterSetName='minorVersion')]
        [Parameter(ParameterSetName='majorVersion')]
        [Parameter(ParameterSetName='buildVersion')]
        [nullable[bool]]$publish,
        [Parameter(ParameterSetName='minorVersion')]
        [Parameter(ParameterSetName='majorVersion')]
        [Parameter(ParameterSetName='buildVersion')]
        [Parameter(ParameterSetName='revisionVersion')]
        [string]$privateDataFile = 'privateData.xml',
        [Parameter(ParameterSetName='minorVersion')]
        [Parameter(ParameterSetName='majorVersion')]
        [Parameter(ParameterSetName='buildVersion')]
        [Parameter(ParameterSetName='revisionVersion')]
        [switch]$ignoreTest
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        #Remove last pester result
        remove-variable -Scope global -Name lastPesterResult -Force -ErrorAction ignore
        $invocationPath = (Get-Item -Path ".\").FullName
        #Subfunction
        function add-header{
            [CmdletBinding()]
            param(
                [string]$header,
                [hashtable]$scriptVars
            )
            Write-Verbose "Adding header: $header"
            $divide = '####################################'
            $content = "`n<#$divide`n$header`n$divide#>"
            $content|Out-File  $scriptVars.moduleFile -Append
        }
        
        #Need to install the module first though
        $btModule = get-module bartender
        if(!$btModule)
        {
            import-module -name bartender
            $btModule = get-module bartender
        }
        write-verbose 'Got BtModule version'
        
        $metaData = @{
            version = $btmodule.version
            author = $btmodule.author
            copyright = $btmodule.Copyright
            name = $btmodule.Name
            description = $btmodule.Description
        }
        
        if(test-path "$invocationPath\$($privateDataFile)")
        {
            write-warning 'Importing privateDataFile, if this is a hashtable it will be added to the module manifest'
            $privateDataHash = import-clixml $privateDataFile
            if($privateDataHash.getType().name -eq 'hashtable')
            {
                $metaData.manifestPrivateData = $privateDataHash
            }else{
                write-warning "$privateDataFile does not contain a single hashtable and therefore will be ignored"
                write-verbose 'Generating a clean privateData hashtable'
                $metaData.manifestPrivateData = @{}
            }
        }else{
            write-verbose 'Generating a clean privateData hashtable'
            $metaData.manifestPrivateData = @{}
        }
        #Add our own privateData stuff as well
        $metaData.manifestPrivateData.moduleCompiledBy = "Bartender | $($btmodule.Description)"
        $metaData.manifestPrivateData.bartenderVersion = $btmodule.version
        $metaData.manifestPrivateData.bartenderCopyright = $btmodule.Copyright
        $metaData.manifestPrivateData.builtBy = $env:USERNAME
        $metaData.manifestPrivateData.builtOn = $(get-date -format s)
        write-verbose "PrivateData debug type: $($metaData.manifestPrivateData.GetType().name)"
        write-verbose "PrivateData debug Data:`n $($metaData.manifestPrivateData|out-string)"
        #Where was this command run from
        
        
        $scriptVars = @{}
        $scriptVars.configFilePath = "$invocationPath\$($configFile)"
        [array]$scriptVars.folders = @('enums','functions','filters','validationClasses','dscClasses','classes','private')
        <#
            Need to deal with Classes slightly differently
            Need to present them in the psd1 file as a script to run
            SO, we need an extras step that adds a classes.ps1 file
            Then grabs and compiles all the classes to that file
            THEN executes that script via the ScriptsToProcess item in the psd file
            This _should_ then make all the classes freely available after importing.
            E.g.
            ScriptsToProcess = @('Classes.ps1')
        #>

        $throwExceptions = @{}
        #NoConfig
        $errCat = [System.Management.Automation.ErrorCategory]::InvalidData
        $errMsg = [System.Exception]::new("Config file was not found.`nUse new-btproject for a new project.")
        $throwExceptions.noConfigError = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$scriptVars.configFile)
        #BadConfig
        $errMsg = [System.Exception]::new('Config file contents unexpected or malformed.')
        $throwExceptions.badConfigError = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$scriptVars.configFile)
        $errMsg = [System.Exception]::new('Bartender version deprecated.')
        $throwExceptions.badBartender = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$scriptVars.configFile)
        #EmptyFunctionsArray
        $scriptVars.functionsToExport = @()
        $scriptVars.DscResourcesToExport = @()
        
    }
    
    process{
        write-verbose "InvocationPath: $invocationPath"
        write-verbose "configfile: $configfile"
        
        
        write-verbose 'Validating Config File'
        
        if(!$(test-path $scriptVars.configFilePath))
        {
            throw $throwExceptions.noConfigError
        }
        try{
            $scriptVars.config = Import-Clixml $scriptVars.configFilePath -ErrorAction Stop
            Write-Verbose "$($scriptvars.config|Out-String)"
            write-verbose "$($scriptvars|out-string)"
            if(!$scriptVars.config.version -or !$scriptVars.config.moduleName -or !$scriptVars.config.moduleAuthor -or !$scriptVars.config.companyName)
            {
                throw $throwExceptions.badConfigError
            }else{
                if(!$ignoreBtVersion)
                {
                    write-verbose 'Confirming BarTender Version'
                    $currentBtVersion = $btModule.version
                    write-verbose "String: $($scriptVars.config.bartenderVersion)" 
                    $configBtVersion = [version]$($scriptVars.config.bartenderVersion)
                    Write-Verbose "Config: $($configBtVersion.ToString())"
                    Write-Verbose "module: $($currentBtVersion.ToString())"
                    write-verbose "module: $($currentBtVersion.ToString()) vs config $($configBtVersion.ToString()) "
                    if($configBtVersion -lt $currentBtVersion)
                    {
                        write-error 'Bartender version in config file is deprecated'
                        'Please update your Bartender Module with the update-btFileStructure command'
                        throw $throwExceptions.badBartender
                    }elseif($configBtVersion -gt $currentBtVersion){
                        write-error 'Bartender version in config file is newer than the one installed'
                        'Please update your Bartender Version with the update-module command'
                        throw $throwExceptions.badBartender
                    }
                }
                #Increment the revision version
                if($scriptVars.config.AutoIncrementRevision -eq $true)
                {
                    Write-Verbose 'Incrementing Revision Version'
                    $scriptVars.newVersion = [version]::new($($scriptVars.config.version.Major),$($scriptVars.config.version.Minor),$($scriptVars.config.version.Build),$($scriptVars.config.version.revision + 1))
                    write-verbose $scriptVars.newVersion
                    
                }else{
                    write-warning 'Skipping revision incrementation'
                }
                write-verbose 'Checking if we need to build a release version'
                switch ($($PSCmdlet.ParameterSetName)) {
                    'minorVersion' {
                        $scriptvars.build = $true
                        write-verbose 'Push Minor Release'
                        $scriptVars.newReleaseVersion = [version]::new($($scriptVars.config.version.Major),$($scriptVars.config.version.Minor + 1),0,0)
                        $scriptVars.newVersionAsTag = "$($scriptVars.newReleaseVersion.Major).$($scriptVars.newReleaseVersion.Minor).$($scriptVars.newReleaseVersion.Build)"
                    }
                    
                    'majorVersion' {
                        write-verbose 'Push Major Release'
                        $scriptvars.build = $true
                        $scriptVars.newReleaseVersion = [version]::new($($scriptVars.config.version.Major + 1),0,0,0)
                        $scriptVars.newVersionAsTag = "$($scriptVars.newReleaseVersion.Major).$($scriptVars.newReleaseVersion.Minor).$($scriptVars.newReleaseVersion.Build)"
                    }
                    'buildVersion' {  
                        write-verbose 'Push Build Release'
                        $scriptvars.build = $true
                        $scriptVars.newReleaseVersion = [version]::new($($scriptVars.config.version.Major),$($scriptVars.config.version.Minor),$($scriptVars.config.version.Build + 1),0)
                        $scriptVars.newVersionAsTag = "$($scriptVars.newReleaseVersion.Major).$($scriptVars.newReleaseVersion.Minor).$($scriptVars.newReleaseVersion.Build)"
                    }
                }
                
                if($scriptVars.newVersion)
                {
                    write-verbose "Incremented Version: $($scriptVars.newVersion)"
                    $scriptVars.config.version = $scriptVars.newVersion
                    #We should also add this bartender version to the config
                    write-verbose 'New bt version incremented in config'
                    write-verbose "Adding this Bartender Version: $($btModule.version.tostring())"
                    #$scriptVars.config.bartenderVersion = $($(get-module -name Bartender).version.tostring())
                    if($scriptVars.config.bartenderVersion)
                    {
                        $scriptVars.config.bartenderVersion = $($btModule.version.tostring())
                    }else{
                        $scriptVars.config | Add-Member -MemberType NoteProperty -Name bartenderVersion -Value $($btModule.version.tostring())
                    }
                    write-verbose 'Working out version tag'
                    $scriptVars.versionAsTag = $scriptVars.config.version.toString()
                    #Save the versionAsTag to the config
                    write-verbose 'Adding versionAsTag to config'
                    try{
                        $scriptVars.config.versionAsTag = $scriptVars.versionAsTag
                    }catch{
                        $scriptVars.config | Add-Member -MemberType NoteProperty -Name versionAsTag -Value $scriptVars.versionAsTag
                    }
                    write-verbose 'Updating Config file'
                    Export-Clixml -Path $scriptVars.configFilePath -InputObject $scriptVars.config
                    write-verbose 'Config file updated'
                }else{
                    write-verbose "Not Incrementing Version $($scriptVars.config.version)"
                }
            }
        }catch{
            write-error $Error[0].Exception
            throw $throwExceptions.badConfigError
        }
        write-verbose 'Config Settings:'
        Write-Verbose "$($scriptvars.config|Out-String)"
        write-debug "Config Updated"
        #Start compiling the items
        #Check we should be compiling for DSC
        #This is important as we need to save the classes as DSC resources
        #Check the folders, paths, tags, versions
        $scriptVars.functionResources = @()
        
        write-verbose 'Creating in revision directory'
            
        $scriptVars.moduleOutputFolder = "$invocationPath\rev\$($scriptVars.versionAsTag)"
        
        write-verbose "Module will be saved to $($scriptVars.moduleOutputFolder)"
        if(!(test-path $scriptVars.moduleOutputFolder))
        {
            write-verbose 'Making module folder since it does not exist'
            new-item $scriptVars.moduleOutputFolder -ItemType Directory | out-null
        }
        $scriptVars.manifestFile = "$($scriptVars.moduleOutputFolder)\$($scriptVars.config.moduleName).psd1"
        write-verbose "Manifest File: $($scriptVars.manifestFile)"
        if(! $(test-path $scriptVars.manifestFile))
        {
            #Should pretty much always create a new one
            Write-Verbose 'New Manifest will be created'
            $updateManifest = $false
        }else{
            #Legacy, change to warning from verbose as a test
            Write-warning 'Existing manifest will be updated'
            $updateManifest = $true
        }
        $scriptVars.moduleFile = "$($scriptVars.moduleOutputFolder)\$($scriptVars.config.moduleName).psm1"
        write-verbose "Module File: $($scriptVars.moduleFile)"
        if(test-path $scriptVars.moduleFile)
        {
            Write-warning 'Module file will be replaced'
            remove-item $scriptVars.moduleFile -Force
            #should basically never see this
            #Making this a warning as well
        }
        
        $scriptVars.preloadFileContents = $false
        $scriptVars.preloadFiles = @()
        foreach($preFile in @('enums.ps1','validators.ps1','classes.ps1'))
        {
            write-verbose "Checking $preFile"
            $preFilePath = "$($scriptVars.moduleOutputFolder)\$preFile"
            if(test-path $preFilePath)
            {
                write-warning "Removing previous prefile: $prefile"
                remove-item $preFilePath
                #Should also never see this
            }
        }
        remove-variable preFile -ErrorAction ignore
        write-debug 'Manifest, Preload and Module files checked'
        #Add the module Header
        $metadata.moduleHeader = "<#`nModule Mixed by BarTender`n`t$($metaData.description)`n`tVersion: $($metaData.version)`n`tAuthor: $($metaData.author)`n`tCopyright: $($metaData.copyright)`n`nModule Details:`n`tModule: $($scriptVars.config.moduleName)`n`tDescription: $($scriptVars.config.moduleDescription)`n`tRevision: $($scriptVars.config.version)`n`tAuthor: $($scriptVars.config.moduleAuthor)`n`tCompany: $($scriptVars.config.companyName)`n`nCheck Manifest for more details`n#>"
        $metadata.moduleHeader|Out-File $scriptVars.moduleFile -Force
        #Add the required script folder items to the module
        foreach($folder in $scriptVars.folders)
        {
            Write-Verbose "Processing folder $folder"
            
            $folderItems = get-btfolderItems -Path "$invocationPath\source\$folder" -psScriptsOnly 
            if($($folderItems | measure-object).Count -ge 1)
            {
                write-verbose "$($folderItems.Count) Files found, getting content"
                write-verbose 'Getting script text, functions etc'
                $textSplat = @{
                    psFile = $folderItems.Path
                    removeQuotes = $scriptVars.config.RemoveSingleLineQuotes
                    trimSpaces = $scriptVars.config.trimSpaces
                    RemoveEmptyLines = $scriptVars.config.RemoveEmptyLines
                }
                if($folder -eq 'functions')
                {
                    write-verbose 'Will flag scripts as functions'
                    $textSplat.isFunction = $true
                }
                if($folder -eq 'private')
                {
                    write-verbose 'Private functions detected'
                    #Do not splat with isFunction so we don't add them to be exported
                    $textSplat.isFunction = $false
                }
                if($folder -eq 'dscClasses')
                {
                    write-verbose 'Will flag scripts as dsc Classes'
                    $textSplat.isDSCClass = $true
                }
                $textOutput = get-btScriptText @textSplat
                if($textOutput.output.length -gt 10)
                {
                    switch ($folder) {
                        'enums' {
                            write-debug 'Processing Enums'
                            write-verbose 'Outputting contents to Enums file'
                            $textOutput.output|Out-File  "$($scriptVars.moduleOutputFolder)\enums.ps1" -Append
                            #We only need this if there are classes
                            $scriptVars.preloadFileContents = $false
                            $scriptVars.preloadFiles += 'enums.ps1'
                        }
                        'validationClasses' {
                            write-debug 'Processing validation classes'
                            write-verbose 'Outputting contents to Validators file'
                            $textOutput.output|Out-File  "$($scriptVars.moduleOutputFolder)\validators.ps1" -Append
                            #We only need this if there are classes
                            $scriptVars.preloadFileContents = $true
                            $scriptVars.preloadFiles += 'validators.ps1'
                          }
                        'classes' {
                            write-debug 'Processing std classes'
                            write-verbose 'Outputting contents to Classes file'
                            $textOutput.output|Out-File  "$($scriptVars.moduleOutputFolder)\classes.ps1" -Append
                            $scriptVars.preloadFileContents = $true
                            $scriptVars.preloadFiles += 'classes.ps1'
                        }
                        Default {
                            write-debug "Processing folder: $folder"
                            $textOutput.output|Out-File  $scriptVars.moduleFile -Append 
                            if($textOutput.functionResources.count -ge 1)
                            {
                                $scriptVars.functionsToExport += $textOutput.functionResources
                            }
                            if($textOutput.dscResources.count -ge 1)
                            {
                                $scriptVars.DscResourcesToExport += $textOutput.dscResources
                            }
                        }
                    }
                    
                    
                }else{
                    write-verbose 'No script contents to include'
                }
            }else{
                write-verbose 'No PS1 files found, ignoring'
            }
            
        }
        remove-variable folder -ErrorAction SilentlyContinue
        
    }end{
        write-verbose 'Copying Static Files'
        $folders = @('lib','bin','resource')
        foreach($folder in $folders)
        {
            $copiedItems = get-btfolderItems -path "$invocationPath\source\$folder" -destination "$($scriptVars.moduleOutputFolder)\$folder" -Copy
            Write-Verbose "Copied $($copiedItems.count) static items"
        }
        
        write-verbose "Version: $($scriptVars.config.version)"
        #By making the hashtable ordered
        #And declaring the things we want null AS Actually null
        #It should actually make them null
        #New-ModuleManifest seems to CARE what order the params are set,
        #if you export functions it sets * to aliases and cmdlets
        $splatManifest = [ordered]@{
            Path = $scriptVars.manifestFile
            RootModule = $(get-item $scriptVars.moduleFile).name
            Author = $($scriptVars.config.moduleAuthor -join ',')
            Copyright = "$(get-date -f yyyy) $($scriptVars.config.companyName)"
            CompanyName = $scriptVars.config.companyName
            Description = $scriptVars.config.moduleDescription
            ModuleVersion = $scriptVars.versionAsTag
            Guid = $scriptVars.config.guid
            PowershellVersion = $scriptVars.config.minimumPsVersion
            FunctionsToExport = @()
            ScriptsToProcess = @()
            NestedModules = $null
            CmdletsToExport = @()
            AliasesToExport = @()
            VariablesToExport = $null
        }
        Write-debug $($splatManifest | Out-String)
        if($scriptVars.DscResourcesToExport.count -ge 1){
            write-verbose "PreloadFileContents: $($scriptvars.preloadFileContents)"
            Write-Verbose 'Adding DSC Resources'
            $splatManifest.DscResourcesToExport = $scriptVars.DscResourcesToExport
            write-warning 'Since DSC resources are included, will not include preload.ps1. It causes some odd behaviour with DSC'
            $scriptvars.preloadFileContents = $false
            write-verbose "PreloadFileContents: $($scriptvars.preloadFileContents)"
            write-verbose 'Need to add the enums to the top of the file'
            $enumsContent = $(get-content "$($scriptVars.moduleOutputFolder)\enums.ps1")
            $moduleContent = get-content $scriptVars.moduleFile
            $enumsContent | out-file $scriptVars.moduleFile -Force
            $moduleContent | out-file $scriptVars.moduleFile -Append
        }
        
        if($scriptVars.config.Tags){
            Write-Verbose 'Adding Tags'
            $splatManifest.tags = $scriptVars.config.Tags
        }
        if($($scriptVars.config.RequiredModules|measure-object).count -ge 1){
            write-verbose 'Finding Appropriate Module Versions'
            $scriptVars.Modules = foreach($reqModule in $scriptVars.config.RequiredModules)
            {
                $reqModuleType = $reqModule.getType().name
                if($reqModuleType -eq 'String')
                {
                    write-verbose "Checking Required module: $reqModule is available on this machine"
                    try{
                        get-btInstalledModule -moduleName $reqModule -errorAction 'Stop'
                        write-verbose "Module $reqModule was Found"
                    }catch{
                        $error[0]
                        throw 'Unable to complete build due to required module error (See Above)'
                    }
                }elseIf($reqModuleType -eq 'Hashtable')
                {
                    if($($reqModule.keys) -contains 'moduleName' -and $($reqModule.keys) -contains 'moduleVersion' -and $($reqModule.keys.count) -eq 2)
                    {
                        write-verbose "Hashtable well formed for $($reqModule.ModuleName) required module"
                        try{
                            get-btInstalledModule @reqModule -errorAction 'Stop'
                            write-verbose "Module $($reqModule.ModuleName) was Found with version $($reqModule.ModuleVersion)"
                        }catch{
                            $error[0]
                            throw 'Unable to complete build due to required module error (See Above)'
                        }
                    }else{
                        throw "Hashtable poorly formed. Required Module Hashtables should contain 2 keys only, moduleName and moduleVersion"
                    }
                }
            }
            $splatManifest.RequiredModules = $scriptVars.Modules
        }
        if($ReleaseNotes)
        {
            Write-Verbose 'Adding Release Notes'
            $splatManifest.ReleaseNotes = $ReleaseNotes
        }
        
        if($scriptVars.config.useGitDetails -eq $true)
        {
            write-verbose 'Adding Git Details'
            $scriptVars.gitSettings = get-btGitDetails -modulePath $invocationPath
            if($scriptVars.gitSettings)
            {
                write-verbose 'Retrieved git details, to splatManifest'
                #Use origin as projectUri
                if($scriptVars.gitSettings.origin -and $scriptVars.gitSettings.origin.length -gt 5)
                {
                    $splatManifest.projectUri = $scriptVars.gitSettings.origin
                }
                #See if we have a license file, if we do add the license URI
                #Could be done with a web-request, but then what do we do with private repos
                if($(test-path "$invocationPath\LICENSE"))
                {
                    $splatManifest.licenseUri = "$($scriptVars.gitSettings.origin)/blob/$($($($scriptVars.gitSettings.branch).replace('*','')).trim())/LICENSE"
                }
                #See if icon.png exists and if it does, add it in as well
                if($(test-path "$invocationPath\icon.png"))
                {
                    $splatManifest.iconUri = "$($scriptVars.gitSettings.origin)/blob/$($($($scriptVars.gitSettings.branch).replace('*','')).trim())/icon.png"
                }
                    
            }else{
                write-warning 'useGitDetails is set to true, but was unable to get the repository settings'
            }
            
        }
        if($scriptVars.config.licenseUri -and $scriptVars.config.licenseUri.length -gt 5)
        {   
            write-verbose 'Adding config LicenseUri'
            $splatManifest.licenseUri = $scriptVars.config.licenseUri
        }
        if($scriptVars.config.projectUri -and $scriptVars.config.projectUri.length -gt 5)
        {   
            write-verbose 'Adding config projectUri'
            $splatManifest.projectUri = $scriptVars.config.projectUri
        }
        if($scriptVars.config.iconUri -and $scriptVars.config.iconUri.length -gt 5)
        {   
            write-verbose 'Adding config iconUri'
            $splatManifest.iconUri = $scriptVars.config.iconUri
        }
        if($scriptVars.functionsToExport.Count -ge 1)
        {
            Write-Verbose 'Adding Function Resources'
            $splatManifest.FunctionsToExport = $scriptVars.functionsToExport
        }
        if($metaData.manifestPrivateData)
        {
            $metaData.manifestPrivateData.moduleRevision = $scriptVars.config.version
            [hashtable]$splatManifest.privateData = $metaData.manifestPrivateData
            write-verbose "Adding privateData;`n`tDebug - privateDataType: $($splatManifest.privatedata.gettype().name)"
        }
        write-verbose "PreloadFileContents: $($scriptvars.preloadFileContents)"
        if($scriptvars.preloadFileContents -eq $true -and $scriptVars.preloadFiles.count -ge 1)
        {
            write-verbose 'Adding Preload to scriptsToProcess'
            $splatManifest.ScriptsToProcess = $scriptVars.preloadFiles
            write-verbose 'Adding nestedModules'
            $splatManifest.NestedModules = $scriptVars.preloadFiles
        }
        Write-Verbose "$updateManifest"
        if($updateManifest -eq $true)
        {
            write-verbose 'Checking for, and removing, old Manifest file'
            remove-item -Path $($scriptVars.manifestFile) -force -ErrorAction Ignore
            New-ModuleManifest @splatManifest
        }else{
            New-ModuleManifest @splatManifest
        }
        
        #work out whether we should publish
        write-verbose "Script config publish default: $($scriptVars.config.publishOnBuild)"
        if($scriptVars.config.publishOnBuild -eq $true)
        {
            $scriptVars.defaultPublish = $true
        }else{
            $scriptVars.defaultPublish = $false
        }
        switch ($publish) {
            $true { $scriptVars.shouldPublish = $true}
            $false { $scriptVars.shouldPublish = $false }
            $null {
                if($scriptvars.build)
                {
                    $scriptVars.shouldPublish = $scriptVars.defaultPublish 
                }else{
                    $scriptVars.shouldPublish = $false
                }
            }
            Default { 
                if($scriptvars.build)
                {
                    $scriptVars.shouldPublish = $scriptVars.defaultPublish 
                }else{
                    $scriptVars.shouldPublish = $false
                }
            }
        }
        write-debug 'Continue with Pester Tests?'
        if($ignoreTest)
        {
            write-warning 'Skipping test phase'
        }else{
            write-verbose 'Starting test phase'
            try{
                $scriptvars.testResults = start-btTestPhase -path $invocationPath -configFile $configFile -modulePath "$($scriptVars.moduleOutputFolder)"
            }catch{
                throw 'Unable to initiate Test Phase' 
            }
        }
        
        
        
        if(($scriptvars.testResults.success -eq $true) -or ($ignoreTest -eq $true))
        {
            if($scriptvars.testResults.pesterDetails)
            {
                write-verbose 'Build Results Returned:'
                $scriptvars.testResults
                $global:lastPesterResult = $scriptvars.testResults.pesterDetails
                write-warning 'PesterResults Saved to global variable $global:lastPesterResult'
            }else{
                write-warning 'Tests explicitely ignored'
            }
            
            #$scriptvars.build
            if($scriptvars.build -eq $true)
            {
                write-debug 'Continue with publish?'
                Write-Verbose 'Publish triggered - cloning revision folder and updating version'
                #This is where we clone the revision
                if($scriptVars.newReleaseVersion -and $scriptVars.newVersionAsTag)
                {
                    write-verbose 'Incrementing version and cloning to release'
                    #make new directory
                    write-verbose 'creating release directory'
                    $scriptVars.releaseDirectory = "$invocationPath\$($scriptVars.config.moduleName)\$($scriptVars.newVersionAsTag)"
                    new-item -itemtype Directory -Path $scriptVars.releaseDirectory|out-null
                    write-verbose 'Cloning'
                    copy-item -Path "$($scriptVars.moduleOutputFolder)\*" -Destination "$($scriptVars.releaseDirectory)\" -recurse
                    
                    write-verbose 'Creating new module manifest'
                    write-verbose 'Checking for Manifest file'
                    $scriptVars.newManifestPath = "$($scriptVars.releaseDirectory)\$($scriptVars.config.moduleName).psd1"
                    if(!$(test-path $scriptVars.newManifestPath))
                    {
                        throw 'Unable to find new Manifest file'
                    }
                    #Check for configuration module and import it
                    write-verbose 'Checking configuration module to update manifest'
                    if(!$(get-module configuraton))
                    {
                        try{
                            import-module -name configuration -ErrorAction Stop
                        }catch{
                            throw 'Configuration module not on this machine. Please install it first'
                        }
                    }
                    #Using the metadata
                    write-verbose 'Updating manifest file version'
                    $metadata = import-metadata $scriptVars.newManifestPath
                    #Update the version
                    $metadata.ModuleVersion = $scriptVars.newVersionAsTag
                    #Add pester details
                    
                    if($global:lastPesterResult)
                    {
                        write-verbose 'Adding Pester details to manifest'
                        $codeCoverage = [math]::round($($global:lastPesterResult.codecoverage.numberOfCommandsExecuted/$global:lastPesterResult.codecoverage.numberofCommandsAnalyzed)*100,0)
                        $passed = [math]::round($($($global:lastPesterResult.PassedCount / $global:lastPesterResult.TotalCount)*100),0)
                        $metadata.privatedata.pester = @{}
                        $metadata.privatedata.pester.codecoverage = $codeCoverage
                        $metadata.privatedata.pester.time = $global:lastPesterResult.time.ToString()
                        $metadata.privatedata.pester.passed = "$passed %"
                    }
                    
                    #Save the file
                    write-verbose 'Saving manifest file'
                    try{
                        $metadata|export-metadata -Path $scriptVars.newManifestPath
                    }catch{
                        throw 'Error updating Manifest file'
                    }
                    
                    #Test
                    write-verbose 'Ensuring Manifest still valid'
                    try{
                        $scriptVars.manifestTest = Test-ModuleManifest $scriptVars.newManifestPath -ErrorAction stop
                    }catch{
                        throw 'Error: Updated Manifest is invalid'
                    }
                    write-verbose 'Updating Config file'
                    write-verbose 'Adding versionAsTag to module config'
                    $scriptVars.config.versionAsTag = $scriptVars.newVersionAsTag
                    write-verbose 'Adding new version to module config'
                    
                    $scriptVars.config.version  = $scriptVars.newReleaseVersion
                    if(!$($scriptVars.config.previousRelease)){
                        write-verbose 'lastRelease property missing from config, creating it'
                        $scriptVars.config|add-member -MemberType NoteProperty -name previousRelease -Value @{}
                    }
                    if(!$($scriptVars.config.lastrelease)){
                        write-verbose 'lastRelease property missing from config, creating it'
                        $scriptVars.config|add-member -MemberType NoteProperty -name lastRelease -Value @{}
                    }else{
                        write-verbose 'Setting previous details from last release'
                        $scriptVars.config.previousRelease = $scriptVars.config.lastrelease.clone()
                    }
                    write-verbose 'Adding lastRelease items to config'
                    $scriptVars.config.lastRelease = @{
                        version = $scriptVars.newReleaseVersion
                        date = $(get-date)
                    }
                    Export-Clixml -Path $scriptVars.configFilePath -InputObject $scriptVars.config
                    write-verbose 'Config file updated'
                    if($scriptVars.shouldPublish)
                    {
                        write-verbose 'Ok to publish'
                        foreach($repo in $scriptVars.config.repository)
                        {
                            publish-btmodule -Repository $repo
                        }
                        
                    }
                    
                }
                if($scriptVars.config.autoDocument -eq $true)
                {
                    write-verbose 'Updating Documentation'
                    get-btDocumentation -path $invocationPath -configFile $configFile
                }else{
                    write-verbose 'Skipping documentation update'
                }
                #PostBuildScripts go here
                $postBuildScripts = get-btFolderItems -psScriptsOnly -Path "$invocationPath\postbuildscripts"
                write-debug 'Execute any postbuild scripts'
                foreach($postbuildscript in $postbuildscripts)
                {
                    write-verbose "Executing postbuild script $($postbuildscript.relativepath)"
                    . $postBuildScripts.Path
                }
            }else{
                Write-Verbose 'No publish triggered'
            }
            
        }else{
            #Write-Verbose 'Failed the testing phase'
            Write-Error 'Failed the testing phase'
            if($scriptvars.testResults.pesterDetails)
            {
                write-warning "Build Results Returned:"
                $scriptvars.testResults
                $global:lastPesterResult = $scriptvars.testResults.pesterDetails
                write-warning 'PesterResults Saved to global variable $global:lastPesterResult'
            }else{
                write-warning 'No pester results returned'
            }
        }
        
        #Last step, cleanup vars
        write-debug 'Clean up old revisions'
        write-verbose 'Cleaning up old revisions'
        start-btRevisionCleanup -path $invocationPath
        
        remove-variable splatManifest,scriptvars -ErrorAction SilentlyContinue
        
        write-verbose 'Finished btbuild run'
        write-information 'Finished btbuild run'
    }
    
}

function start-btTestPhase
{
    <#
        .SYNOPSIS
            Run a test of the module
             
        .DESCRIPTION
            - Create a new runspace
            - Run the module in as a job
            - If pester is installed, include in the job any tests in the source\pester folder
            - Return a custom result of what occured
            Running as a job ensures we are on a clean powershell process, hopefully with no modules loading
            Will respect the .btIgnore, .btOrder* files
             
        .PARAMETER path
            The path of your bartender module
            Defaults to current working directory
         
        .PARAMETER configFile
            The bartender configfile to use
            Defaults to btconfig.xml
             
        .EXAMPLE
            start-btTestPhase
              
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2018-05-17
             
             
            Changelog:
                2018-05-16 - AA
                     
                    - Initial Script
                    - Tested ok
                    - Improved job execution
                2018-05-17 - AA
                     
                    - Added help
                    - Allowed passing of variables to pester for basic module tests
                    - Improved job execution
                    - Improved the return object
                2019-01-31 - AA
                     
                    - Rewrite to accept a path for the module
                    - So we don't always use the dist path
                2019-02-25
                    - Somehow this file disappeared
                        - Pulled from the last commit with this file
                2019-03-12
                    - Somehow this file disappeared
                        - Change from using module to import-module
                     
        .COMPONENT
            Bartender
        .INPUTS
           null
        .OUTPUTS
            custom object
    #>

    [CmdletBinding()]
    PARAM(
        [Parameter(Position=0)]
        [string]$path = (Get-Item -Path ".\").FullName,
        [string]$configFile = 'btconfig.xml',
        [string]$modulePath
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        $invocationPath = $path
        $scriptVars = @{}
        $scriptVars.configFilePath = "$invocationPath\$($configFile)"
        $scriptVars.testsPath = "$invocationPath\source\pester"
        $throwExceptions = @{}
        #NoConfig
        $errCat = [System.Management.Automation.ErrorCategory]::InvalidData
        $errMsg = [System.Exception]::new("Config file was not found.`nUse new-btproject for a new project.")
        $throwExceptions.noConfigError = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$scriptVars.configFile)
        #BadConfig
        $errMsg = [System.Exception]::new('Config file contents unexpected or malformed.')
        $throwExceptions.badConfigError = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$scriptVars.configFile)
        $returnResult = [pscustomobject] @{
            success = $false
            message = $null
            pesterDetails = $null
            pesterFails = $null
            pesterCodeCoverPercent = 0
            pesterCommandsAnalyzed = 0
            pesterResults = '0/0'
        }
    }
    
    process{
        if(!$(test-path $scriptVars.configFilePath))
        {
            throw $throwExceptions.noConfigError
        }
        
        $scriptVars.config = Import-Clixml $scriptVars.configFilePath -ErrorAction Stop
        Write-Verbose "$($scriptvars.config|Out-String)"
        if(!$scriptVars.config.version -or !$scriptVars.config.moduleName -or !$scriptVars.config.moduleAuthor -or !$scriptVars.config.companyName)
        {
            throw $throwExceptions.badConfigError
        }else{
            write-verbose 'Configuring Params'
            $scriptVars.versionAsTag = $scriptVars.config.versionAsTag
            if($modulePath)
            {
                write-verbose "Using module path of $modulePath"
                write-debug 'Using specified module path'
                
                $scriptVars.moduleFolder = $modulePath
                
            }else{
                write-warning 'Testing last release'
                Write-Debug 'Testing last release'
                
                $scriptVars.moduleFolder = "$invocationPath\dist\$($scriptVars.config.moduleName)\$($scriptVars.versionAsTag)"
            }
            #$scriptVars.moduleFolder = "$invocationPath\dist\$($scriptVars.config.moduleName)\$($scriptVars.versionAsTag)"
            write-verbose 'Checking for Pester'
            $scriptVars.pesterModule = $(get-module -refresh -ListAvailable -Name Pester |sort-object -Property Version -Descending |select-object -First 1)
            if(!$scriptVars.pesterModule)
            {
                write-warning 'Pester not installed, no tests will be performed'
            }elseif(!$scriptVarspestermodule.Version.Major -lt 4)
            {
                Write-Verbose "Found pester Version: $($scriptVars.pesterModule.Version.ToString())"
                write-warning 'This version of Pester is deprecated. Please update to a version greater than 4'
                write-warning 'No tests will be performed'
                $scriptVars.pesterModule -eq $null
            }else{
                Write-Verbose "Using pester Version: $($scriptVars.pesterModule.Version.ToString())"
                write-verbose 'Getting Pester Tests'
                $scriptVars.testScripts = get-btFolderItems -psScriptsOnly -Path $scriptVars.testsPath
                $scriptVars.testCount = $($scriptVars.testScripts | measure-object).count
            }
            if($scriptVars.testCount -ge 1 -and $($scriptVars.pesterModule))
            {
                write-verbose "Found $($scriptVars.testCount) test files. Will process them with invoke"
                
                #$scriptVars.pesterResults = invoke-pester -Script $scriptVars.testScripts.path
                #`$pesterResult = invoke-pester -passthru -script @($($(foreach($script in $scriptVars.testScripts.path){"'$script'"}) -join ',')) -show None -codeCoverage '$($scriptVars.moduleFolder)\*'
                $scriptVars.myTestBlock = "
                    import-module '$($scriptVars.moduleFolder)\$($scriptVars.config.moduleName).psd1'
                    `$scriptsArray = @($($(foreach($script in $scriptVars.testScripts.path){"'$script'"}) -join ','))
                    `$pesterScriptsParameter = foreach(`$script in `$scriptsArray)
                    {
                        @{
                            path=`$script
                            Parameters= @{
                                modulePath = '$($scriptVars.moduleFolder)'
                                moduleVersion = '$($scriptVars.versionAsTag)'
                                moduleName = '$($scriptVars.config.moduleName)'
                            }
                        }
                    }
                    import-module Pester
                     
                    `$pesterResult = invoke-pester -passthru -script `$pesterScriptsParameter -show None -codeCoverage '$($scriptVars.moduleFolder)\$($scriptVars.config.moduleName).psm1'
                    `$pesterResult
                "

            }else{
                write-verbose 'No tests - just checking module'
                $scriptVars.myTestBlock ="
                    using module $($scriptVars.moduleFolder)
                    [psCustomObject] @{
                        TotalCount = 0
                        PassedCount = 0
                        FailedCount = 0
                        Time = `$([timespan]::new(0)).tostring()
                    }
                "

            }
            try{
                write-verbose 'Creating script block'
                $scriptBlock = [scriptblock]::Create($scriptVars.myTestBlock)
                write-verbose "ScriptBlock Contents:`n`n$($scriptVars.myTestBlock)"
            }catch{
                
                write-error 'Unable to compile scriptblock. Module path could be missing'
                write-verbose "ScriptBlock Contents:`n`n$($scriptVars.myTestBlock)"
                $returnResult.message = 'Unable to compile scriptblock. Module path could be missing'
            }
            write-verbose 'Executing Scriptblock As Job'
            $job = start-job -ScriptBlock $scriptBlock 
            Wait-Job $job | Out-Null
            $pesterResults = receive-job $job
            $returnResult.message = 'Module loaded succesfully'
            if($pesterResults.FailedCount -eq 0)
            {
                $returnResult.success = $true
            }else{
                $returnResult.pesterFails = $pesterResults.TestResult | where-object{$_.passed -eq $false}
            }
            $returnResult.pesterDetails = $pesterResults
            if($pesterResults.codecoverage)
            {
                $returnResult.pesterCommandsAnalyzed = $pesterResults.codecoverage.NumberOfCommandsAnalyzed
                $returnResult.pesterCodeCoverPercent = [math]::round($($($pesterResults.codecoverage.NumberOfCommandsExecuted)/$($pesterResults.codecoverage.NumberOfCommandsAnalyzed))*100,0)
            }
            if($pesterResults.PassedCount -ge 1)
            {
                $returnResult.pesterResults = "$($pesterResults.PassedCount)/$($pesterResults.TotalCount)"
            }
            $returnResult
        }
    }
}

function update-btFileStructure
{
    <#
        .SYNOPSIS
            Update the current projects file and config to the installed version of bartender
             
        .DESCRIPTION
            Check the folder structer and files are what the installed version are expecting
            Add them if they are not present
            Add the bartender version to the module config
            Add the autodocument variable to the module config
             
        .PARAMETER path
            The path of your bartender module
            Defaults to current working directory
         
        .PARAMETER configFile
            The bartender configfile to use
            Defaults to btconfig.xml
        .PARAMETER force
            Ignore any differences in version and run anyway
         
        .EXAMPLE
            update-btFileStructure
             
            #### DESCRIPTION
            Check for differences in Bartender Versions
            Update if required
             
             
        .EXAMPLE
            update-btFileStructure -force
             
            #### DESCRIPTION
            Always update
             
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2018-05-17
             
             
            Changelog:
                2018-0
                2018-05-17 - AA
                     
                    - Initial Script
                    - Added Help
                    - Updated Config file
                2018-05-17 - AA
                     
                    - Added add-btBasicTests to the update function
                2018-08-13
                    - Added validationClasses folder
                2018-10-30
                    - Added postBuildScript folder
                    - Fixed errormsg with btversion that could occur where the var was declared but empty
                2018-12-03
                    - Added Revisions folder (rev)
                 
                2019-02-03
                    - Add a default setting for autodocument
                     
         .COMPONENT
            Bartender
        .INPUTS
           null
        .OUTPUTS
            custom object
    #>

    [CmdletBinding()]
    PARAM(
        [string]$path = (Get-Item -Path ".\").FullName,
        [string]$configFile = 'btconfig.xml',
        [switch]$force
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        $btModule = get-module bartender
        $invocationPath = $path
        $scriptVars = @{}
        $scriptVars.configFilePath = "$invocationPath\$($configFile)"
        $throwExceptions = @{}
        #NoConfig
        $errCat = [System.Management.Automation.ErrorCategory]::InvalidData
        $errMsg = [System.Exception]::new("Config file was not found.`nUse new-btproject for a new project.")
        $throwExceptions.noConfigError = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$scriptVars.configFile)
        #BadConfig
        $errMsg = [System.Exception]::new('Config file contents unexpected or malformed.')
        $throwExceptions.badConfigError = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$scriptVars.configFile)
    }
    
    process{
        if(!$(test-path $scriptVars.configFilePath))
        {
            throw $throwExceptions.noConfigError
        }
        $scriptVars.config = Import-Clixml $scriptVars.configFilePath -ErrorAction Stop
        Write-Verbose "$($scriptvars.config|Out-String)"
        if(!$scriptVars.config.version -or !$scriptVars.config.moduleName -or !$scriptVars.config.moduleAuthor -or !$scriptVars.config.companyName)
        {
            throw $throwExceptions.badConfigError
        }else{
            $currentBtVersion = $btModule.version
            $configBtVersion = [version]$scriptVars.config.bartenderVersion
            if($configBtVersion -eq $currentBtVersion -and (!$force))
            {
                write-verbose 'Bartender version is already ok'
                
            }elseif($configBtVersion -gt $currentBtVersion -and (!$force)){
                write-error 'Bartender version in config file is newer than the one installed'
                throw 'Please update your Bartender Version with the update-module command'
            }else{
                write-verbose 'Need to upgrade'
                write-verbose "Adding this Bartender Version: $($btModule.version.tostring())"
                #$scriptVars.config.bartenderVersion = $($(get-module -name Bartender).version.tostring())
                if($configBtVersion)
                {
                    $scriptVars.config.bartenderVersion = $($btModule.version.tostring())
                }else{
                    try{
                        $scriptVars.config | Add-Member -MemberType NoteProperty -Name bartenderVersion -Value $($btModule.version.tostring()) -ErrorAction Stop
                    }catch{
                        $scriptVars.config.bartenderVersion = $($btModule.version.tostring())
                    }
                    
                }
                if($scriptVars.config.autoDocument -eq $null)
                {
                    write-warning 'Adding the autoDocument and setting it to true, if this is undesired edit the config.xml'
                    $scriptVars.config | Add-Member -MemberType NoteProperty -Name autoDocument -Value $true
                    add-btBasicTests
                }
                write-verbose 'Updating Config file updated'
                Export-Clixml -Path $scriptVars.configFilePath -InputObject $scriptVars.config
                write-verbose 'Config file updated'
                
                write-verbose '***CREATE THE FILES AND FOLDERS***'
                add-btFilesAndFolders -path $path -force
            }
        }       
        
    }
    
}

function update-btProject
{
    <#
        .SYNOPSIS
            Provide a way to update an existing module
             
        .DESCRIPTION
            Will build out the folder structure for a new bt project
            and make a btConfig.xml file with all the settings set in the parameters
             
        .PARAMETER moduleName
            If you want to update the name of your module
            Will not reset the versioning
        .PARAMETER moduleDescription
            Update the description of your module
         
        .PARAMETER moduleAuthor
            Will append to the authors list
        .PARAMETER minimumPsVersion
            PS Version for your module
            Used in making the manifest and restricting its use
            Default is 5.0.0.0
        .PARAMETER AutoIncrementRevision
            Default True
            Increment the Revision version when running start-btBuild command
        .PARAMETER RemoveSingleLineQuotes
            Default True
            Remove any single line quotes when compiling scripts
         
        .PARAMETER RemoveEmptyLines
            Default True
            Remove any extra empty lines when compiling scripts
        .PARAMETER trimSpaces
            Default False
            Remove any extra spaces
            Breaks your function spacing, so only use if you want a flat file
        .PARAMETER publishOnBuild
            Default True
            When incrementing build version, will push to the repository specified
        .PARAMETER runPesterTests
            Default True
            Add the basic module pester tests to the pester folder
        .PARAMETER repository
            Change the default repository(s) to publish to
        .PARAMETER RequiredModules
            Array of modules you want to include as mandatory when building the manifest
            If a string is supplied, then the version will be whatever latest version is installed
            If a hashtable is supplied it should be constructed as such:
            @{moduleName='myModule';moduleVersion='1.2.3'}
             
        .PARAMETER Tags
             
            The tags to apply in the manifest, helping when searching repositories
         
        .PARAMETER autoDocument
            Default True
            Specify that when running start-btBuild, you want to automatically run get-btDocumentation as well
        .PARAMETER useGitDetails
            Try and get the license, project URIs from GIT
        .PARAMETER licenseUri
            Override the licenseUri
        .PARAMETER iconUri
            Override the iconUri
        .PARAMETER projectUri
            Override the projectUri
        .EXAMPLE
            new-btProject -moduleName myModule -moduleDescription 'A new module'
             
            #### DESCRIPTION
            Make a new powershell module project, include the repository token, use the defaults for everything else
             
             
            #### OUTPUT
            Will make the following folder structure:
            - dist
              - documentation
              - source
                - classes
                - dscClasses
                - enums
                - filters
                - functions
                - pester
                - private
                - resource
                - bin
                - lib
             
            Will include a default .gitignore, .btOrderStart, .btOrderEnd
            Will make a new publishtoken.txt that _should_ not be tracked by GIT
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2018-05-17
             
             
           Changelog:
                2018-04-19 - AA
                     
                    - Initial Script
                    - Added primary functionality
                    - Based off the combine scripts
                 
                2018-04-23 - AA
                     
                    - Added token and repository details
                2018-04-26 - AA
                     
                    - Fixed the help
                2018-05-17 - AA
                     
                    - Updated help
                    - Ensure we error out if we already have a config
                    - Cleaned up no longer needed comments
                2018-12-03 - AA
                     
                    - Remove the folder and file creation
                    - Execute the update-btFileStructure to make them instead
                2019-02-04 - AA
                    - Changed all the boolean params
                        - They can now be null
                        - Defaults are now, as a result, in the begin block
                            - Will read from a saved config if one exists
                            - Will then fall-back to an appropriate default
                    - Made the RequiredModules an array
                        - The array will check for HashTable or String entries
                            - Strings need to be the name of an installed module
                            - Hashtable requires moduleVersion and moduleName strings
                            - Where a hashtable is supplied, the version must be installed on the machine
                        - Makes use of new get-btInstalledModules function
                        - Will lock-down the version to the latest at build time as a result
                            - This should allow updated modules to push the latest required package to the repository as a result
                    - Updated repository to allow multiple arguments
                        - Updated -start-btbuild and publish-btModule cmdlets as a result
                        - Allows you to set multiple default repositories to publish to
                2019-02-25 - AA
                    - Fixed some issues with checking the config file
                2019-03-04 - AA
                    - Fixed this up so that it correctly used existing settings
                        - Would overwrite existing settings with defaults prior
                    - Fixed issue where moduleName and Description were getting dropped
                        - Therefore causing this script to always error
                    - Fix the GUID getting dropped
                    - Fix version rebasing back to 0 by removing it as an update version
                        - Can still be updated manually if needed
                2019-03-08 - AA
                - Added way to update licenseuri, iconuri and projecturi
                2019-03-08 - AA
                    - Stopped loosing required modules on update
                     
        .COMPONENT
            Bartender
        .INPUTS
           null
        .OUTPUTS
           null
    #>

    [CmdletBinding()]
    PARAM(
        [string]$moduleName,
        [string]$moduleDescription,
        [string[]]$repository,
        [string[]]$moduleAuthor,
        [string]$companyName,
        [version]$minimumPsVersion,
        [Nullable[boolean]]$AutoIncrementRevision,
        [Nullable[boolean]]$RemoveSingleLineQuotes,
        [Nullable[boolean]]$RemoveEmptyLines,
        [Nullable[boolean]]$trimSpaces,
        [Nullable[boolean]]$publishOnBuild,
        [Nullable[boolean]]$runPesterTests,
        [Nullable[boolean]]$autoDocument,
        [Nullable[boolean]]$useGitDetails,
        [array]$RequiredModules,
        [string[]]$Tags,
        [string]$modulePath = $(get-location).path,
        [string]$configFile = 'btConfig.xml',
        [string]$licenseUri,
        [string]$projectUri,
        [string]$iconUri
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $$(MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $$($MyInvocation.BoundParameters|Out-String)"
        if($modulePath -like '*\')
        {
            Write-Verbose 'Superfluous \ found in path, removing'
            $modulePath = $modulePath.Substring(0,$($modulePath.Length-1))
            Write-Verbose "New path = $modulePath"
        }
        write-verbose 'Loading Existing Settings'
        $existingSettings = import-clixml "$modulePath\$configFile"
        if(!$existingSettings)
        {
            write-warning 'Existing Settings not found'
        }else{
            write-debug 'Existing Settings Loaded'
            write-debug $($existingSettings|Format-List|out-string)
        }
        write-verbose 'Loading user defaults'
        $userDefaults = get-btDefaultSettings
        if(!$userDefaults)
        {
            write-warning 'User defaults Not Found'
            write-warning 'You can save your default module preferences by using the save-btDefaultSettings cmdlet'
        }else{
            write-debug 'User Defaults Loaded'
            write-debug $($userDefaults|format-list|out-string)
        }
        write-verbose 'Ensuring we have a module name or description'
        if(!$moduleName)
        {
            if($existingSettings.moduleName){
                $moduleName = $existingSettings.moduleName
            }else{
                throw 'Modulename not supplied or found'
            }
        }
        write-verbose 'Capturing release details'
        if($existingSettings.lastRelease){
            $lastRelease = $existingSettings.lastRelease
        }else{
            $lastRelease = @{}
        }
        if($existingSettings.previousRelease){
            $previousRelease = $existingSettings.previousRelease
        }else{
            $previousRelease = @{}
        }
        write-verbose 'Add in the GUID'
        $guid = $existingSettings.guid
        if(!$guid)
        {
            throw 'Unable to load in the GUID, check your btconfig.xml file because this may cause issues'
        }
        write-verbose 'Add in the current version'
        $version = $existingSettings.version
        if(!$version)
        {
            throw 'Unable to load in the version, check your btconfig.xml file because this may cause issues'
        }
        if(!$moduleDescription)
        {
            if($existingSettings.moduleDescription){
                $moduleDescription = $existingSettings.moduleDescription
            }else{
                throw 'Modulename not supplied or found'
            }
        }
        write-verbose 'Populating Params where missing'
        if(!$moduleAuthor)
        {
            if($existingSettings.moduleAuthor){
                $moduleAuthor = $existingSettings.moduleAuthor
            }
            elseif($userDefaults.author)
            {
                $moduleAuthor = $userDefaults.author
            }else{
                $moduleAuthor = $($env:USERNAME)
            }
        }
        if(!$repository)
        {
            if($existingSettings.repository){
                $repository = $existingSettings.repository
            }elseif($userDefaults.repository)
            {
                $repository = $userDefaults.repository
            }else{
                write-warning 'Repository not configured'
            }
        }
        if(!$companyName)
        {
            if($existingSettings.companyName){
                $companyName = $existingSettings.companyName
            }elseif($userDefaults.company){
                $companyName = $userDefaults.company
            }else{
                write-warning 'companyName not configured'
                $companyName = ' '
            }
        }
        if(!$Tags)
        {
            if($existingSettings.tags)
            {
                $tags = $existingSettings.tags
            }
            elseif($userDefaults.Tags)
            {
                $Tags = $userDefaults.Tags
            }
        }
        if(!$minimumPsVersion)
        {
            if($existingSettings.minimumPsVersion)
            {
                $minimumPsVersion = $existingSettings.minimumPsVersion
            }
            elseif($userDefaults.minimumPsVersion)
            {
                $minimumPsVersion = $userDefaults.minimumPsVersion
            }else{
                $minimumPsVersion = [version]::new(5,0,0,0)
            }
        }
        if($AutoIncrementRevision -eq $null)
        {
            if($existingSettings.AutoIncrementRevision -ne $null)
            {
                $AutoIncrementRevision = $existingSettings.AutoIncrementRevision
            }
            elseif($userDefaults.AutoIncrementRevision -ne $null)
            {
                $AutoIncrementRevision = $userDefaults.AutoIncrementRevision
            }else{
                $AutoIncrementRevision  = $true
            }
        }
        if($RemoveSingleLineQuotes -eq $null)
        {
            if($existingSettings.removeSingleLineQuotes -ne $null)
            {
                $removeSingleLineQuotes = $existingSettings.removeSingleLineQuotes
            }
            elseif($userDefaults.RemoveSingleLineQuotes -ne $null)
            {
                $RemoveSingleLineQuotes = $userDefaults.RemoveSingleLineQuotes
            }else{
                $RemoveSingleLineQuotes  = $true
            }
        }
        if($RemoveEmptyLines -eq $null)
        {
            if($existingSettings.RemoveEmptyLines -ne $null)
            {
                $RemoveEmptyLines = $existingSettings.RemoveEmptyLines
            }
            elseif($userDefaults.RemoveEmptyLines -ne $null)
            {
                $RemoveEmptyLines = $userDefaults.RemoveEmptyLines
            }else{
                $RemoveEmptyLines  = $true
            }
        }
        if($trimSpaces -eq $null)
        {
            if($existingSettings.trimSpaces -ne $null)
            {
                $trimSpaces = $existingSettings.trimSpaces
            }
            elseif($userDefaults.trimSpaces -ne $null)
            {
                $trimSpaces = $userDefaults.trimSpaces
            }else{
                $trimSpaces  = $false
            }
        }
        if($publishOnBuild -eq $null)
        {
            if($existingSettings.publishOnBuild -ne $null)
            {
                $publishOnBuild = $existingSettings.publishOnBuild
            }
            elseif($userDefaults.publishOnBuild -ne $null)
            {
                $publishOnBuild = $userDefaults.publishOnBuild
            }else{
                $publishOnBuild  = $true
            }
        }
        if($runPesterTests -eq $null)
        {
            if($existingSettings.runPesterTests)
            {
                $runPesterTests = $existingSettings.runPesterTests
            }
            elseif($userDefaults.runPesterTests -ne $null)
            {
                $runPesterTests = $userDefaults.runPesterTests
            }else{
                $runPesterTests  = $true
            }
        }
        if($autoDocument -eq $null)
        {
            if($existingSettings.autoDocument -ne $null)
            {
                $autoDocument = $existingSettings.autoDocument
            }
            elseif($userDefaults.autoDocument -ne $null)
            {
                $autoDocument = $userDefaults.autoDocument
            }else{
                $autoDocument  = $true
            }
        }
        if($useGitDetails -eq $null)
        {
            if($existingSettings.useGitDetails -ne $null)
            {
                $useGitDetails = $existingSettings.useGitDetails
            }
            elseif($userDefaults.useGitDetails -ne $null)
            {
                $useGitDetails = $userDefaults.useGitDetails
            }else{
                $useGitDetails  = $true
            }
        }
        if($licenseUri -eq $null)
        {
            if($existingSettings.licenseUri -ne $null)
            {
                $licenseUri = $existingSettings.licenseUri
            }
            elseif($userDefaults.licenseUri -ne $null)
            {
                $licenseUri = $userDefaults.licenseUri
            }else{
                $licenseUri  = $true
            }
        }
        if($licenseUri -eq $null)
        {
            if($existingSettings.licenseUri -ne $null)
            {
                $licenseUri = $existingSettings.licenseUri
            }
            elseif($userDefaults.licenseUri -ne $null)
            {
                $licenseUri = $userDefaults.licenseUri
            }else{
                $licenseUri  = $true
            }
        }
        write-verbose 'Configuring config file'
        $configPath = "$modulePath\$configFile"
        $throwExceptions = @{}
        
        $errCat = [System.Management.Automation.ErrorCategory]::InvalidData
        $errMsg = [System.Exception]::new("configfile found.`nUse update-btFileStructure to ensure folder structure is compliant with btVersion.")
        $throwExceptions.existingConfigErr = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$configPath)
        $errCat = [System.Management.Automation.ErrorCategory]::InvalidData
        $errMsg = [System.Exception]::new("Unable to validate path for $modulePath")
        $throwExceptions.invalidPathErr = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$configPath)
    }
    
    process{
        write-debug 'starting process'
        write-verbose 'Validating path'
        if(!$(test-path $modulePath))
        {
            throw $throwExceptions.invalidPathErr
        }
        if(!$(test-path $configPath))
        {
           
            throw $throwExceptions.existingConfigErr
        }
        #Create the gitIgnore and the tokenFile
        #"publishtoken.txt" | Out-File "$modulePath\.gitignore" -Force -Encoding utf8
        write-verbose 'Checking repository viability'
        if($repository)
        {
            foreach($repo in $repository)
            {
                try{
                    $rep = get-psrepository $repository -ErrorAction Stop
                    $rep|out-null
                }catch{
                    write-error "Repository $($repo) is not currently configured.`nPlease configure your publish repository first or specify a different one"
                    return
                }
            }
        }
        write-verbose 'Testing the required modules'
        if($requiredModules)
        {
            foreach($reqModule in $requiredModules)
            {
                $reqModuleType = $reqModule.getType().name
                
                if($reqModuleType -eq 'String')
                {
                    write-verbose "Checking Required module: $reqModule is available on this machine"
                    try{
                        $reqModFound = get-btInstalledModule -moduleName $reqModule -errorAction 'Stop'
                        write-verbose "Module $reqModule was Found"
                    }catch{
                        $error[0]
                        throw 'Unable to complete new-btProject due to required module error (See Above)'
                    }
                }elseIf($reqModuleType -eq 'Hashtable')
                {
                    if($($reqModule.keys) -contains 'moduleName' -and $($reqModule.keys) -contains 'moduleVersion' -and $($reqModule.keys.count) -eq 2)
                    {
                        write-verbose "Hashtable well formed for $($reqModule.ModuleName) required module"
                        try{
                            $reqModFound = get-btInstalledModule @reqModule -errorAction 'Stop'
                            write-verbose "Module $($reqModule.ModuleName) was Found with version $($reqModule.ModuleVersion)"
                        }catch{
                            $error[0]
                            throw 'Unable to complete new-btProject due to required module error (See Above)'
                        }
                    }else{
                        throw "Hashtable poorly formed. Required Module Hashtables should contain 2 keys only, moduleName and moduleVersion"
                    }
                }else{
                    throw 'Required Modules should be a string or a Hashtable'
                }
            }
        }else{
            if($existingSettings.requiredModules)
            {
                $requiredModules = $existingSettings.requiredModules
            }
        }
        #Create the folder structure for support items
        write-verbose '***CREATE THE FILES AND FOLDERS***'
        add-btFilesAndFolders -path $modulePath -force
        write-verbose 'Should have finished updating files'
        #Create the config file
        write-verbose 'Creating Config File'
        $config = [pscustomobject] @{
            moduleAuthor = $moduleAuthor
            moduleName = $moduleName
            moduleDescription = $moduleDescription
            companyName = $companyName
            version = $version
            guid = $guid
            AutoIncrementRevision = $AutoIncrementRevision
            RemoveSingleLineQuotes = $RemoveSingleLineQuotes
            RemoveEmptyLines = $RemoveEmptyLines
            minimumPsVersion = $minimumPsVersion
            RequiredModules = [array]$RequiredModules
            Tags = $Tags
            Repository = $repository
            trimSpaces = $trimSpaces
            publishOnBuild = $publishOnBuild
            runPesterTests = $runPesterTests
            bartenderVersion = $($(get-module -name Bartender).version.tostring())
            autoDocument = $autoDocument
            useGitDetails = $useGitDetails
            licenseUri = $licenseUri
            projectUri = $projectUri
            iconUri = $iconUri
            lastRelease = $lastRelease
            previousRelease = $previousRelease
        }
        Write-Debug "Your Config Object:`n`n$($config|Out-String)"
        if(!$config.version -or !$config.moduleName -or !$config.moduleAuthor -or !$config.companyName)
        {
            Write-Error 'Invalid Config'
            return
        }else{
            Write-Verbose 'Exporting Config'
            Export-Clixml -Path $configPath -InputObject $config
        }     
    }
}


function add-btBasicTests
{
    <#
        .SYNOPSIS
            Add a basic pester test to the pester folder
             
        .DESCRIPTION
            Add a pester-test to the ..\source\pester folder
            This pester-test will test the basic health of your module on execution
            The script file (baseModuleTest.ps1) will be added to both the .btOrderStart and .btOrderEnd files, so will run twice
            The tests will ensure you are working on only the newly-compiled module and not a previously installed module
             
        .PARAMETER path
            The path of your bartender module
            Defaults to current working directory
         
        .EXAMPLE
            add-btBasicTests
             
            #### DESCRIPTION
            Create file ..\source\pester\baseModuleTest.ps1
            Insert filename into .btOrderStart (At Top)
            Insert filename into .btOrderEnd (At Bottom)
             
             
            #### OUTPUT
            N/A
             
             
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2018-05-17
             
             
            Changelog:
                2018-05-17 - AA
                     
                    - Initial Script
                    - Tested - working
                    - Forced a change on get-btFolderItem
                2018-05-18 - AA
                     
                    - Fixed a bug where it was absolute referencing the module name in the basetest
                     
        .COMPONENT
            Bartender
             
        .INPUTS
           null
        .OUTPUTS
            null
    #>

    [CmdletBinding()]
    PARAM(
        [string]$path = (Get-Item -Path ".\").FullName
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        $fileContents = @'
Param(
    $moduleVersion,
    $modulePath,
    $moduleName
)
describe 'The module was imported succesfully' {
    $module = get-module -name $moduleName
    it 'Should have imported a single module' {
        ($module | measure-object).count | should -be 1
    }
    it 'Name of the module is correct' {
        $module.Name |Should -be $moduleName
    }
    it 'Should be sourced from the dist folder' {
        $module.modulebase |Should -Be $modulePath
    }
    it 'Imported module Version should match' {
        $module.version |Should -be $moduleVersion
    }
}
describe 'Check for module Dependancies' {
    $loadedModules = get-module
    $moduleDependencies
}
'@

    $pesterFolder = "$path\source\pester"
    $testFilename = 'baseModuleTest.ps1'
    
    }
    
    process{
        if(test-path $pesterFolder)
        {
            write-verbose 'Pester folder found'
            $fullname = "$($(get-item $pesterFolder).fullname)\$testfilename"
            write-verbose "Creating test file at $fullname"
            $fileContents | out-file $fullname -Force
            $orderFiles = @('.btOrderStart','.btOrderEnd')
            foreach($file in $orderFiles)
            {
                write-verbose "Checking if $testFilename in $file, adding if necessary"
                if(test-path $("$pesterFolder\$file"))
                {
                    $content = get-content $("$pesterFolder\$file")
                    if($content -notcontains ".\$testfilename")
                    {
                        write-verbose "adding to $file"
                        if($file -like '*end')
                        {
                            ".\$testfilename" | out-file $("$pesterFolder\$file") -Append
                        }else{
                            ".\$testfilename" | out-file $("$pesterFolder\$file") -force
                            $content |out-file $("$pesterFolder\$file") -Append
                        }
                        
                    }else{
                        write-verbose "Already present in $file"
                    }
                    
                    remove-variable content -ErrorAction Ignore
                }else{
                    write-warning "$file file not found"
                }
            }
        }else{
            write-error 'Unable to find pester folder'
        }
    }
    
}

function add-btFilesAndFolders
{
    <#
        .SYNOPSIS
            Add the files and folders required by Bartender
             
        .DESCRIPTION
            Check the folder structer and files are what the installed version are expecting
            Add them if they are not present
             
             
        .PARAMETER path
            The path of your bartender module
            Defaults to current working directory
         
        .PARAMETER force
            Required so that this is not accidentally called
         
        .EXAMPLE
            update-btFileStructure
             
            #### DESCRIPTION
            Check for differences in Bartender Versions
            Update if required
             
             
        .EXAMPLE
            add-btFilesAndFolders -force
             
            #### DESCRIPTION
            Add the folders and files
             
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2018-12-03
             
             
            Changelog:
                2018-12-03 - AA
                     
                    - Initial Script
                    - Migrated from update-btFileStructure
                2019-01-30
                    - Create new folder for revision
                    - Remove the old publishToken.txt
                2019-03-04
                    - Fix a bug where the stripping of auth tokens was accidentally a scriptblock
                2019-03-11
                    - Copy the readme.md if there is not one or the existing one is small
                     
         .COMPONENT
            Bartender
        .INPUTS
           null
        .OUTPUTS
            custom object
    #>

    [CmdletBinding()]
    PARAM(
        [string]$path = (Get-Item -Path ".\").FullName,
        [switch]$force
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        write-verbose "Using path: $path"
        write-debug "Using path: $path"
        
        if(!$force)
        {
            throw 'This function should be called from other functions, use -force if you wish to proceed anyway'
        }
    }
    
    process{
        
        if($force)
        {
            write-verbose 'Verifying the base folder structure'
            $directories = @('documentation','source','rev')
            
            foreach($directory in $directories)
            {
                $fullPath = "$path\$directory"
                if(test-path $fullPath)
                {
                    write-verbose "$directory ok"
                }else{
                    write-verbose "Need to create Source Folder for path $directory"
                    new-item -ItemType Directory -path $fullPath |Out-Null
                    new-item -ItemType File -Path "$fullPath\.gitignore" |Out-Null
                }
                
            }
            Write-Verbose 'Creating source structure'
            $directories = @('functions','enums','classes','pester','filters','dscClasses','validationClasses','private','resource','lib','bin')
            $files = @('.gitignore','.btignore','.btorder','.btorderStart','.btorderEnd')
            foreach($directory in $directories)
            {
                $fullPath = "$path\source\$directory"
                if(test-path $fullPath)
                {
                    write-verbose "$directory ok"
                }else{
                    Write-Verbose "Need to create Source Folder for path $directory"
                    new-item -ItemType Directory -path $fullPath |Out-Null
                    
                }
                foreach($file in $files)
                {
                    $fileFullPath = "$fullPath\$file"
                    if(test-path $fileFullPath)
                    {
                        write-verbose "$fileFullpath exists"
                        if($file -eq '.btorder')
                        {
                            write-verbose 'Renaming .btorder to .btorderstart'
                            rename-item -Path $fileFullPath -NewName '.btorderStart' -Force
                        }
                    }elseif($file -ne '.btorder'){
                        {
                        }
                        write-verbose "Creating $fileFullpath"
                        new-item -ItemType File -Path $fileFullPath
                    }
                }   
            }
            write-verbose 'Creating PostbuildScript folder'
            $directory = 'postBuildScripts'
            $fullPath = "$path\$directory"
            if(test-path $fullPath)
            {
                write-verbose 'postBuildScripts ok'
            }else{
                Write-Verbose 'Need to create postBuildScripts folder'
                new-item -ItemType Directory -path $fullPath |Out-Null
                
            }
            #This should still exist as a var, redeclaring in case it needs chaning in the future
            $files = @('.gitignore','.btignore','.btorder','.btorderStart','.btorderEnd')
            foreach($file in $files)
            {
                $fileFullPath = "$fullPath\$file"
                if(test-path $fileFullPath)
                {
                    write-verbose "$fileFullpath exists"
                    if($file -eq '.btorder')
                    {
                        write-verbose 'Renaming .btorder to .btorderstart'
                        rename-item -Path $fileFullPath -NewName '.btorderStart' -Force
                    }
                }elseif($file -ne '.btorder'){
                    {
                    }
                    write-verbose "Creating $fileFullpath"
                    new-item -ItemType File -Path $fileFullPath
                }
            }
            write-verbose 'Removing legacy publish token'
            
            $secrets = get-childitem -path $path -filter 'publishtoken.txt'
            if($secrets)
            {
                remove-item $secrets -force
                write-warning 'Removed legacy publishtoken.txt'
                write-warning 'Use the save-btRepository cmdlet to save repository settings'
            }
            
            
            #Add the pester files
            add-btBasicTests -path $path
            write-verbose 'Checking for readme.md'
            $readmePath = "$path\readme.md"
            $moduleBase = $(get-module bartender |sort-object -Property Version -Descending |Select-Object -First 1).moduleBase
            $readmeCopy = "$modulebase\resource\readme.md"
            if(test-path $readmeCopy)
            {
                if(!$(test-path $readmePath))
                {
                    #Always Copy
                    write-warning 'readme.md not found, copying from btmodule'
                    copy-item -Path $readmeCopy -Destination $readmePath -Force
                }elseIf($(get-content -path $readmePath).Length -le 30){
                    write-warning 'readme.md found, but length indicates not in use, copying from btModule'
                }else{
                    write-verbose 'readme.md found, length indicative file in use, will not copy from btModule'
                }
            }else{
                write-verbose 'Resource readme not found'
            }
            
        }else{
            throw 'This function should be called from other functions, use -force if you wish to proceed anyway'
        }     
        
        
    }
    
}

function get-btDocumentation
{
    <#
        .SYNOPSIS
            If you have platyPs on your system, extract the comment_based_help to markdown
             
        .DESCRIPTION
            Check if you have platyps
            If you have platyps:
             - launch the latest compiled version of the module in a scriptblock
             - Read the exported commands from the module manifest
             - Use platyPs to export the inline comment_based_help to MarkDown
             
             Markdown will be placed in the documentation folder under the respective version
             i.e. ..\documentation\1.0.0\my-function.ps1
             
        .PARAMETER path
            The path of your bartender module
            Defaults to current working directory
         
        .PARAMETER configFile
            The bartender configfile to use
            Defaults to btconfig.xml
             
        .EXAMPLE
            get-btDocumentation
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2018-05-17
             
             
            Changelog:
                2018-07-17 - AA
                     
                    - Attempted to only execute if there are functions to document
                2018-05-17 - AA
                     
                    - Initial Script
                    - Tested, working
                2019-01-30 - 2019-02-04 - AA
                    - Updated to use new folder path
                    - Code clean-up
                2019-03-07 - 2019-02-04 - AA
                    - Made a call to create md file for release notes
                    - Moved function help to a functions folder
                 
                     
        .COMPONENT
            Bartender
        .INPUTS
           null
        .OUTPUTS
            null
         
    #>

    [CmdletBinding()]
    PARAM(
        [string]$path = (Get-Item -Path ".\").FullName,
        [string]$configFile = 'btconfig.xml'
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $$(MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $$($MyInvocation.BoundParameters|Out-String)"
        $invocationPath = $path
        $scriptVars = @{}
        $scriptVars.configFilePath = "$invocationPath\$($configFile)"
        $scriptVars.testsPath = "$invocationPath\source\pester"
        $throwExceptions = @{}
        #NoConfig
        $errCat = [System.Management.Automation.ErrorCategory]::InvalidData
        $errMsg = [System.Exception]::new("Config file was not found.`nUse new-btproject for a new project.")
        $throwExceptions.noConfigError = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$scriptVars.configFile)
        #BadConfig
        $errMsg = [System.Exception]::new('Config file contents unexpected or malformed.')
        $throwExceptions.badConfigError = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$scriptVars.configFile)
        $errMsg = [System.Exception]::new('PlatyPS module not installed on this system. Please install it for this feature')
        $throwExceptions.noPlatyPs = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$scriptVars.configFile)
        $errMsg = [System.Exception]::new('Unable to compile Scriptblock')
        $throwExceptions.badScriptblock = [System.Management.Automation.ErrorRecord]::new($errMsg,1,$errCat,$scriptVars.configFile)
    }
    
    process{
        if(!$(test-path $scriptVars.configFilePath))
        {
            throw $throwExceptions.noConfigError
        }
        
        $scriptVars.config = Import-Clixml $scriptVars.configFilePath -ErrorAction Stop
        Write-Verbose "$($scriptvars.config|Out-String)"
        if(!$scriptVars.config.version -or !$scriptVars.config.moduleName -or !$scriptVars.config.moduleAuthor -or !$scriptVars.config.companyName)
        {
            throw $throwExceptions.badConfigError
        }else{
            write-verbose 'Configuring Params'
            $scriptVars.versionAsTag = $scriptVars.config.versionAsTag
            $scriptVars.moduleFolder = "$invocationPath\$($scriptVars.config.moduleName)\$($scriptVars.versionAsTag)"
            write-verbose 'Checking for PlatyPS'
            $scriptVars.platyPsModule = $(get-module -refresh -ListAvailable -Name platyPs |sort-object -Property Version -Descending |select-object -First 1)
            if(!$scriptVars.platyPsModule)
            {
                throw $throwExceptions.noPlatyPs
            }
            #import-module $scriptVars.platyPsModule
            #What do we need for this to work
            #The commands to get help for
            #The output folder
            #The imput folder
            write-verbose 'Constructing script-block text'
            $scriptVars.myTestBlock = "
                using module '$($scriptVars.moduleFolder)\'
                import-module PlatyPS
                `$moduleData = Import-PowerShellDataFile '$($scriptVars.moduleFolder)\$($scriptVars.config.moduleName).psd1'
                `$outputFolder = '$($invocationPath)\documentation\$($scriptVars.versionAsTag)\functions'
                if(!`$(test-path `$outputFolder))
                {
                    new-item -itemtype directory -path `$outputFolder
                }
                if(`$moduleData.FunctionsToExport.count -ge 1)
                {
                    foreach(`$function in `$moduleData.FunctionsToExport)
                    {
                        try{
                            new-markdownHelp -command `$function -outputfolder `$outputFolder -force
                        }catch{
                            write-warning unable to create help for `$function
                        }
                    }
                }else{
                    write-verbose 'No Functions to document'
                }
            "

            try{
                write-verbose 'Converting script block'
                $scriptBlock = [scriptblock]::Create($scriptVars.myTestBlock)
                write-verbose "ScriptBlock Contents:`n`n$($scriptVars.myTestBlock)"
            }catch{
                write-verbose "ScriptBlock Contents:`n`n$($scriptVars.myTestBlock)"
                throw $throwExceptions.badScriptblock
            }
            write-verbose 'Executing Scriptblock As Job'
            $job = start-job -ScriptBlock $scriptBlock 
            Wait-Job $job | Out-Null
            write-verbose 'Help should be created'
            write-verbose 'Creating release notes'
            $releaseNotesPath = "$($invocationPath)\documentation\$($scriptVars.versionAsTag)\release.md"
            try{
                get-btReleaseMarkdown|out-file $releaseNotesPath
                write-verbose 'release.md created'
            }catch{
                write-warning 'Error creating release notes'
            }
            try{
                update-btMarkdownHeader -path "$($invocationPath)"
                write-verbose 'Updated readme.md header if possible'
            }catch{
                write-warning 'Error updating readme.md'
            }
        }
    }
}

function get-btMarkdownFromHashtable
{
    <#
        .SYNOPSIS
            Simple description
             
        .DESCRIPTION
            Detailed Description
             
        .PARAMETER hashtable
            The Hashtable to convert to a Markdown Table
             
        ------------
        .EXAMPLE
            get-mdFromHashtable @{
                first = 'firstname'
                last = 'lastname'
            }
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2019-03-08
             
             
            Changelog:
                2019-03-08 - AA
                     
                    - Initial Script
                     
        .COMPONENT
            bartender
    #>

    [CmdletBinding()]
    PARAM(
        [hashtable]$hashtable
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        
    }
    
    process{
        $header = "|item|value|`n|:-:|:-:|"
        $body = foreach($key in $hashtable.Keys)
        {
            "|$key|$($hashtable."$key")|"
        }
        "$header`n$($body -join "`n")"
        
    }
    
}

function get-btReleaseMarkdown
{
    <#
        .SYNOPSIS
            Create a markdown file of release notes from the last release
             
        .DESCRIPTION
            Uses the get-btChangeDetails to get the function files that have changed
            Tries to create a new release markdown file
             
        .PARAMETER modulePath
            Path to the module
        .PARAMETER configFile
            btConfig xml file
        .PARAMETER versionOverride
            Used to build based on a different version tag
            Generally used for debug only
             
        ------------
        .EXAMPLE
            get-btChangeDetails
             
             
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2019-03-06
             
             
            Changelog:
                2019-03-06 - AA
                     
                    - Initial Script
                2019-03-08 - AA
                     
                    - Fixed up some spelling mistakes
                    - Fixed the badges
                    - Added files
                    - Added ability to override version
                    - Added badge for commentBasedHelp
                    - Moved everything into a single hear-string
                    - Added a dumb way to close bracket for md link
                    - Fixed a bug where I was multiplying the length by a kb instead of dividing
                2019-03-08 - AA
                    - Reduced kb decimal places to 2
                    - Removed the dumb way to close bracket for md link
                    - Fixed the markdown link
                    - Changed the codeblocks to markdown tables
                    - Center aligned the top badges
                    - Moved the bartender badge to the right
                    - Fix the order of unmodified functions
                    - Fix the spacing around releaseNotes
                2019-03-11 - AA
                    - Changed to handle updated get-btChangeDetails script
                     
        .COMPONENT
            Bartender
    #>

    [CmdletBinding()]
    PARAM(
        [string]$modulePath = $(get-location).path,
        [string]$configFile = 'btConfig.xml',
        [version]$versionOverride
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        if($modulePath -like '*\')
        {
            Write-Verbose 'Superfluous \ found in path, removing'
            $modulePath = $modulePath.Substring(0,$($modulePath.Length-1))
            Write-Verbose "New path = $modulePath"
        }
        
    }
    
    process{
        write-verbose 'Getting config settings'
        $configSettings = import-clixml "$modulePath\$configFile"
        if(!$configSettings)
        {
            throw 'Unable to find config file'
        }
        write-verbose 'Loading manifest and getting version'
        $versionString = if($versionOverride){
            $versionOverride.ToString()
        }else{
            $configSettings.versionAsTag
        }
        $manifestPath = "$modulePath\$($configSettings.moduleName)\$versionString\$($configSettings.moduleName).psd1"
        $metadata = import-metadata -Path $manifestPath
        write-verbose 'Setting Git Details'
        $gitDetails = get-btGitDetails -modulePath $modulePath
        if($gitDetails)
        {
            write-verbose 'Adding Git Details'
            $branch = $($gitDetails.branch.replace('* ','').trim())
            $gitHt = @{
                branch = $branch
                origin = $gitDetails.origin
                commit = $gitDetails.commitShort
            }
            $gitMarkdown = "---`n## Git Details`n$(get-btMarkdownFromHashtable $gitHt)`n"
            
        }
        write-verbose 'Retrieving Change Details'
        $changeDetails = get-btChangeDetails -modulePath $modulePath -newRelease
        if($changeDetails)
        {
            write-verbose 'Creating Summary Block'
            $summaryMarkdown = "---`n## Changes Summary`n$(get-btMarkdownFromHashtable $changeDetails.summary)`n"
            write-verbose 'Creating Files Section' 
            $mdFileStringSelector = @{
                name = 'mdString'
                expression = {"|$($_.basename)|$($_.relativePath)|$($_.extension)|$([math]::round($($_.length / 1kb),2))|"}
            }
            $filesHeader = "|name|path|extension|size(kb)`n|----------------|--------------------------------|-----|-----|"
            $newFiles = $($changeDetails.files|where-object{$_.fileIsNew -eq $true}|select-object $mdFileStringSelector).mdString | out-string
            $unmodFiles = $($changeDetails.files|where-object{$_.fileIsModified -eq $false -and $_.fileIsNew -eq $false}|select-object $mdFileStringSelector).mdString | out-string
            $modFiles = $($changeDetails.files|where-object{$_.fileIsModified -eq $true -and $_.fileIsNew -eq $false}|select-object $mdFileStringSelector).mdString | out-string
            if($newFiles)
            {
                $newFilesMd = "#### New Files`n$filesHeader`n$newFiles`n"
            }
            if($modFiles)
            {
                $modFilesMd = "#### Modified Files`n$filesHeader`n$modFiles`n"
            }
            if($unmodFiles)
            {
                $unmodFilesMd = "#### Unchanged Files`n$filesHeader`n$unmodFiles`n"
            }
            $filesMarkdown = "---`n## File`n`n### Summary`n`n$(get-btMarkdownFromHashtable $changeDetails.filesummary)`n`n### File List`n`n$newFilesMd`n$modFilesMd`n$unmodFilesMd`n"
            write-verbose 'Creating Functions Section' 
            $mdFunctionStringSelector = @{
                name = 'mdString'
                expression = {"|$($_.function)|$(if($_.folder -eq 'private'){"Private"}else{"Public"})|$(if($_.hasmarkdown){"[link](./functions/$($_.function).md)"})|$($_.relativePath)|"}
            }
            $functionsHeader = "|function|type|markdown link|filename|`n|-|-|-|-|"
            $newFuncs = $($changeDetails.functions|where-object{$_.fileIsNew -eq $true}|select-object $mdFunctionStringSelector).mdString | out-string
            $unmodFuncs = $($changeDetails.functions|where-object{$_.fileIsModified -eq $false -and $_.fileIsNew -eq $false}|select-object $mdFunctionStringSelector).mdString | out-string
            $modFuncs = $($changeDetails.functions|where-object{$_.fileIsModified -eq $true -and $_.fileIsNew -eq $false}|select-object $mdFunctionStringSelector).mdString | out-string
            if($newFuncs)
            {
                $newFuncsMd = "#### New Functions`n$functionsHeader`n$newFuncs"
            }
            if($modFuncs)
            {
                $modFuncsMd = "#### Modified Functions`n$functionsHeader`n$modFuncs"
            }
            if($unmodFuncs)
            {
                $unmodFuncsMd = "#### Unmodified Functions`n$functionsHeader`n$unmodFuncs"
            }
            $functionsMarkdown = "---`n## Functions`n`n### Summary`n`n$(get-btMarkdownFromHashtable $changeDetails.functionSummary)`n`n### Function List`n`n$newFuncsMd`n$modFuncsMd`n$unmodFuncsMd"
        }
        write-verbose 'Getting any Required Modules'
        $requiredModulesSelector = @{
            name = 'rmString'
            expression = {"|$($_.moduleName)|$($_.RequiredVersion)|"}
        }
        $modulesmd = $($($metadata.requiredModules|select-object $requiredModulesSelector).rmString|out-string)
        if($modulesmd)
        {
            $modulesMarkdown = "---`n## Required Modules`n|moduleName|requiredVersion|`n|-|-|`n$modulesmd`n"
        }
        
        write-verbose 'Getting Pester Details'
        if($metadata.privatedata.pester)
        {
            write-verbose 'Adding Pester Details'
            $pesterMarkdown = "---`n## Pester Details`n$(get-btMarkdownFromHashtable $metadata.privatedata.pester)`n"
            $badgeColor = switch ($($metadata.privatedata.pester.codecoverage)) {
                {$_ -le 20} {"red";break;}
                {$_ -le 40} {"orange";break;}
                {$_ -le 60} {"yellow"; break;}
                {$_ -le 75} {"yellowgreen"; break;}
                {$_ -le 90} {"green"; break;}
                {$_ -le 100} {"brightgreen"; break;}
                default {"lightgrey"; break;}
            }
            $pesterBadge = "[pesterbadge]: https://img.shields.io/static/v1.svg?label=pester&message=$($metadata.privatedata.pester.codecoverage)&color=$badgeColor"
        }else{
            $pesterMarkdown = $null
            $pesterBadge = '[pesterbadge]: https://img.shields.io/static/v1.svg?label=pester&message=na&color=lightgrey'
        }
        write-verbose 'Generating GIT Badges'
        $btbadge = "[btbadge]: https://img.shields.io/static/v1.svg?label=bartender&message=$($metadata.PrivateData.bartenderVersion)&color=0B2047"
        $releaseBadge = "[releasebadge]: https://img.shields.io/static/v1.svg?label=version&message=$($metadata.moduleVersion)&color=blue"
        $commentBasedHelpCoverage = $changeDetails.summary.commentBasedHelpCoverage
        if(!$commentBasedHelpCoverage)
        {
            $commentBasedHelpCoverage = 'na'
        }
        $badgeColor = switch ($commentBasedHelpCoverage) {
            {$_ -le 20} {"red";break;}
            {$_ -le 40} {"orange";break;}
            {$_ -le 60} {"yellow"; break;}
            {$_ -le 75} {"yellowgreen"; break;}
            {$_ -le 90} {"green"; break;}
            {$_ -le 100} {"brightgreen"; break;}
            default {"lightgrey"; break;}
        }
        $helpCoverage = "[helpcoveragebadge]: https://img.shields.io/static/v1.svg?label=get-help&message=$commentBasedHelpCoverage&color=$badgeColor"
        
        
        write-verbose 'Creating Overview'
        $overviewHt = @{
            BuildDate = $($metaData.privatedata.builtOn)
            'Author(s)' = $($metaData.author)
            BuildUser = $($metaData.privateData.builtBy)
            Company = $($metaData.CompanyName)
        }
        $overviewMarkdown = "## Overview`n$(get-btMarkdownFromHashtable $overviewHt)`n"
        write-verbose 'Generating Final Markdown'
        #Still hate herestring and how it deals with tabs
        
        $markdown = @"
# $($configSettings.moduleName) - Release $versionString
| Version | Code Coverage | Code Based Help Coverage |Bartender Version|
|:-------------------:|:-------------------:|:-------------------:|:-------------------:|
|![releasebadge]|![pesterbadge]|![helpcoveragebadge]|![btbadge]|
$overviewMarkdown
`n
$(
    if($metadata.privatedata.psdata.releasenotes)
    {
        "### Release Notes:`n`n$($metadata.privatedata.psdata.releasenotes)`n`n"
    }
)
`n
$summaryMarkdown
`n
$filesMarkdown
`n
$functionsMarkdown
`n
$modulesMarkdown
`n
$pesterMarkdown
`n
$gitMarkdown
`n
$pesterbadge
$btbadge
$releaseBadge
$helpCoverage
"@

        $markdown
        
    }
    
    
}

function get-btScriptFunctions
{
    <#
        .SYNOPSIS
            Try and get all the functions from a script
             
        .DESCRIPTION
            Try and get all the functions from a script
             
        .PARAMETER path
            PS1 file to check
             
        .EXAMPLE
            get-scriptFunctions myscript.ps1
             
        DESCRIPTION
            ------------
            Try and get all the functions from a script
             
             
        OUTPUT
        ------------
            Copy of the output of this line
             
             
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2017-11-13
             
             
            Changelog:
                2017-11-13 - AA
                     
                    - Initial Script
                2019-03-06 - AA
                    - Copied into BarTender module
                     
        .COMPONENT
            What cmdlet does this script live in
    #>

    [CmdletBinding()]
    PARAM(
        [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$false,Position=0)]
        [string]$path
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        
    }
    
    process{
        $item = get-item $path
        if(Test-Path $item)
        {
            $contents = get-content $item
            foreach($line in $contents)
            {
                if($line -like 'function *')
                {
                    $($line -split 'function ')[1]
                }
            }
        }else{
            Write-Error 'Nope, cannot find file'
        }
        
    }
    
}

function get-btStringComparison
{
    <#
        .SYNOPSIS
            Make a comparison between two strings
            See how many characters are the same by counting them up
        .DESCRIPTION
            Compare String1 to String2
            Group each string by the number of characters
            Compare the comparison
            Work out how similar the character composition is of both strings
             
            Works with the group-object function
            As such, provides a very efficient way of comparing string similarity
            It can compare two files of ~ 200,000 chars each, in about 6 seconds
             
        .PARAMETER string1
            the first string to compare
        .PARAMETER string2
            the second string to compare
        .EXAMPLE
            get-btStringComparison -string1 $(get-content $file1) -string2 $(get-content $file2)
             
        #### DESCRIPTION
            Find out how similar two files are
             
             
        #### OUTPUT
            Copy of the output of this line
             
             
        .OUTPUTS
            TotalChars1 TotalChars2 Difference DiffPercent
            ----------- ----------- ---------- -----------
                210269 210269 0 0
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2019-03-11
             
             
            Changelog:
                2019-03-11 - AA
                    - Changed Initial Script
                     
        .COMPONENT
            What cmdlet does this script live in
    #>

    [CmdletBinding()]
    param(
        [ValidateNotNullOrEmpty()]
        [string]$string1,
        [ValidateNotNullOrEmpty()]
        [string]$string2
    )
    $length1 = $string1.Length
    $length2 = $string2.Length
    $charArr1 = $string1.ToCharArray()
    $charArr2 = $string2.ToCharArray()
    $charArrU = $charArr1 + $charArr2 |Select-object -Unique
    $summary1 = $charArr1|group-object|select-object name,count
    $summary2 = $charArr2|group-object|select-object name,count
    $summaryAll = foreach($char in $charArrU)
    {
        $c1 = $($summary1|where-object {$_.name -eq $char}).Count
        $c2 = $($summary2|where-object {$_.name -eq $char}).Count
        $diff = $c2 - $c1
        if($diff -lt 0)
        {
            #FlipToPos
            $diff = $diff * -1
        }
        [psCustomObject]@{
            char = $char
            count1 = $c1
            count2 = $c2
            diff = $diff
        }
    }
    $totals = [pscustomobject]@{
        TotalChars1 = $length1
        TotalChars2 = $length2
        Difference = $($summaryAll.diff|measure-object -sum).Sum
        DiffPercent = [math]::round($($($summaryAll.diff|measure-object -sum).Sum / $(@($length1,$lenght2)|measure-object -maximum).Maximum)*100,2)
    }
    $totals
    
}

function start-btRevisionCleanup
{
    <#
        .SYNOPSIS
            Clean-up bt revisions folder
             
        .DESCRIPTION
            Remove any previous revisions to try and keep the size down
            By default keep the last 5
             
             
        .PARAMETER path
            The path of your bartender module
            Defaults to current working directory
         
        .EXAMPLE
            start-btRevisionCleanup
             
            #### DESCRIPTION
            Remove previous revisions
             
             
        .NOTES
            Author: Adrian Andersson
            Last-Edit-Date: 2019-12-03
             
             
            Changelog:
                2019-01-30
                     
                    - Initial Script
                    - Now we are dealing with revisions, we need a way to not keep creeping the revisions up
                     
         .COMPONENT
            Bartender
        .INPUTS
           null
        .OUTPUTS
            custom object
    #>

    [CmdletBinding()]
    PARAM(
        [string]$path = (Get-Item -Path ".\").FullName,
        [string]$configFile = 'btconfig.xml',
        [int]$revisions
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        $defaultRevisions = 5
        $invocationPath = $path
        $scriptVars = @{}
        $scriptVars.configFilePath = "$invocationPath\$($configFile)"
        $scriptVars.revisionPath = "$invocationPath\rev" 
        
    }
    
    process{
        $scriptVars.config = Import-Clixml $scriptVars.configFilePath -ErrorAction Stop
        Write-Verbose "$($scriptvars.config|Out-String)"
        if(!$scriptVars.config.version -or !$scriptVars.config.moduleName -or !$scriptVars.config.moduleAuthor -or !$scriptVars.config.companyName)
        {
            throw "Config file was not found.`nUse new-btproject for a new project."
        }
        
        if($revisions -ge 1)
        {
            write-verbose 'Using revision param count'
        }else{
            write-verbose 'Reading revision count from config'
            $revisions = $scriptVars.config.revisionCount
            if(!$revisions)
            {
                write-verbose "Revision count not found, using default of $defaultRevisions"
                $revisions = $defaultRevisions
                try{
                    $scriptVars.config.revisionCount = $defaultRevisions
                }catch{
                    $scriptVars.config|add-member -MemberType NoteProperty -Name revisionCount -Value $defaultRevisions
                }
                
                write-verbose 'Saving default to config file'
                Export-Clixml -Path $scriptVars.configFilePath -InputObject $scriptVars.config
                write-verbose 'Config file updated'
            }
        }
        if(!$revisions)
        {
            throw 'Something wrong with Revision counter'
        }
        write-verbose "Revisions set to: $revisions"
        #If we parse a version from the name, then we can sort properly
        #That way if the dates are all strange from a git view, it wont matter
        #And name itself might be odd, consider version 1.1,10.1,2.1,20.1 and the order that would be in
        $versionSelect = @{
            name ='version'
            expression = {[version]::Parse($($_.name))}
        }
        write-verbose 'Getting revisions'
        #Use * so we can still pipe it to remove-item
        $childItems = get-childItem $scriptVars.revisionPath|select-object *,$versionSelect|where-object{$null -ne $_.version}
        $currentCount = $($childItems|measure-object).count
        $removeCount = $currentCount - $revisions
        if($removeCount -ge 1)
        {
            write-verbose "Need to remove $removeCount versions"
            $childItems|sort-object -property version|select-object -first $removeCount|remove-item -Force -Recurse
        }else{
            write-verbose 'No revisions to remove'
        } 
    }
    
}

function update-btMarkdownHeader
{
    <#
        .SYNOPSIS
            Update the top of a markdown file
             
        .DESCRIPTION
            Update the top of a markdown file
            Executes on build to add appropriate info to the header section
            Will also update the name and description
             
        .PARAMETER path
            Path to the module
        .PARAMETER configFile
            btConfig file
        .PARAMETER markdownFile
            Markdown file name
             
        ------------
        .EXAMPLE
            update-btMarkdownHeader
             
             
             
        .NOTES
            Author: Adrian Andersson
             
             
            Changelog:
                2019-03-10 - AA
                     
                    - Initial Script
                    - Updated start-btBuild to get release data
                2019-03-11 - AA
                    - Fixed the icon missing
                     
        .COMPONENT
            What cmdlet does this script live in
    #>

    [CmdletBinding()]
    PARAM(
        [string]$path = (Get-Item -Path ".\").FullName,
        [string]$configFile = 'btconfig.xml',
        [string]$markdownFile = 'readme.md'
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        
        $configFilePath = "$path\$($configFile)"
        $markdownFilePath = "$path\$($markdownFile)"
        $headerMatch = '<!--Bartender Dynamic Header -- Code Below Here -->'
        
    }
    
    process{
        write-verbose 'Importing config'
        if(!$(test-path $configFilePath))
        {
            throw "$configFile not found"
        }
        $config = import-clixml $configFilePath
        if(!(test-path $markdownFilePath))
        {
            throw "$markdownfile not found"
        }
        write-verbose 'Importing existing markdown'
        $content = get-content $markdownFilePath
        if($config.lastrelease.version)
        {
            $versionTag = $config.lastrelease.version.tostring(3)
            $releaseDate = get-date $config.lastRelease.date -Format yyyy-MM-dd
            
        }else{
            $versionTag = 'na'
            $releaseDate = 'na'
        }
        if(test-path "$path\documentation\$versionTag\release.md")
        {
            $latestReleaseLink = "Latest Release Notes: [here](./documentation/$versionTag/release.md)"
        }else{
            $latestReleaseLink = $null
        }
        write-verbose 'Creating Shield Badges'
        $badges = @{
            releasebadge = "[releasebadge]: https://img.shields.io/static/v1.svg?label=version&message=$versionTag&color=blue"
            datebadge = "[datebadge]: https://img.shields.io/static/v1.svg?label=Date&message=$releaseDate&color=yellow"
            powershellBadge = "[psbadge]: https://img.shields.io/static/v1.svg?label=PowerShell&message=$($config.minimumPsVersion.ToString(3))&color=5391FE&logo=powershell"
            btBadge = "[btbadge]: https://img.shields.io/static/v1.svg?label=bartender&message=$($config.bartenderVersion.toString())&color=0B2047"
        }
        write-verbose 'Generating new Markdown'
        $headerArr = @(
            "# $($config.modulename.toUpper())",
            "$(if(test-path "$path\icon.png"){'![logo](./icon.png)'})",
            "",
            "> $($config.moduleDescription)",
            "",
            "$($badges.releasebadge)",
            "$($badges.datebadge)",
            "$($badges.powershellBadge)",
            "$($badges.btBadge)",
            '',
            '',
            "| Language | Release Version | Release Date | Bartender Version |",
            "|:-------------------:|:-------------------:|:-------------------:|:-------------------:|",
            "|![psbadge]|![releasebadge]|![datebadge]|![btbadge]|"
            '',
            '',
            "Authors: $($config.moduleAuthor -join ',')",
            '',
            "$(if($config.companyName){"Company: $($config.companyName)"})",
            '',
            "$latestReleaseLink",
            '',
            '***',
            ''
        )
        write-verbose 'Checking for header marker for markdown entry point'
        $i = 0
        $contentLines = $content.count
        $lineMatch = -1
        while($i -lt $contentLines -and $lineMatch -lt 0)
        {
            if($content[$i] -eq $headerMatch)
            {
                $lineMatch = $i
            }
            $i++
        }
        if($lineMatch -gt 0)
        {
            write-verbose "Found line at $lineMatch"
            $remainder = $content[$lineMatch..$contentLines]
            $newContent = $headerArr + $remainder
            $contentMarkdown = $newContent -join "`n"
            if($contentMarkdown)
            {
                write-verbose 'Exporting updated markdown file'
                $contentMarkdown|out-file $markdownFilePath
            }
        }
    }
    
}