Powershell启动作业范围

Sha*_*har 2 powershell scope start-job scriptblock

我有一个很长的剧本。我有一个记录功能:

function Log ([string]$Content){
    $Date = Get-Date
    Add-Content -Path $LogPath -Value ("$Date : $Content")
}
Run Code Online (Sandbox Code Playgroud)

在脚本的某些时候,我需要并行运行作业。我有一个计算机名称列表,我需要对每个计算机名称使用 psexec。这应该作为并行运行的作业来完成


        Start-Job -ScriptBlock {
        
        Log "$line Has called"
            $Program_List = gc $USERS_DB_PATH\$line.txt | select -Skip 1
            if (Test-Connection $line -Quiet) {
                    ForEach ($program in $Program_List){
                    Log "$line $program"
                    #"line $line bot is $bot pwd is $pwd"
                    psexec \\"$line" -u bla.local\"$bot" -p $pwd cmd bla
                    
                    }
            }
            else{
                  Log "Cannot Connect to $line"
            }
        
        
        }
        #Remove-Item "$USERS_DB_PATH\$line.txt"


}

Run Code Online (Sandbox Code Playgroud)

我知道这与范围有关,但是如何使这个脚本块看到函数 Log 和所有必要的变量?他们都空了

mkl*_*nt0 6

太长了;博士

  • 通过作用域引用调用作用域中的变量$using:

  • Log在后台作业的上下文中重新创建您的函数,使用
    $function:Log = "$using:function:Log"

Start-Job -ScriptBlock {

  # Required in Windows PowerShell only (if needed).
  # Change to the same working directory as the caller.
  Set-Location -LiteralPath ($using:PWD).ProviderPath

  # Recreate the Log function.
  $function:Log = "$using:function:Log"
        
  # All variable values from the *caller*'s scope must be $using: prefixed.
  Log "$using:line Has called"
  # ...        
        
}
Run Code Online (Sandbox Code Playgroud)
  • 请继续阅读以获取解释。

  • 请参阅底部部分,了解:和(仅限 PowerShell (Core) 7+)的更好替代方案Start-JobStart-ThreadJobForEach-Object -Parallel


后台作业在不可见的 PowerShell子进程中运行,即单独的powershell.exe(Windows PowerShell) pwsh(PowerShell (Core) 7+) 进程。

这样的子进程:

  • 不加载$PROFILE文件。
  • 对调用者的状态一无所知;也就是说,它无权访问会话中定义的调用者的变量、函数、别名等;只有环境变量是从调用者继承的

相反,这意味着默认情况下在后台作业中只有以下命令可用

  • 外部程序和*.ps1脚本,通过环境变量中列出的目录$env:PATH
  • 模块中的命令可通过module-autoloading 功能$env:PSModulePath环境变量(具有默认模块)中列出的目录中获取。

将调用者状态信息传递给后台作业:

  • 变量

    • 虽然您无法将变量传递给后台作业,但您可以使用作用域传递它们的$using:;换句话说:您可以获取调用者范围内的变量的值,但不能更新变量 - 请参阅概念 about_Remote_Variables。

    • 或者,通过的( ) 参数将值作为参数传递,然后参数必须以通常的方式访问该参数:通过自动变量或通过使用块显式声明的参数。Start-Job-ArgumentList-Args-ScriptBlock$argsparam()

  • function:

    • 类似地,您不能传递函数本身,而只能传递函数的主体,最简单的方法是通过命名空间变量表示法;例如,要获取函数体foo,请使用$function:foo; 要将其传递给后台作业(或远程调用),请使用"$using:function:foo".

    • 由于命名空间变量表示法也可用于分配值,分配$function:foo创建或更新名为 的函数foo,以便有效地在后台会话中$function:foo = $using:function:foo重新创建函数。foo

      • 请注意,虽然$function:foo将函数体作为[scriptblock]实例返回,$using:function:foo但 , 在序列化期间会变成字符串(请参阅GitHub 问题 #11698 ;不过,幸运的是,您也可以从字符串创建函数体。

      • 因此,对于 ; 来说,括起来$using:function:foo并不是"..."严格必要的Start-Job。然而,它是必需的,因为在Start-ThreadJob基于线程的并行性中没有序列化的情况下,$using:function:foo 它是一个实例,但与调用者的[scriptblock]运行空间相关联,因此必须从作业上下文中的字符串重建(否则,可能会发生状态损坏) )。

      • 即使允许此类脚本块引用也可能是一种Start-ThreadJob疏忽并且 PowerShell v7+功能(与 共享技术基础)明确不允许它们,因此需要通过辅助变量来解决方法,该变量首先在调用者的范围内对脚本块进行字符串化 - 请参阅此答案ForEach-Object -ParallelStart-ThreadJob

  • class英语

    • 虽然没有命名空间变量表示法,但您可以通过帮助程序脚本块解决该问题:请参阅此答案
  • 工作目录

    • Windows PowerShell后台作业中,使用固定的工作目录:usersDocuments文件夹。为了确保后台作业使用与调用者相同的目录,请将 call
      Set-Location -LiteralPath ($using:PWD).ProviderPath作为传递给 的脚本块内部的第一条语句-ScriptBlock

    • 现在在PowerShell (Core) 7+后台作业中 - 幸运的是 - 使用与调用者相同的工作目录。

重新输入保真度的警告:

  • 由于值必须跨流程边界进行编组,因此必须涉及值的序列化和反序列化。后台作业使用与 PowerShell 的远程处理相同的序列化基础设施,除了少数众所周知的类型(包括 .NET 基元类型)之外,这会导致类型保真度的损失,无论是后台作业传递值还是后台作业接收输出- 看这个答案

后台作业的首选替代方案:线程作业,通过Start-ThreadJob

PowerShell (Core) 7+附带该ThreadJob模块,该模块提供Start-ThreadJobcmdlet;在Windows PowerShell中您可以按需安装它

  • 此外,PowerShell (Core) 7+ForEach-Object通过参数提供与 cmdlet 扩展基本相同的功能-Parallel,该参数在每个输入对象的单独线程中执行传递给它的脚本块。

Start-ThreadJob与PowerShell的其他作业管理cmdlet完全集成,但使用线程(即进程内并发)而不是子进程,这意味着:

  • 执行速度更快
  • 使用更少的资源
  • 不会损失类型保真度(尽管您可能会遇到线程安全问题并且可能需要显式同步)

此外,调用者的工作目录也是继承的。

$using:/的需要-ArgumentList同样适用。

  • 正在ForEach-Object -Parallel考虑进行改进,以允许在选择加入的基础上将调用者的状态复制到线程脚本块 - 请参阅GitHub 问题 #12240

这个答案提供了 和 的概述ForEach-Object -Parallel、比较和Start-Job对比Start-ThreadJob