ProfileFever.psm1

<#
    .SYNOPSIS
        Import the profile configuration file.
#>

function Import-ProfileConfig
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Path
    )

    $configs = Get-Content -Path $Path | ConvertFrom-Json

    # Update configurations
    if ($configs.Name -contains "$Env:ComputerName\$Env:Username")
    {
        $config = $configs.Where({$_.Name -eq "$Env:ComputerName\$Env:Username"})[0]
    }
    else
    {
        $config = $configs.Where({$_.Name -eq 'Default'})[0]
    }

    Write-Output $config
}

<#
    .SYNOPSIS
        Show the headline with information about the local system and current
        user.
#>

function Show-HostHeadline
{
    # Get Windows version from registry. Update the object for non Windows 10 or
    # Windows Server 2016 systems to match the same keys.
    $osVersion = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
    if ($null -eq $osVersion.ReleaseId)
    {
        $osVersion | Add-Member -MemberType NoteProperty -Name 'ReleaseId' -Value $osVersion.CurrentVersion
    }
    if ($null -eq $osVersion.UBR)
    {
        $osVersion | Add-Member -MemberType NoteProperty -Name 'UBR' -Value '0'
    }

    # Rename the ConsoleHost string to a nice understandable string
    $profileHost = $Host.Name.Replace('ConsoleHost', 'Windows PowerShell Console Host')

    # Get the PowerShell version depending on the edition
    if ($PSVersionTable.PSEdition -eq 'Core')
    {
        $psVersion = 'Version {0}' -f $PSVersionTable.PSVersion
    }
    else
    {
        $psVersion = 'Version {0}.{1} (Build {2}.{3})' -f $PSVersionTable.PSVersion.Major, $PSVersionTable.PSVersion.Minor, $PSVersionTable.PSVersion.Build, $PSVersionTable.PSVersion.Revision
    }

    $Host.UI.WriteLine(('{0}, Version {1} (Build {2}.{3})' -f $osVersion.ProductName, $osVersion.ReleaseId, $osVersion.CurrentBuildNumber, $osVersion.UBR))
    $Host.UI.WriteLine(('{0}, {1}' -f $profileHost, $psVersion))
    $Host.UI.WriteLine()
    $Host.UI.WriteLine(('{0}\{1} on {2}, Uptime {3:%d} day(s) {3:hh\:mm\:ss}' -f $Env:USERDOMAIN, $Env:USERNAME, $Env:COMPUTERNAME.ToUpper(), [System.TimeSpan]::FromMilliseconds([System.Environment]::TickCount)))
    $Host.UI.WriteLine()
}

<#
    .SYNOPSIS
        Set the console host configuration.
#>

function Set-ConsoleConfig
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalFunctions', '')]
    param
    (
        [Parameter(Mandatory = $false)]
        [System.Int32]
        $WindowWidth,

        [Parameter(Mandatory = $false)]
        [System.Int32]
        $WindowHeight,

        [Parameter(Mandatory = $false)]
        [System.ConsoleColor]
        $ForegroundColor,

        [Parameter(Mandatory = $false)]
        [System.ConsoleColor]
        $BackgroundColor
    )


    # Step 1: Window and buffer size
    if ($PSCmdlet.ShouldProcess('Window and Buffer', 'Change Size'))
    {
        $bufferSize = $Global:Host.UI.RawUI.BufferSize
        $windowSize = $Global:Host.UI.RawUI.WindowSize

        $bufferSize.Height = 9999

        if ($PSBoundParameters.ContainsKey('WindowWidth'))
        {
            $bufferSize.Width = $WindowWidth
            $windowSize.Width = $WindowWidth
        }

        if ($PSBoundParameters.ContainsKey('WindowHeight'))
        {
            $windowSize.Height = $WindowHeight
        }

        $Global:Host.UI.RawUI.BufferSize = $bufferSize
        $Global:Host.UI.RawUI.WindowSize = $windowSize
    }


    # Step 2: Window foreground and background color
    if ($PSCmdlet.ShouldProcess('Color', 'Change Color'))
    {
        if ($PSBoundParameters.ContainsKey('ForegroundColor'))
        {
            $Global:Host.UI.RawUI.ForegroundColor = $ForegroundColor
        }

        if ($PSBoundParameters.ContainsKey('BackgroundColor'))
        {
            $Global:Host.UI.RawUI.BackgroundColor = $BackgroundColor
        }
    }
}

<#
    .SYNOPSIS
        Play the fun tool of Rick Astley.
#>

function Start-RickAstley
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    param ()

    Start-Process -FilePath 'powershell.exe' -ArgumentList '-noprofile -noexit -command iex (New-Object Net.WebClient).DownloadString(''http://bit.ly/e0Mw9w'')'
}

<#
    .SYNOPSIS
        Add a command not found action to the list of actions.
#>

function Add-CommandNotFoundAction
{
    [CmdletBinding()]
    param
    (
        # Name of the command.
        [Parameter(Mandatory = $true)]
        [System.String]
        $CommandName,

        # For the remoting command, set the computer name of the target system.
        [Parameter(Mandatory = $true)]
        [Parameter(ParameterSetName = 'RemotingWithCredential')]
        [Parameter(ParameterSetName = 'RemotingWithVault')]
        [System.String]
        $ComputerName,

        # For the remoting command, set the credentials.
        [Parameter(Mandatory = $false, ParameterSetName = 'RemotingWithCredential')]
        [System.Management.Automation.PSCredential]
        $Credential,

        # For the remoting command, but only a pointer to the credential vault.
        [Parameter(Mandatory = $true, ParameterSetName = 'RemotingWithVault')]
        [System.String]
        $VaultTargetName,

        # Define a script block to execute for the command.
        [Parameter(Mandatory = $true, ParameterSetName = 'ScriptBlock')]
        [System.Management.Automation.ScriptBlock]
        $ScriptBlock
    )

    $command = @{
        CommandName = $CommandName
    }

    switch ($PSCmdlet.ParameterSetName)
    {
        'RemotingWithCredential'
        {
            $command['CommandType']  = 'Remoting'
            $command['ComputerName'] = $ComputerName
            $command['Credential']   = $Credential
        }

        'RemotingWithVault'
        {
            $command['CommandType']     = 'Remoting'
            $command['ComputerName']    = $ComputerName
            $command['VaultTargetName'] = $VaultTargetName
        }

        'ScriptBlock'
        {
            $command['CommandType'] = 'ScriptBlock'
            $command['ScriptBlock'] = $ScriptBlock
        }
    }

    $Script:CommandNotFoundAction += [PSCustomObject] $command
}

<#
    .SYNOPSIS
        Unregister the command not found action callback.
#>

function Disable-CommandNotFoundAction
{
    [CmdletBinding()]
    param ()

    $Global:ExecutionContext.InvokeCommand.CommandNotFoundAction = $null
}

<#
    .SYNOPSIS
        Register the command not found action callback.
#>

function Enable-CommandNotFoundAction
{
    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalFunctions', '')]
    param ()

    $Global:ExecutionContext.InvokeCommand.CommandNotFoundAction = {
        param ($CommandName, $CommandLookupEventArgs)

        foreach ($command in $Script:CommandNotFoundAction)
        {
            if ($command.CommandName -eq $CommandName)
            {
                $commandLine = (Get-PSCallStack)[1].Position.Text.Trim()

                switch ($command.CommandType)
                {
                    'Remoting'
                    {
                        $credentialSplat = @{}
                        if ($command.Credential)
                        {
                            $credentialSplat['Credential'] = $command.Credential
                            $credentialVerbose = " -Credential '{0}'" -f $command.Credential.UserName
                        }
                        if ($command.VaultTargetName)
                        {
                            $credential = Get-VaultCredential -TargetName $command.VaultTargetName
                            $credentialSplat['Credential'] = $credential
                            $credentialVerbose = " -Credential '{0}'" -f $credential.UserName
                        }

                        # Option 1: Enter Session
                        # If no parameters were specified, just enter into a
                        # remote session to the target system.
                        if ($CommandName -eq $commandLine)
                        {
                            Write-Verbose ("Enter-PSSession -ComputerName '{0}'{1}" -f $command.ComputerName, $credentialVerbose)

                            $CommandLookupEventArgs.StopSearch = $true
                            $CommandLookupEventArgs.CommandScriptBlock = {
                                $session = New-PSSession -ComputerName $command.ComputerName @credentialSplat -ErrorAction Stop
                                if ($Host.Name -eq 'ConsoleHost')
                                {
                                    Invoke-Command -Session $session -ErrorAction Stop -ScriptBlock { Set-Location -Path "$Env:SystemDrive\"; $PromptLabel = $Env:ComputerName.ToUpper(); $PromptIndent = $using:session.ComputerName.Length + 4; function Global:prompt { Write-Host "[$PromptLabel]" -NoNewline -ForegroundColor Cyan; "$("`b `b" * $PromptIndent) $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) " } }
                                }
                                Enter-PSSession -Session $session -ErrorAction Stop
                            }.GetNewClosure()
                        }

                        # Option 2: Open Session
                        # If a variable is specified as output of the command,
                        # a new remoting session will be opened and returned.
                        $openSessionRegex = '^\$\S+ = {0}$' -f ([System.Text.RegularExpressions.Regex]::Escape($CommandName))
                        if ($commandLine -match $openSessionRegex)
                        {
                            Write-Verbose ("New-PSSession -ComputerName '{0}'{1}" -f $command.ComputerName, $credentialVerbose)

                            $CommandLookupEventArgs.StopSearch = $true
                            $CommandLookupEventArgs.CommandScriptBlock = {
                                New-PSSession -ComputerName $command.ComputerName @credentialSplat -ErrorAction Stop
                            }.GetNewClosure()
                        }

                        # Option 3: Invoke Command
                        # If a script is appended to the command, execute that
                        # script on the remote system.
                        if ($commandline.StartsWith($CommandName) -and $commandLine.Length -gt $CommandName.Length)
                        {
                            $scriptBlock = [System.Management.Automation.ScriptBlock]::Create($commandLine.Substring($CommandName.Length).Trim())

                            Write-Verbose ("Invoke-Command -ComputerName '{0}'{1} -ScriptBlock {{ {2} }}" -f $command.ComputerName, $credentialVerbose, $scriptBlock.ToString())

                            $CommandLookupEventArgs.StopSearch = $true
                            $CommandLookupEventArgs.CommandScriptBlock = {
                                Invoke-Command -ComputerName $command.ComputerName @credentialSplat -ScriptBlock $scriptBlock -ErrorAction Stop
                            }.GetNewClosure()
                        }
                    }

                    'ScriptBlock'
                    {
                        throw 'Not implemented!'
                    }
                }
            }
        }
    }
}

<#
    .SYNOPSIS
        Disable the custom prompt and restore the default prompt.
#>

function Disable-Prompt
{
    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalFunctions', '')]
    param ()

    function Global:Prompt
    {
        "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "
        # .Link
        # http://go.microsoft.com/fwlink/?LinkID=225750
        # .ExternalHelp System.Management.Automation.dll-help.xml
    }
}

<#
    .SYNOPSIS
        Disable the prompt alias recommendation output after each command.
#>

function Disable-PromptAlias
{
    [CmdletBinding()]
    [Alias('dalias')]
    param ()

    Remove-Variable -Scope Script -Name PromptAlias -ErrorAction SilentlyContinue -Force
    New-Variable -Scope Script -Option ReadOnly -Name PromptAlias -Value $false -Force
}

<#
    .SYNOPSIS
        Disable the git repository status in the prompt.
#>

function Disable-PromptGit
{
    [CmdletBinding()]
    [Alias('dgit')]
    param ()

    Remove-Variable -Scope Script -Name PromptGit -ErrorAction SilentlyContinue -Force
    New-Variable -Scope Script -Option ReadOnly -Name PromptGit -Value $false -Force
}

<#
    .SYNOPSIS
        Disable the prompt timestamp output.
#>

function Disable-PromptTimeSpan
{
    [CmdletBinding()]
    [Alias('dtimespan')]
    param ()

    Remove-Variable -Scope Script -Name PromptTimeSpan -ErrorAction SilentlyContinue -Force
    New-Variable -Scope Script -Option ReadOnly -Name PromptTimeSpan -Value $false -Force
}

<#
    .SYNOPSIS
        Enable the custom prompt by replacing the default prompt.
#>

function Enable-Prompt
{
    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalFunctions', '')]
    param ()

    function Global:Prompt
    {
        if ($Script:PromptHistory -ne $MyInvocation.HistoryId)
        {
            $Script:PromptHistory = $MyInvocation.HistoryId

            if ($Script:PromptAlias) { Show-PromptAliasSuggestion }
            if ($Script:PromptTimeSpan) { Show-PromptLastCommandDuration }
        }

        $Host.UI.Write($Script:PromptColor, $Host.UI.RawUI.BackgroundColor, '[{0:dd MMM HH:mm}]' -f [DateTime]::Now)
        $Host.UI.Write(" $($ExecutionContext.SessionState.Path.CurrentLocation)")
        if ($Script:PromptGit) { Write-VcsStatus }
        return "`n$($MyInvocation.HistoryId.ToString().PadLeft(3, '0'))$('>' * ($NestedPromptLevel + 1)) "
    }
}

<#
    .SYNOPSIS
        Enable the prompt alias recommendation output after each command.
#>

function Enable-PromptAlias
{
    [CmdletBinding()]
    [Alias('ealias')]
    param ()

    Remove-Variable -Scope Script -Name PromptAlias -ErrorAction SilentlyContinue -Force
    New-Variable -Scope Script -Option ReadOnly -Name PromptAlias -Value $true -Force
}

<#
    .SYNOPSIS
        Enable the git repository status in the prompt.
#>

function Enable-PromptGit
{
    [CmdletBinding()]
    [Alias('egit')]
    param ()

    if ($null -eq (Get-Module -Name posh-git))
    {
        Import-Module -Name posh-git -Force
    }

    Remove-Variable -Scope Script -Name PromptGit -ErrorAction SilentlyContinue -Force
    New-Variable -Scope Script -Option ReadOnly -Name PromptGit -Value $true -Force
}

<#
    .SYNOPSIS
        Enable the prompt timestamp output.
#>

function Enable-PromptTimeSpan
{
    [CmdletBinding()]
    [Alias('etimespan')]
    param ()

    Remove-Variable -Scope Script -Name PromptTimeSpan -ErrorAction SilentlyContinue -Force
    New-Variable -Scope Script -Option ReadOnly -Name PromptTimeSpan -Value $true -Force
}

<#
    .SYNOPSIS
        Show the alias suggestion for the latest command.
#>

function Show-PromptAliasSuggestion
{
    [CmdletBinding()]
    param ()

    if ($MyInvocation.HistoryId -gt 1)
    {
        $history = Get-History -Id ($MyInvocation.HistoryId - 1)
        $reports = @()
        foreach ($alias in (Get-Alias))
        {
            if ($history.CommandLine.IndexOf($alias.ResolvedCommandName) -ne -1)
            {
                $reports += $alias
            }
        }
        if ($reports.Count -gt 0)
        {
            $report = $reports | Group-Object -Property 'ResolvedCommandName' | ForEach-Object { ' ' + $_.Name + ' => ' + ($_.Group -join ', ') }
            $Host.UI.WriteLine('Magenta', $Host.UI.RawUI.BackgroundColor, "Alias suggestions:`n" + ($report -join "`n"))
        }
    }
}

<#
    .SYNOPSIS
        Show the during of the last executed command.
#>

function Show-PromptLastCommandDuration
{
    [CmdletBinding()]
    param ()

    if ($MyInvocation.HistoryId -gt 1 -and $Host.UI.RawUI.CursorPosition.Y -gt 0)
    {
        $history  = Get-History -Id ($MyInvocation.HistoryId - 1)
        $duration = ($history.EndExecutionTime - $history.StartExecutionTime).ToString('\ \ \ h\:mm\:ss\.ffff')

        # Move cursor one up and to the right to show the execution time.
        $position = $Host.UI.RawUI.CursorPosition
        $position.Y = $position.Y - 1
        $position.X = $Host.UI.RawUI.WindowSize.Width - $duration.Length
        $Host.UI.RawUI.CursorPosition = $position

        $Host.UI.Write('Gray', $Host.UI.RawUI.BackgroundColor, $duration)
    }
}

<#
    .SYNOPSIS
        Disable the information output stream for the global shell.
#>

function Disable-Information
{
    [CmdletBinding()]
    [Alias('di')]
    param ()

    Set-Variable -Scope Global -Name InformationPreference -Value 'SilentlyContinue'
}

<#
    .SYNOPSIS
        Disable the verbose output stream for the global shell.
#>

function Disable-Verbose
{
    [CmdletBinding()]
    [Alias('dv')]
    param ()

    Set-Variable -Scope Global -Name VerbosePreference -Value 'SilentlyContinue'
}
<#
    .SYNOPSIS
        Enable the information output stream for the global shell.
#>

function Enable-Information
{
    [CmdletBinding()]
    [Alias('ei')]
    param ()

    Set-Variable -Scope Global -Name InformationPreference -Value 'Continue'
}

<#
    .SYNOPSIS
        Enable the verbose output stream for the global shell.
#>

function Enable-Verbose
{
    [CmdletBinding()]
    [Alias('ev')]
    param ()

    Set-Variable -Scope Global -Name VerbosePreference -Value 'Continue'
}

<#
    .SYNOPSIS
        Update the workspace configuration for Visual Studio Code which is used
        by the extension vscode-open-project.
 
    .LINK
        https://marketplace.visualstudio.com/items?itemName=svetlozarangelov.vscode-open-project
#>

function Update-Workspace
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param
    (
        [Parameter(Mandatory = $false)]
        [System.String]
        $Path = "$HOME\Workspace",

        [Parameter(Mandatory = $false)]
        [System.String]
        $ProjectListPath = "$Env:AppData\Code\User\projectlist.json"
    )

    $projectList = @{
        projects = [Ordered] @{}
    }

    foreach ($group in (Get-ChildItem -Path $Path -Directory))
    {
        foreach ($repo in (Get-ChildItem -Path $group.FullName -Directory))
        {
            $key = '{0} \ {1}' -f $group.Name, $repo.Name

            $projectList.projects.Add($key, $repo.FullName)
        }
    }

    if ($PSCmdlet.ShouldProcess($ProjectListPath, 'Update Project List'))
    {
        $projectList | ConvertTo-Json | Set-Content -Path $ProjectListPath
    }
}


# Module profile configuration variables
$Script:PromptHistory  = 0
$Script:PromptAdmin    = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
$Script:PromptColor    = $(if($Script:PromptAdmin) { 'Red' } else { 'DarkCyan' })
$Script:PromptAlias    = $false
$Script:PromptTimeSpan = $false
$Script:PromptGit      = $false

# Module command not found action variables
$Script:CommandNotFoundAction = @()