ProfileFever.psm1
<#
.SYNOPSIS Root module file. .DESCRIPTION The root module file loads all classes, helpers and functions into the module context. #> ## Module loader <# .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 Connect to a SSH jump host. .DESCRIPTION This script will connect to the specified SSH jump host by using the credentials and the plink.exe tool. With the shared secret, it will generate a time-based one-time password as two factor authentication. .PARAMETER ComputerName DNS hostname of the jump host. .PARAMETER Credential Username and password of the jump host user. .PARAMETER SharedSecret Shared secret for the TOTP calculation. #> function Connect-JumpHost { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $ComputerName, [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $Credential, [Parameter(Mandatory = $true)] [System.Security.SecureString] $SharedSecret, [Parameter(Mandatory = $false)] [System.Int32] $InputOffset = 2 ) # Hide verbose preference $VerbosePreference = 'SilentlyContinue' # Connection credentials $username = $Credential.UserName $password = $Credential.Password | Unprotect-SecureString # TOTP shared secret $secret = $SharedSecret | Unprotect-SecureString # Get the current cursor position, to calculate the input $cursorTop = [System.Console]::CursorTop + $InputOffset # This script block is invoked asynchronously to enter the TOTP two factor # as soon as the desired line is reached. $scriptBlock = { param ($title, $line, $secret) while ([System.Console]::CursorTop -lt $line) { Start-Sleep -Milliseconds 5 } $wShell = New-Object -ComObject 'WScript.Shell' $wShell.AppActivate($title) $totp = Get-TimeBasedOneTimePassword -SharedSecret $secret $totp.ToString().ToCharArray() | ForEach-Object { $wShell.SendKeys($_) } $wShell.SendKeys('~') } $runspace = [PowerShell]::Create() $runspace.AddScript($scriptBlock).AddArgument($Host.UI.RawUI.WindowTitle).AddArgument($cursorTop).AddArgument($secret) | Out-Null $runspace.BeginInvoke() | Out-Null plink.exe '-ssh' "$username@$ComputerName" '-pw' $password } <# .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, ParameterSetName = 'RemotingWithCredential')] [Parameter(Mandatory = $true, 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 = Use-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' { Write-Verbose ("& {{ {0} }}" -f $command.ScriptBlock) $CommandLookupEventArgs.StopSearch = $true $CommandLookupEventArgs.CommandScriptBlock = $command.ScriptBlock } } } } } } <# .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')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')] param () if ($null -eq (Get-Module -Name posh-git)) { Import-Module -Name posh-git -Force $Global:GitPromptSettings.EnableWindowTitle = '{0} ~ ' -f $Host.UI.RawUI.WindowTitle } 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 = "{0:0.000}s" -f ($history.EndExecutionTime - $history.StartExecutionTime).TotalSeconds # 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 - 1 $Host.UI.RawUI.CursorPosition = $position $Host.UI.WriteLine('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 ($workspace in (Get-ChildItem -Path $Path -Filter '*.code-workspace' -File)) { $projectList.projects.Add(('Workspace {0}' -f $workspace.BaseName), $workspace.FullName) } 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 } } # Get and dot source all external functions (public) Split-Path -Path $PSCommandPath | Get-ChildItem -Filter 'Functions' -Directory | Get-ChildItem -Include '*.ps1' -File -Recurse | ForEach-Object { . $_.FullName } ## Module configuration # Module path New-Variable -Name 'ModulePath' -Value $PSScriptRoot # 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 = @() |