序言:这不是“修复代码”,因为我已经修复了它。 这是关于“了解哪里出了问题,以避免将来发生类似的错误”
情况:
Powershell 7.4.1.
我在我的 $Profile
中使用
THIS一段代码(我从某个我不记得的网站上获得的)来延迟加载模块和脚本。实时代码有更多代码,但不相关:我已经测试过,这就是问题所在。
具体来说,我用它来加载我编写的供个人使用的模块。 (我知道我实际上不需要在我的
$Profile
脚本中加载模块,只要它们在我的 $env:PSModulePath
中即可;我确信我这样做是有原因的,但老实说不记得是什么了。)
原始模块的真实内容并不重要,因为最小可重现示例是:
抄写于
$profile
$OutputEncoding = [console]::InputEncoding = [console]::OutputEncoding = [Text.Encoding]::UTF8
# https://seeminglyscience.github.io/powershell/2017/09/30/invocation-operators-states-and-scopes
$GlobalState = [psmoduleinfo]::new($false)
$GlobalState.SessionState = $ExecutionContext.SessionState
$Job = Start-ThreadJob -Name TestJob -ArgumentList $GlobalState -ScriptBlock {
$GlobalState = $args[0]
. $GlobalState {
# We always need to wait so that Get-Command itself is available
do {
Start-Sleep -Milliseconds 200
} until (Get-Command Import-Module -ErrorAction Ignore)
# other dot-sourced scripts...
# . "$ProfileDirectory\CustomAlias.ps1"
# . "$ProfileDirectory\CustomConstants.ps1"
# . "$ProfileDirectory\CustomVariables.ps1"
# . "$ProfileDirectory\CustomPrompt.ps1"
# Import-Module -Name Sirtao
Import-Module -Name Get-DirectoryItem
}
}
$null = Register-ObjectEvent -InputObject $Job -EventName StateChanged -SourceIdentifier Job.Monitor -Action {
# JobState: NotStarted = 0, Running = 1, Completed = 2, etc.
if ($Event.SourceEventArgs.JobStateInfo.State -eq 'Completed') {
$Result = $Event.Sender | Receive-Job
if ($Result) {
$Result | Out-String | Write-Host
}
$Event.Sender | Remove-Job
Unregister-Event Job.Monitor
Get-Job Job.Monitor | Remove-Job
}
elseif ($Event.SourceEventArgs.JobStateInfo.State -gt 2) {
$Event.Sender | Receive-Job | Out-String | Write-Host
}
}
模块已加载
function Get-DirectoryItem {
[CmdletBinding(DefaultParameterSetName = 'BaseSet')]
[Alias('Get-Dir', 'GD')]
param (
)
process {
1..3 | Where-Object { $_ }
1..3 | ForEach-Object { $_ }
$a = @('a', 'b', 'c')
$a | ForEach-Object { $_ }
$a | Where-Object { $_ }
}
}
我所做的尝试:只需运行命令。
我所期待的:返回数组值的脚本
我得到了什么:错误
ForEach-Object: Object reference not set to an instance of an object.
和
Where-Object: Object reference not set to an instance of an object.
请注意,
Get-Item
、Get-ChildItem
和Get-FileHash
(我在模块中使用的唯一其他管道示例)按预期工作
我是如何修复它的:从作业中删除导入。该模块仍然自动导入,一切都按预期工作。但正如我所说,这不是修复,而是理解。
那么...有什么想法吗?
此答案试图解释问题发生的原因,但这并不意味着建议实施此操作以实现“更快的配置文件加载”。
$module = [psmoduleinfo]::new($false)
$module.SessionState = $ExecutionContext.SessionState
Start-ThreadJob {
. $using:module {
while (-not (Get-Command Import-Module -ErrorAction Ignore)) {
Start-Sleep -Milliseconds 200
}
$newItemSplat = @{
Value = 'function Test-Func { 0..10 | Where-Object { $_ } }'
Path = [guid]::NewGuid().ToString() + '.psm1'
}
$tmp = New-Item @newItemSplat
Import-Module $tmp.FullName
$tmp | Remove-Item
}
} | Receive-Job -Wait -AutoRemoveJob
Test-Func
正如 mclayton 的有用评论所指出的,添加 .Ast.GetScriptblock()
Value = 'function Test-Func { 0..10 | Where-Object { $_ }.Ast.GetScriptblock() }'
为什么它可以解决问题?.GetScriptblock()
创建了一个新实例,剥离了其运行空间关联性,Paul Higin(ThreadJob 模块的创建者)在GitHub 问题 #4003 中对此进行了讨论。另请参阅 PR #18138 - 在声明
NoRunspaceAffinity
属性时使 PowerShell 类不附属于 Runspace。 为什么会失败?
. $using:module { ... Import-Module ... }
时,我们在
$module
中创建一个嵌套模块,并且当尝试调用
Test-Func
时,
Where-Object
脚本块正在尝试封送回不再活动的原始运行空间因为ThreadJob已经结束,因此出现空引用异常。如何证明事实如此?
$module = [psmoduleinfo]::new($false)
$module.SessionState = $ExecutionContext.SessionState
$rs = [runspacefactory]::CreateRunspace()
$rs.Open()
$ps = [powershell]::Create($rs).AddScript({
. $args[0] {
while (-not (Get-Command Import-Module -ErrorAction Ignore)) {
Start-Sleep -Milliseconds 200
}
$newItemSplat = @{
Value = 'function Test-Func { 0..10 | Where-Object { $_ } }'
Path = [guid]::NewGuid().ToString() + '.psm1'
}
$tmp = New-Item @newItemSplat
Import-Module $tmp.FullName
$tmp | Remove-Item
}
}).AddArgument($module)
$task = $ps.BeginInvoke()
while (-not $task.AsyncWaitHandle.WaitOne(200)) { }
Test-Func