管道到Where-Object和Foreach.Object在$Profile中延迟加载的模块中不起作用

问题描述 投票:0回答:1

序言:这不是“修复代码”,因为我已经修复了它。 这是关于“了解哪里出了问题,以避免将来发生类似的错误”

情况
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
(我在模块中使用的唯一其他管道示例)按预期工作

我是如何修复它的:从作业中删除导入。该模块仍然自动导入,一切都按预期工作。但正如我所说,这不是修复,而是理解

那么...有什么想法吗?

powershell module profile foreach-object where-object
1个回答
0
投票

此答案试图解释问题发生的原因,但这并不意味着建议实施此操作以实现“更快的配置文件加载”。


首先,重现此问题的更简单方法:

$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
    
© www.soinside.com 2019 - 2024. All rights reserved.