PowerShell $PROFILE、变量和 NoteProperty / Add-Member
PowerShell $PROFILE, Variables and NoteProperty / Add-Member
$profile
让我有些头疼。 $Profile.GetType()
解析为 String
这很好,但它具有 NoteProperty 值:
$profile | Get-Member -Type NoteProperty
AllUsersAllHosts, AllUsersCurrentHost, CurrentUserAllHosts, CurrentUserCurrentHost
当我键入 $profile
时,CurrentUserCurrentHost NoteProperty 是 returned。这很好,但我需要更改此值 - 它很复杂,但我的公司 VPN 使用网络配置文件,当我在线时,它会尝试为我的 $profile
引用该位置,这意味着每个控制台启动需要 9 秒(慢得可怕)。如果配置文件是在本地加载的,我可以将其缩短到 1 秒,但这意味着要更改这些值。为此,我将以下内容放入 AllUsersAllHosts
profile.ps1
$profile = C:\Users\($env:Username)\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"
很好,但是通过这样做,我发现所有的 NoteProperty 值都被删除了!所以我尝试了:
$profile.CurrentUserCurrentHost = C:\Users\($env:Username)\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"
但这失败了,因为 $Profile 的根值继续指向网络配置文件并且控制台启动又是 9 秒!
然后我也注意到了以下奇怪的地方:
$x = [string]$profile
$x -eq $profile
主要问题是:
• 为什么 $x return True
即使 $x 在 $profile
中有 none 个 NoteProperty
值(因为对象肯定是不一样!)?
• 如何在不破坏 NoteProperty
值的情况下控制 $profile
的根值,以及
• 我如何更新 .CurrentUserAllHosts
和 .CurrentUserCurrentHost
以使根值也相应地更新?即,即使我执行以下操作,$profile
的根值仍保持不变(仍然指向非常慢的网络配置文件位置):
Add-Member -InputObject $PROFILE -NotePropertyName "AllUsersAllHosts" -NotePropertyValue "C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1"
Add-Member -InputObject $PROFILE -NotePropertyName "AllUsersCurrentHost" -NotePropertyValue "C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1"
Add-Member -InputObject $PROFILE -NotePropertyName "CurrentUserAllHosts" -NotePropertyValue "C:\Users$($env:Username)\Documents\WindowsPowerShell\profile.ps1"
Add-Member -InputObject $PROFILE -NotePropertyName "CurrentUserCurrentHost" -NotePropertyValue "C:\Users$($env:Username)\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"
虽然技术上可以将 NoteProperty 成员从一个字符串实例复制到另一个字符串实例(见下文),但不幸的是,这对您没有帮助:
PowerShell 为 用户的 利益公开了 $PROFILE
变量:
- 它不会在内部使用该变量的值来确定加载配置文件时的配置文件位置,
- 这些 配置文件位置不可配置。
您应该将 $PROFILE
视为只读变量,即使它在技术上可以修改。
对于解决方法,请参阅this related question的答案。
如果启动交互式会话是重点,最有前途的方法是创建一个专用的快捷方式文件 (*.lnk
) 来启动您的 PowerShell 会话,使用如下目标命令定义:
powershell -noprofile -noexit -c ". c:\CustomProfileDir\profile.ps1"
要使用 PowerShell [Core] 6+,请将 pwsh
替换为 powershell
。
正在对象之间复制 NoteProperty 成员:
注:
如上所述,这 不会 解决您的问题,但它说明了如何将 NoteProperty 成员从一个对象复制到另一个对象,即使在具有不同的值。
特别是 [string]
个实例,需要使用 -PassThru
并重新分配给输入变量 ($obj = $obj | Add-Member -PassThru ...
),以便保留 NoteProperty 成员;对于其他类型,$obj | Add-Member ...
就足够了。
# Assign the new string value to a new (temporary) variable.
$newProfile = '/path/to/my.ps1'
# Copy the NoteProperty members from the original, decorated string
# to the new variable's string instance.
$PROFILE.psobject.properties | where MemberType -eq 'NoteProperty' | foreach {
$newProfile = $newProfile |
Add-Member -PassThru -NotePropertyName $_.Name -NotePropertyValue $_.Value
}
# You can now assign the new variable to the old one,
# but note that *with $PROFILE that won't help*.
# $PROFILE SHOULD BE TREATED AS READ-ONLY.
$PROFILE = $newProfile
正如@mklement0 所指出的,我无法绕过 $profile 的功能,但我可以欺骗它。如果没有以下内容,当我远程连接到我的公司 VPN 时,所有 shell 和脚本都需要 6.5 到 9 秒才能启动(其中一部分,大约 3 秒是我的 $profile
函数等)。在下面,所有控制台和脚本在 1 秒内启动(包括我的 $profile
函数大约 1000 行代码)。将此解决方案放在这里以防对其他人有帮助。
运行 此答案底部的代码导致将以下内容推送到我的 $Profile.AllUsersAllHosts
中,它现在将始终在默认 C:\ 位置引用配置文件(并且根据主机类型正确以及 - 控制台、ISE、VSCode 等)。
Set-Location C:\Users$($env:Username)
# Prevent loading profile from network share
if ($Profile.CurrentUserCurrentHost -ne "C:\Users$($env:Username)\Documents\WindowsPowerShell$(($Profile).split(`"\`")[-1])") {
$Profile = "C:\Users$($env:Username)\Documents\WindowsPowerShell$(($Profile).split(`"\`")[-1])"
if (Test-Path "$Profile") { . $Profile }
}
然后我需要做的就是删除网络共享上的所有配置文件,现在无论是否远程连接到 VPN,以及是否打开控制台或脚本等,我的加载时间都是 1 秒。
Write-Host ""
Write-Host "Do you want to update `$Profile.AllUsersAllHosts to always redirect to C:\Users$($env:Username) ?"
Write-Host "This can significantly speed up PowerShell load times when working with a network share over a VPN."
Write-Host "Note that this is just a bypass to always use the *default* profile location on C:\."
Write-Host " `$Profile.AllUsersAllHosts = C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1"
Write-Host " `$Profile.CurrentHostCurrentUser = C:\Users$($env:Username)\Documents\WindowsPowerShell$(($Profile).split("\"))[-1]_profile.ps1"
Write-Host ""
$ProfileAdmin = "C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1"
# Use (($Profile).split("\"))[-1] to capture correct prefix PowerShell, VSCode. $ShellId does not work for VSCode etc
$ProfileUser = "C:\Users$($env:Username)\Documents\WindowsPowerShell$(($Profile).split('\')[-1])"
$ProfileUserText = '"C:\Users$($env:Username)\Documents\WindowsPowerShell$(($Profile).split(`"\`")[-1])"'
Write-Host "Press any key to use `$Profile.AllUsersAllHosts to stop using a profile from a network share"
pause
# - Create $Profile.AllUsersAllHosts
# - Set-Location C:\Users$($env:Username) (override Admin defaulting into system32)
# - If the system decides that it will use C:\ have to check that so that we do not double-load $Profile!
# - If that condition is ok, set $Profile = "C:\Users$($env:Username)\Documents\WindowsPowerShell$($ShellId)_profile.ps1"
# - Then just dotsource $Profile as it's set to the C:\ drive as required.
if (!(Test-Path $(Split-Path $ProfileAdmin))) { mkdir $(Split-Path $ProfileAdmin) -Force }
if (!(Test-Path $(Split-Path $ProfileUser))) { mkdir $(Split-Path $ProfileUser) -Force }
Write-Host "`nCreating backup of existing profile ..."
If (Test-Path $ProfileAdmin) { Copy-Item -Path "$($ProfileAdmin)" -Destination "$($ProfileAdmin)_$(Get-Date -format "yyyy-MM-dd__HH-mm-ss").txt" }
Set-Content -Path $ProfileAdmin -Value "Set-Location C:\Users\`$(`$env:Username)"
Add-Content -Path $ProfileAdmin -Value ""
Add-Content -Path $ProfileAdmin -Value "# Prevent loading profile from network share"
Add-Content -Path $ProfileAdmin -Value "if (`$Profile.CurrentUserCurrentHost -ne $ProfileUserText) {"
Add-Content -Path $ProfileAdmin -Value " `$Profile = $ProfileUserText"
Add-Content -Path $ProfileAdmin -Value " if (Test-Path `"`$Profile`") { . `$Profile }"
Add-Content -Path $ProfileAdmin -Value "}"
$profile
让我有些头疼。 $Profile.GetType()
解析为 String
这很好,但它具有 NoteProperty 值:
$profile | Get-Member -Type NoteProperty
AllUsersAllHosts, AllUsersCurrentHost, CurrentUserAllHosts, CurrentUserCurrentHost
当我键入 $profile
时,CurrentUserCurrentHost NoteProperty 是 returned。这很好,但我需要更改此值 - 它很复杂,但我的公司 VPN 使用网络配置文件,当我在线时,它会尝试为我的 $profile
引用该位置,这意味着每个控制台启动需要 9 秒(慢得可怕)。如果配置文件是在本地加载的,我可以将其缩短到 1 秒,但这意味着要更改这些值。为此,我将以下内容放入 AllUsersAllHosts
profile.ps1
$profile = C:\Users\($env:Username)\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"
很好,但是通过这样做,我发现所有的 NoteProperty 值都被删除了!所以我尝试了:
$profile.CurrentUserCurrentHost = C:\Users\($env:Username)\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"
但这失败了,因为 $Profile 的根值继续指向网络配置文件并且控制台启动又是 9 秒!
然后我也注意到了以下奇怪的地方:
$x = [string]$profile
$x -eq $profile
主要问题是:
• 为什么 $x return True
即使 $x 在 $profile
中有 none 个 NoteProperty
值(因为对象肯定是不一样!)?
• 如何在不破坏 NoteProperty
值的情况下控制 $profile
的根值,以及
• 我如何更新 .CurrentUserAllHosts
和 .CurrentUserCurrentHost
以使根值也相应地更新?即,即使我执行以下操作,$profile
的根值仍保持不变(仍然指向非常慢的网络配置文件位置):
Add-Member -InputObject $PROFILE -NotePropertyName "AllUsersAllHosts" -NotePropertyValue "C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1"
Add-Member -InputObject $PROFILE -NotePropertyName "AllUsersCurrentHost" -NotePropertyValue "C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1"
Add-Member -InputObject $PROFILE -NotePropertyName "CurrentUserAllHosts" -NotePropertyValue "C:\Users$($env:Username)\Documents\WindowsPowerShell\profile.ps1"
Add-Member -InputObject $PROFILE -NotePropertyName "CurrentUserCurrentHost" -NotePropertyValue "C:\Users$($env:Username)\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"
虽然技术上可以将 NoteProperty 成员从一个字符串实例复制到另一个字符串实例(见下文),但不幸的是,这对您没有帮助:
PowerShell 为 用户的 利益公开了 $PROFILE
变量:
- 它不会在内部使用该变量的值来确定加载配置文件时的配置文件位置,
- 这些 配置文件位置不可配置。
您应该将 $PROFILE
视为只读变量,即使它在技术上可以修改。
对于解决方法,请参阅this related question的答案。
如果启动交互式会话是重点,最有前途的方法是创建一个专用的快捷方式文件 (*.lnk
) 来启动您的 PowerShell 会话,使用如下目标命令定义:
powershell -noprofile -noexit -c ". c:\CustomProfileDir\profile.ps1"
要使用 PowerShell [Core] 6+,请将 pwsh
替换为 powershell
。
正在对象之间复制 NoteProperty 成员:
注:
如上所述,这 不会 解决您的问题,但它说明了如何将 NoteProperty 成员从一个对象复制到另一个对象,即使在具有不同的值。
特别是
[string]
个实例,需要使用-PassThru
并重新分配给输入变量 ($obj = $obj | Add-Member -PassThru ...
),以便保留 NoteProperty 成员;对于其他类型,$obj | Add-Member ...
就足够了。
# Assign the new string value to a new (temporary) variable.
$newProfile = '/path/to/my.ps1'
# Copy the NoteProperty members from the original, decorated string
# to the new variable's string instance.
$PROFILE.psobject.properties | where MemberType -eq 'NoteProperty' | foreach {
$newProfile = $newProfile |
Add-Member -PassThru -NotePropertyName $_.Name -NotePropertyValue $_.Value
}
# You can now assign the new variable to the old one,
# but note that *with $PROFILE that won't help*.
# $PROFILE SHOULD BE TREATED AS READ-ONLY.
$PROFILE = $newProfile
正如@mklement0 所指出的,我无法绕过 $profile 的功能,但我可以欺骗它。如果没有以下内容,当我远程连接到我的公司 VPN 时,所有 shell 和脚本都需要 6.5 到 9 秒才能启动(其中一部分,大约 3 秒是我的 $profile
函数等)。在下面,所有控制台和脚本在 1 秒内启动(包括我的 $profile
函数大约 1000 行代码)。将此解决方案放在这里以防对其他人有帮助。
运行 此答案底部的代码导致将以下内容推送到我的 $Profile.AllUsersAllHosts
中,它现在将始终在默认 C:\ 位置引用配置文件(并且根据主机类型正确以及 - 控制台、ISE、VSCode 等)。
Set-Location C:\Users$($env:Username)
# Prevent loading profile from network share
if ($Profile.CurrentUserCurrentHost -ne "C:\Users$($env:Username)\Documents\WindowsPowerShell$(($Profile).split(`"\`")[-1])") {
$Profile = "C:\Users$($env:Username)\Documents\WindowsPowerShell$(($Profile).split(`"\`")[-1])"
if (Test-Path "$Profile") { . $Profile }
}
然后我需要做的就是删除网络共享上的所有配置文件,现在无论是否远程连接到 VPN,以及是否打开控制台或脚本等,我的加载时间都是 1 秒。
Write-Host ""
Write-Host "Do you want to update `$Profile.AllUsersAllHosts to always redirect to C:\Users$($env:Username) ?"
Write-Host "This can significantly speed up PowerShell load times when working with a network share over a VPN."
Write-Host "Note that this is just a bypass to always use the *default* profile location on C:\."
Write-Host " `$Profile.AllUsersAllHosts = C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1"
Write-Host " `$Profile.CurrentHostCurrentUser = C:\Users$($env:Username)\Documents\WindowsPowerShell$(($Profile).split("\"))[-1]_profile.ps1"
Write-Host ""
$ProfileAdmin = "C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1"
# Use (($Profile).split("\"))[-1] to capture correct prefix PowerShell, VSCode. $ShellId does not work for VSCode etc
$ProfileUser = "C:\Users$($env:Username)\Documents\WindowsPowerShell$(($Profile).split('\')[-1])"
$ProfileUserText = '"C:\Users$($env:Username)\Documents\WindowsPowerShell$(($Profile).split(`"\`")[-1])"'
Write-Host "Press any key to use `$Profile.AllUsersAllHosts to stop using a profile from a network share"
pause
# - Create $Profile.AllUsersAllHosts
# - Set-Location C:\Users$($env:Username) (override Admin defaulting into system32)
# - If the system decides that it will use C:\ have to check that so that we do not double-load $Profile!
# - If that condition is ok, set $Profile = "C:\Users$($env:Username)\Documents\WindowsPowerShell$($ShellId)_profile.ps1"
# - Then just dotsource $Profile as it's set to the C:\ drive as required.
if (!(Test-Path $(Split-Path $ProfileAdmin))) { mkdir $(Split-Path $ProfileAdmin) -Force }
if (!(Test-Path $(Split-Path $ProfileUser))) { mkdir $(Split-Path $ProfileUser) -Force }
Write-Host "`nCreating backup of existing profile ..."
If (Test-Path $ProfileAdmin) { Copy-Item -Path "$($ProfileAdmin)" -Destination "$($ProfileAdmin)_$(Get-Date -format "yyyy-MM-dd__HH-mm-ss").txt" }
Set-Content -Path $ProfileAdmin -Value "Set-Location C:\Users\`$(`$env:Username)"
Add-Content -Path $ProfileAdmin -Value ""
Add-Content -Path $ProfileAdmin -Value "# Prevent loading profile from network share"
Add-Content -Path $ProfileAdmin -Value "if (`$Profile.CurrentUserCurrentHost -ne $ProfileUserText) {"
Add-Content -Path $ProfileAdmin -Value " `$Profile = $ProfileUserText"
Add-Content -Path $ProfileAdmin -Value " if (Test-Path `"`$Profile`") { . `$Profile }"
Add-Content -Path $ProfileAdmin -Value "}"