PowerShell在多个提示功能和作用域之间切换
我发现以下行为我不理解。我的$profile中有一些函数专门用来更改我的提示,因此函数prmopt{}的设置会更改我的提示,并且当我启动控制台时,如果我对函数进行了源代码转换的话。PromptCustom,它将完全生效,新的提示将接管。但是,我不希望我的$profile太大,所以我将五个左右不同的提示移到了一个模块中,但是当我尝试对其中任何一个进行dotsource时,什么也没有发生。它们只输出提示的外观,但不作为默认提示 我们的目标是能够根据需要在提示之间切换多个功能,也就是说,没有一个单独的提示适用于每个控制台,我只会在$profile中添加函数提示。当我将遵循以下模板的函数移动到一个模块中时,它们都会中断,因此我想知道这是否是一个范围问题,以及如何实现在一个模块中具有多个提示函数的目标,我可以在这些函数之间切换,而不是被迫将它们保留在我的$profile中?编辑:正如@mklement0所指出的,更新这个问题,因为它实际上是关于所需的目标,也就是说,有我可以切换的提示 下面是我的一个提示函数,如果在我的$profile中定义了该函数,则该函数完全可以作为默认提示使用,但如果将其放入模块中,则不会执行任何操作:PowerShell在多个提示功能和作用域之间切换,powershell,scope,prompt,Powershell,Scope,Prompt,我发现以下行为我不理解。我的$profile中有一些函数专门用来更改我的提示,因此函数prmopt{}的设置会更改我的提示,并且当我启动控制台时,如果我对函数进行了源代码转换的话。PromptCustom,它将完全生效,新的提示将接管。但是,我不希望我的$profile太大,所以我将五个左右不同的提示移到了一个模块中,但是当我尝试对其中任何一个进行dotsource时,什么也没有发生。它们只输出提示的外观,但不作为默认提示 我们的目标是能够根据需要在提示之间切换多个功能,也就是说,没有一个单独的
function PromptShortenPath {
# https://stackoverflow.com/questions/1338453/custom-powershell-prompts
function shorten-path([string] $path) {
$loc = $path.Replace($HOME, '~')
# remove prefix for UNC paths
$loc = $loc -replace '^[^:]+::', ''
# make path shorter like tabs in Vim,
# handle paths starting with \\ and . correctly
return ($loc -replace '\\(\.?)([^\\])[^\\]*(?=\\)','\$1$2')
}
function prompt {
# our theme
$cdelim = [ConsoleColor]::DarkCyan
$chost = [ConsoleColor]::Green
$cloc = [ConsoleColor]::Cyan
write-host "$([char]0x0A7) " -n -f $cloc
write-host ([net.dns]::GetHostName()) -n -f $chost
write-host ' {' -n -f $cdelim
write-host (shorten-path (pwd).Path) -n -f $cloc
write-host '}' -n -f $cdelim
return ' '
}
if ($MyInvocation.InvocationName -eq "PromptShortenPath") {
"`nWarning: Must dotsource '$($MyInvocation.MyCommand)' or it will not be applied to this session.`n`n . $($MyInvocation.MyCommand)`n"
} else {
. prompt
}
}
如果删除外部函数并在模块路径中以相同名称另存为文件夹中的modulename.psm1:
Function shorten-path([string] $path) {
$loc = $path.Replace($HOME, '~')
# remove prefix for UNC paths
$loc = $loc -replace '^[^:]+::', ''
# make path shorter like tabs in Vim,
# handle paths starting with \\ and . correctly
return ($loc -replace '\\(\.?)([^\\])[^\\]*(?=\\)','\$1$2')
}
Function prompt {
# our theme
$cdelim = [ConsoleColor]::DarkCyan
$chost = [ConsoleColor]::Green
$cloc = [ConsoleColor]::Cyan
write-host "$([char]0x0A7) " -n -f $cloc
write-host ([net.dns]::GetHostName()) -n -f $chost
write-host ' {' -n -f $cdelim
write-host (shorten-path (pwd).Path) -n -f $cloc
write-host '}' -n -f $cdelim
return ' '
}
现在只需:
Import-Module modulename
请注意,新的提示现在在导入功能时生效为在导入时激活提示功能提供了有效的解决方案
如下文所述,如果函数是从模块导入的,则您问题中所述的按需激活函数的方法,即稍后点源化一个嵌套提示函数的函数,基本上无法像编写的那样工作;有关解决方案,请参见底部部分
至于你尝试了什么:
。提示
这不是函数提示符的定义,而是在寻源范围内运行函数
实际上,它会毫无意义地打印一次,作为输出,提示字符串应该是什么,并使函数局部变量停留在调用方的范围内。
因此,通过将提示函数定义嵌套在PromptShortenPath函数中,可以自动在调用者的作用域中定义提示函数的点源,以及shortenPath函数[1]
如果您的PromptShortenPath函数是在模块外部定义的,则点寻源意味着寻源范围是非模块调用方的当前范围,该范围定义了其中的嵌套函数,并且随着新提示函数的出现,交互式提示字符串将按预期进行更改
相反,如果PromptShortenPath函数是在模块内部定义的,则点源表示源作用域是源模块,这意味着调用方的当前作用域不受影响,并且从未看到嵌套的shorten path和prompt函数-因此,交互式提示字符串不会更改
这需要重复:点源函数与脚本相反,它在源代码的作用域的当前作用域中运行函数,而不是在调用方的当前作用域中运行函数;也就是说,点源函数总是在模块的当前作用域中运行,这与调用方的作用域不同,也与调用方的作用域无关,除非调用方恰好是同一模块中的顶级作用域。
相反,怀疑论者的解决方案是,通过使shorten path和prompt函数成为模块的顶级函数,使用Import module隐式地将它们导出并导入调用者的作用域,并且,调用者作用域中新的prompt函数的出现会改变交互提示字符串,尽管是在进口时
也适用于模块的替代方法:
最简单的解决方案是使用作用域说明符global:定义嵌套函数,该说明符直接在全局作用域中定义它,而不管定义包含在哪个作用域中
作为一个有益的副作用,您不再需要在调用时点源提示激活函数
请注意,下面的解决方案将helper函数shorten path嵌入到global:prompt函数中,以确保后者可用;另一种方法是将shortenpath定义为global:shortenpath,但是没有必要用helper函数来混乱全局范围,特别是在可能发生名称冲突的情况下
# Use a dynamic module to simulate importing the `Set-Prompt` function
# from a (regular, persisted) module.
$null = New-Module {
function Set-Prompt {
# Note the `global:` prefix.
Function global:prompt {
# Note the *embedded* definition of helper function shorten-path,
# which makes it available to the enclosing function only and avoids
# the need to make the helper function global too.
Function shorten-path([string] $path) {
$loc = $path.Replace($HOME, '~')
# remove prefix for UNC paths
$loc = $loc -replace '^[^:]+::', ''
# make path shorter like tabs in Vim,
# handle paths starting with \\ and . correctly
return ($loc -replace '\\(\.?)([^\\])[^\\]*(?=\\)', '\$1$2')
}
# our theme
$cdelim = [ConsoleColor]::DarkCyan
$chost = [ConsoleColor]::Green
$cloc = [ConsoleColor]::Cyan
Write-Host "$([char]0x0A7) " -n -f $cloc
Write-Host ([net.dns]::GetHostName()) -n -f $chost
Write-Host ' {' -n -f $cdelim
Write-Host (shorten-path (pwd).Path) -n -f $cloc
Write-Host '}' -n -f $cdelim
return ' '
}
}
}
# Now that Set-Prompt is imported, invoke it as you would
# any function, and the embedded `prompt` function will take effect.
Set-Prompt
[1] 请注意,虽然shorten path原则上遵循PowerShell的名词-动词命名约定,但shorten不在列表中。我最终得出以下解决方案。感谢您帮助这位@mklement/@politicalist。最后,我真的 只需要全局:调用。我不想要一个动态函数,虽然我很感兴趣,但它可能会很有用,而且我不想在导入模块时激活提示,事实上,这正是我想要避免的 所有这些都可以通过进入任何个人模块来实现。导入模块将不会激活提示这是我想要的结果。然后,只需调用设置该提示符或其别名的函数,即可根据需要激活每个提示符 编辑:请随意添加任何更多的提示功能,做有趣的事情。我总是非常有兴趣看到更多有用的技巧和变化的提示配置!:
function PromptDefault {
# get-help about_Prompt
# https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_prompts?view=powershell-7
function global:prompt {
"PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) ";
# .Link
# https://go.microsoft.com/fwlink/?LinkID=225750
# .ExternalHelp System.Management.Automation.dll-help.xml
$Elevated = ""
$user = [Security.Principal.WindowsIdentity]::GetCurrent();
if ((New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) {$Elevated = "Administrator: "}
# $TitleVer = "PS v$($PSVersionTable.PSversion.major).$($PSVersionTable.PSversion.minor)"
$TitleVer = "PowerShell"
$Host.UI.RawUI.WindowTitle = "$($Elevated)$($TitleVer)"
}
}
# More simple alternative prompt, need to dotsource this
function PromptTimeUptime {
function global:prompt {
# Adds date/time to prompt and uptime to title bar
$Elevated = "" ; if (Test-Admin) {$Elevated = "Administrator: "}
$up = Uptime
$Host.UI.RawUI.WindowTitle = $Elevated + "PowerShell [Uptime: $up]" # Title bar info
$path = Get-Location
Write-Host '[' -NoNewline
Write-Host (Get-Date -UFormat '%T') -ForegroundColor Green -NoNewline # $TitleDate = Get-Date -format "dd/MM/yyyy HH:mm:ss"
Write-Host '] ' -NoNewline
Write-Host "$path" -NoNewline
return "> " # Must have a line like this at end of prompt or you always get " PS>" on the prompt
}
}
function PromptTruncatedPaths {
# https://www.johndcook.com/blog/2008/05/12/customizing-the-powershell-command-prompt/
function global:prompt {
$cwd = (get-location).Path
[array]$cwdt=$()
$cwdi = -1
do {$cwdi = $cwd.indexofany("\", $cwdi+1) ; [array]$cwdt+=$cwdi} until($cwdi -eq -1)
if ($cwdt.count -gt 3) { $cwd = $cwd.substring(0,$cwdt[0]) + ".." + $cwd.substring($cwdt[$cwdt.count-3]) }
$host.UI.RawUI.WindowTitle = "$(hostname) – $env:USERDNSDOMAIN$($env:username)"
$host.UI.Write("Yellow", $host.UI.RawUI.BackGroundColor, "[PS]")
" $cwd> "
}
}
function PromptShortenPath {
# https://stackoverflow.com/questions/1338453/custom-powershell-prompts
function global:shorten-path([string] $path) {
$loc = $path.Replace($HOME, '~')
# remove prefix for UNC paths
$loc = $loc -replace '^[^:]+::', ''
# make path shorter like tabs in Vim,
# handle paths starting with \\ and . correctly
return ($loc -replace '\\(\.?)([^\\])[^\\]*(?=\\)','\$1$2')
}
function global:prompt {
# our theme
$cdelim = [ConsoleColor]::DarkCyan
$chost = [ConsoleColor]::Green
$cloc = [ConsoleColor]::Cyan
write-host "$([char]0x0A7) " -n -f $cloc
write-host ([net.dns]::GetHostName()) -n -f $chost
write-host ' {' -n -f $cdelim
write-host (shorten-path (pwd).Path) -n -f $cloc
write-host '}' -n -f $cdelim
return ' '
}
}
function PromptUserAndExecutionTimer {
function global:prompt {
### Title bar info
$user = [Security.Principal.WindowsIdentity]::GetCurrent();
$Elevated = ""
if ((New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) {$Elevated = "Admin: "}
$TitleVer = "PS v$($PSVersionTable.PSversion.major).$($PSVersionTable.PSversion.minor)"
# $($executionContext.SessionState.Path.CurrentLocation.path)
### Custom Uptime without seconds (not really necessary)
# $wmi = gwmi -class Win32_OperatingSystem -computer "."
# $LBTime = $wmi.ConvertToDateTime($wmi.Lastbootuptime)
# [TimeSpan]$uptime = New-TimeSpan $LBTime $(get-date)
# $s = "" ; if ($uptime.Days -ne 1) {$s = "s"}
# $TitleUp = "[Up: $($uptime.days) day$s $($uptime.hours) hr $($uptime.minutes) min]"
$Host.UI.RawUI.WindowTitle = "$($Elevated) $($TitleVer)" # $($TitleUp)"
### History ID
$HistoryId = $MyInvocation.HistoryId
# Uncomment below for leading zeros
# $HistoryId = '{0:d4}' -f $MyInvocation.HistoryId
Write-Host -Object "$HistoryId " -NoNewline -ForegroundColor Cyan
### Time calculation
$Success = $?
$LastExecutionTimeSpan = if (@(Get-History).Count -gt 0) {
Get-History | Select-Object -Last 1 | ForEach-Object {
New-TimeSpan -Start $_.StartExecutionTime -End $_.EndExecutionTime
}
}
else {
New-TimeSpan
}
$LastExecutionShortTime = if ($LastExecutionTimeSpan.Days -gt 0) {
"$($LastExecutionTimeSpan.Days + [Math]::Round($LastExecutionTimeSpan.Hours / 24, 2)) d"
}
elseif ($LastExecutionTimeSpan.Hours -gt 0) {
"$($LastExecutionTimeSpan.Hours + [Math]::Round($LastExecutionTimeSpan.Minutes / 60, 2)) h"
}
elseif ($LastExecutionTimeSpan.Minutes -gt 0) {
"$($LastExecutionTimeSpan.Minutes + [Math]::Round($LastExecutionTimeSpan.Seconds / 60, 2)) m"
}
elseif ($LastExecutionTimeSpan.Seconds -gt 0) {
"$($LastExecutionTimeSpan.Seconds + [Math]::Round($LastExecutionTimeSpan.Milliseconds / 1000, 1)) s"
}
elseif ($LastExecutionTimeSpan.Milliseconds -gt 0) {
"$([Math]::Round($LastExecutionTimeSpan.TotalMilliseconds, 0)) ms"
# ms are 1/1000 of a sec so no point in extra decimal places here
}
else {
"0 s"
}
if ($Success) {
Write-Host -Object "[$LastExecutionShortTime] " -NoNewline -ForegroundColor Green
}
else {
Write-Host -Object "! [$LastExecutionShortTime] " -NoNewline -ForegroundColor Red
}
### User, removed
$IsAdmin = (New-Object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
# Write-Host -Object "$($env:USERNAME)$(if ($IsAdmin){ '[A]' } else { '[U]' }) " -NoNewline -ForegroundColor DarkGreen
# Write-Host -Object "$($env:USERNAME)" -NoNewline -ForegroundColor DarkGreen
# Write-Host -Object " [" -NoNewline
# if ($IsAdmin) { Write-Host -Object 'A' -NoNewline -F Red } else { Write-Host -Object 'U' -NoNewline }
# Write-Host -Object "] " -NoNewline
Write-Host "$($env:USERNAME)" -NoNewline -ForegroundColor DarkGreen
Write-Host "[" -NoNewline
if ($IsAdmin) { Write-Host 'A' -NoNewline -F Red } else { Write-Host -Object 'U' -NoNewline }
Write-Host "] " -NoNewline
# ### Path
# $Drive = $pwd.Drive.Name
# $Pwds = $pwd -split "\\" | Where-Object { -Not [String]::IsNullOrEmpty($_) }
# $PwdPath = if ($Pwds.Count -gt 3) {
# $ParentFolder = Split-Path -Path (Split-Path -Path $pwd -Parent) -Leaf
# $CurrentFolder = Split-Path -Path $pwd -Leaf
# "..\$ParentFolder\$CurrentFolder"
# go # }
# elseif ($Pwds.Count -eq 3) {
# $ParentFolder = Split-Path -Path (Split-Path -Path $pwd -Parent) -Leaf
# $CurrentFolder = Split-Path -Path $pwd -Leaf
# "$ParentFolder\$CurrentFolder"
# }
# elseif ($Pwds.Count -eq 2) {
# Split-Path -Path $pwd -Leaf
# }
# else { "" }
# Write-Host -Object "$Drive`:\$PwdPath" -NoNewline
Write-Host $pwd -NoNewline
return "> "
}
}
function PromptSlightlyBroken {
# https://community.spiceworks.com/topic/1965997-custom-cmd-powershell-prompt
# if ($MyInvocation.InvocationName -eq "PromptOverTheTop") {
# "`nWarning: Must dotsource '$($MyInvocation.MyCommand)' or it will not be applied to this session.`n`n . $($MyInvocation.MyCommand)`n"
# } else {
if ($host.name -eq 'ConsoleHost') {
# fff
$Shell = $Host.UI.RawUI
$Shell.BackgroundColor = "Black"
$Shell.ForegroundColor = "White"
$Shell.CursorSize = 10
}
# $Shell=$Host.UI.RawUI
# $size=$Shell.BufferSize
# $size.width=120
# $size.height=3000
# $Shell.BufferSize=$size
# $size=$Shell.WindowSize
# $size.width=120
# $size.height=30
# $Shell.WindowSize=$size
# $Shell.BackgroundColor="Black"
# $Shell.ForegroundColor="White"
# $Shell.CursorSize=10
# $Shell.WindowTitle="Console PowerShell"
function global:Get-Uptime {
$os = Get-WmiObject win32_operatingsystem
$uptime = (Get-Date) - ($os.ConvertToDateTime($os.lastbootuptime))
$days = $Uptime.Days ; if ($days -eq "1") { $days = "$days day" } else { $days = "$days days"}
$hours = $Uptime.Hours ; if ($hours -eq "1") { $hours = "$hours hr" } else { $hours = "$hours hrs"}
$minutes = $Uptime.Minutes ; if ($minutes -eq "1") { $minutes = "$minutes min" } else { $minutes = "$minutes mins"}
$Display = "$days, $hours, $minutes"
Write-Output $Display
}
function Spaces ($numspaces) { for ($i = 0; $i -lt $numspaces; $i++) { Write-Host " " -NoNewline } }
# $MaximumHistoryCount=1024
$IPAddress = @(Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object {$_.DefaultIpGateway})[0].IPAddress[0]
$IPGateway = @(Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object {$_.DefaultIpGateway})[0].DefaultIPGateway[0]
$UserDetails = "$env:UserDomain\$env:UserName (PS-HOME: $HOME)"
$PSExecPolicy = Get-ExecutionPolicy
$PSVersion = "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor) ($PSExecPolicy)"
$ComputerAndLogon = "$($env:COMPUTERNAME)"
$ComputerAndLogonSpaces = 28 - $ComputerAndLogon.Length
Clear
Write-Host "-----------------------------------------------------------------------------------------------------------------------" -ForegroundColor Green
Write-Host "| ComputerName: " -nonewline -ForegroundColor Green; Write-Host $ComputerAndLogon -nonewline -ForegroundColor White ; Spaces $ComputerAndLogonSpaces ; Write-Host "UserName:" -nonewline -ForegroundColor Green ; Write-Host " $UserDetails" -ForegroundColor White
Write-Host "| Logon Server: " -nonewline -ForegroundColor Green; Write-Host $($env:LOGONSERVER)"`t`t`t`t" -nonewline -ForegroundColor White ; Write-Host "IP Address:`t" -nonewline -ForegroundColor Green ; Write-Host "`t$IPAddress ($IPGateway)" -ForegroundColor White
Write-Host "| Uptime: " -nonewline -ForegroundColor Green; Write-Host "$(Get-Uptime)`t" -nonewline -ForegroundColor White; Write-Host "PS Version:`t" -nonewline -ForegroundColor Green ; Write-Host "`t$PSVersion" -ForegroundColor White
Write-Host "-----------------------------------------------------------------------------------------------------------------------" -ForegroundColor Green
# Write-Host "-----------------------------------------------------------------------------------------------------------------------" -ForegroundColor Green
# Write-Host "|`tComputerName:`t" -nonewline -ForegroundColor Green; Write-Host $($env:COMPUTERNAME)"`t`t`t`t" -nonewline -ForegroundColor White ; Write-Host "UserName:`t$UserDetails" -ForegroundColor White
# Write-Host "|`tLogon Server:`t" -nonewline -ForegroundColor Green; Write-Host $($env:LOGONSERVER)"`t`t`t`t" -nonewline -ForegroundColor White ; Write-Host "IP Address:`t$IPAddress ($IPGateway)" -ForegroundColor White
# Write-Host "|`tUptime:`t`t" -nonewline -ForegroundColor Green; Write-Host "$(Get-Uptime)`t" -nonewline -ForegroundColor White; Write-Host "PS Version:`t$PSVersion" -ForegroundColor White
# Write-Host "-----------------------------------------------------------------------------------------------------------------------" -ForegroundColor Green
function global:admin {
$Elevated = ""
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal( [Security.Principal.WindowsIdentity]::GetCurrent() )
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -eq $true) { $Elevated = "Administrator: " }
$Host.UI.RawUI.WindowTitle = "$Elevated$TitleVer"
}
admin
Set-Location C:\
function global:prompt{
$br = "`n"
Write-Host "[" -noNewLine
Write-Host $(Get-date) -ForegroundColor Green -noNewLine
Write-Host "] " -noNewLine
Write-Host "[" -noNewLine
Write-Host "$env:username" -Foregroundcolor Red -noNewLine
Write-Host "] " -noNewLine
Write-Host "[" -noNewLine
Write-Host $($(Get-Location).Path.replace($home,"~")) -ForegroundColor Yellow -noNewLine
Write-Host $(if ($nestedpromptlevel -ge 1) { '>>' }) -noNewLine
Write-Host "] "
return "> "
}
}
Set-Alias p0 PromptDefault
Set-Alias p-default PromptDefault
Set-Alias p-timer PromptUserAndExecutionTimer # Using this as my console default
Set-Alias p-short PromptShortenPath
Set-Alias p-trunc PromptTruncatedPaths
Set-Alias p-uptime PromptTimeUptime
Set-Alias p-broken PromptSlightlyBroken
# View current prompt with: (get-item function:prompt).scriptblock or cat function:\prompt