Powershell 什么时候脚本块不是脚本块?

Powershell 什么时候脚本块不是脚本块?,powershell,scope,scriptblock,Powershell,Scope,Scriptblock,我并不是说这个问题听起来太可爱,但这确实是我手头的问题。考虑下面的两个函数:PosiS壳模块Test.PSM1安装在$Env:PSModulePath: function Start-TestAsync { [CmdletBinding()] param([ScriptBlock]$Block, [string]$Name = '') Start-Job { Start-Test -Name $using:Name -Block $using:Block } }

我并不是说这个问题听起来太可爱,但这确实是我手头的问题。考虑下面的两个函数:PosiS壳模块Test.PSM1安装在$Env:PSModulePath:

function Start-TestAsync
{
    [CmdletBinding()]
    param([ScriptBlock]$Block, [string]$Name = '')   
    Start-Job { Start-Test -Name $using:Name -Block $using:Block }
}

function Start-Test
{
    [CmdletBinding()]
    param([ScriptBlock]$Block, [string]$Name = '')
    # do some work here, including this:
    Invoke-Command -ScriptBlock $Block
}
导入模块后,我可以运行同步功能

PS> Start-Test -Name "My Test" -Block { ps | select -first 9 }
…并显示Get进程的适当输出

但是,当我尝试运行异步版本时

PS> $testJob=Start-TestAsync -Name "My Test" -Block { ps | select -first 9 }
…然后查看其输出

PS> Receive-Job $testJob
。。。它在将参数引入Start Test函数时失败,报告无法将字符串转换为ScriptBlock。因此,-Block$using:Block传递的是字符串而不是ScriptBlock

经过一些实验,我确实找到了解决办法。如果我修改Start Test,使$Block参数的类型为[string]而不是[ScriptBlock]——然后将该字符串转换回块以馈送调用命令

function Start-Test
{
    [CmdletBinding()]
    param([string]$Block, [string]$Name = '')
    $myBlock = [ScriptBlock]::Create($Block)
    Invoke-Command -ScriptBlock $myBlock
}
然后,当我从上面运行相同的命令时,我获得了正确的结果:

PS> $testJob=Start-TestAsync -Name "My Test" -Block { ps | select -first 9 }
PS> Receive-Job $testJob
在我最初的示例中将脚本块转换为字符串时,使用范围是否正常工作?关于它的有限文档提供的指导很少。
最终,当启动测试的$Block参数被输入为[ScriptBlock]时,是否有办法使其工作?

我意识到这并不能完全回答您的问题,但我认为您可以将其简化为一个函数:

功能启动测试 { [CmdletBinding] param [ScriptBlock]$Block, [字符串]$Name=, [开关]$Async 在这里做一些工作,包括: 调用命令-ScriptBlock$Block-AsJob:$Async } 因为Invoke命令已经可以为您启动作业,所以您可以让函数接受-Async开关,然后将其值传递给-AsJob开关

调用同步 启动测试块{ps |选择-第一个9} 调用异步 启动测试-块{ps |选择-第一个9}-异步 投机
至于您看到的实际情况发生的原因,我不确定,但我认为可能与嵌套脚本块有关,尽管我目前无法进行适当的测试

我相信您看到这一点的原因是,$using的目的是在远程系统上使用脚本块之前,扩展脚本块中本地变量的值-它实际上不会在远程会话中创建这些变量。scriptblock最接近值的是它的命令文本。

这显然是出于设计:

上述链接的解决方法是使用[ScriptBlock]::创建:

发生这种情况的原因是$ScriptToNest scriptblock正在转换为字符串 因为PowerShell序列化的工作方式。您可以通过显式 创建脚本块。将$outerscript块中的param块替换为 以下$ip是输入:

[scriptblock]$OuterScriptblock = {
param($ip)
[ScriptBlock]$ScriptToRun = [ScriptBlock]::Create($ip)
这将是您的工作,因为您发现:

function Start-TestAsync
{
    [CmdletBinding()]
    param([ScriptBlock]$Block, [string]$Name = '')
    Start-Job { Start-Test -Name $using:Name -Block $using:Block }
}

function Start-Test
{
    [CmdletBinding()]
    param($Block, [string]$Name = '')
    # do some work here, including this:
    $sb = [ScriptBlock]::Create($Block)
    Invoke-Command -ScriptBlock $sb
}

虽然知道谢谢很有用,@KeithHill我看到的是一个已知的问题对不起,我的意思是设计我真正的问题最终没有得到回答,当启动测试的$Block参数被键入[ScriptBlock]时,有没有办法让它工作

昨晚我突然想到了答案:

function Start-TestAsync
{
    [CmdletBinding()]
    param([ScriptBlock]$Block, [string]$Name = '')   
    Start-Job {
        $myBlock = [ScriptBlock]::Create($using:Block);
        Start-Test -Name $using:Name -Block $myBlock  }
}

function Start-Test
{
    [CmdletBinding()]
    param([ScriptBlock]$Block, [string]$Name = '')   
    # do some work here, including this:
    Invoke-Command -ScriptBlock $Block
}
请注意,在Start TestSync中,我在内部允许序列化发生$using:Block,将ScriptBlock转换为字符串,然后立即将其重新转换为ScriptBlock,然后可以安全地将其作为真正的ScriptBlock传递给Start Test。
对我来说,这是对我问题中解决方法的重大改进,因为现在这两个函数上的公共API都是正确的。

Keith,也许我遗漏了什么,但这基本上就是我在问题中报告的,不是吗?我只看到一个表面上的区别,在$Block参数中完全省略了作为字符串的类型。。。不过,我非常感谢您提供的“连接”参考。是的,但对于其他人的未来参考,在其中一个答案中包含这项工作是很好的:-好建议,@briantist!不幸的是,这在我的特殊情况下不起作用,因为我有不止一个Invoke命令需要作为作业运行。