cmdlet ForEach-Object 对输入对象集合中的每个项执行操作。 输入对象可以通过管道传递给 cmdlet,也可以使用 InputObject 参数指定。

从 Windows PowerShell 3.0 开始,可通过两种不同的方法来构造 ForEach-Object 命令。

  • 脚本块 。 你可以使用某个脚本块来指定操作。 在脚本块中,使用 $_ 变量表示当前对象。 脚本块是 Process 参数的值。 脚本块可以包含任何 PowerShell 脚本。

    例如,以下命令将获取计算机上每个进程的 ProcessName 属性值。

    Get-Process | ForEach-Object {$_.ProcessName}

    ForEach-Object 支持 、 process end 块, begin about_functions 中所述。

    脚本块在调用方的范围内运行。 因此,这些块有权访问该范围内的变量,并且可以在 cmdlet 完成后创建在该范围内保留的新变量。

  • 操作语句 。 还可以编写操作语句,这更像是自然语言。 你可以使用该操作语句来指定属性值或调用方法。 Windows PowerShell 3.0 中引入了操作语句。

    例如,以下命令还将获取计算机上每个进程的 ProcessName 属性值。

    Get-Process | ForEach-Object ProcessName

  • 并行运行的脚本块 。 从 PowerShell 7.0 开始,可以使用第三个参数集并行运行每个脚本块。 ThrottleLimit 参数限制一次运行的并行脚本数。 如前所述,使用 $_ 变量表示脚本块中的当前输入对象。 $using: 使用 关键字将变量引用传递给正在运行的脚本。

    在 PowerShell 7 中,为每个循环迭代创建新的运行空间,以确保最大隔离。 如果正在执行的工作与创建新运行空间相比较小,或者执行大量工作,则可能会对性能和资源造成较大影响。 从 PowerShell 7.1 起,默认情况下会重复使用 runspace 池中的 runspace。 Runspace 池大小由 ThrottleLimit 参数指定。 默认运行空间池大小为 5。 你仍然可以使用 UseNewRunspace 开关为每个迭代创建新的 runspace。

    默认情况下,并行脚本块使用启动并行任务的调用方当前工作目录。

    有关详细信息,请参阅本文的 NOTES 部分。

    Get-ChildItem $PSHOME |
      ForEach-Object -Process {if (!$_.PSIsContainer) {$_.Name; $_.Length / 1024; " " }}

    如果对象不是目录,则脚本块获取文件的名称,将其 Length 属性的值除以 1024,并添加一个空格 (“) ,以将其与下一个条目分开。 cmdlet 使用 PSISContainer 属性来确定对象是否为目录。

    示例 3:对最新的系统事件进行操作

    此示例将系统事件日志中的 1000 个最新事件写入文本文件。 当前时间显示在处理事件之前和之后。

    $Events = Get-EventLog -LogName System -Newest 1000
    $events | ForEach-Object -Begin {Get-Date} -Process {Out-File -FilePath Events.txt -Append -InputObject $_.Message} -End {Get-Date}

    Get-EventLog 从系统事件日志中获取 1000 个最新事件,并将其存储在 变量中 $Events $Events 然后通过管道连接到 ForEach-Object cmdlet。 Begin 参数将显示当前日期和时间。 接下来, Process 参数使用 Out-File cmdlet 创建名为 events.txt 的文本文件,并将该文件中每个事件的消息属性存储在该文件中。 最后, End 参数用于显示完成所有处理后的日期和时间。

    示例 4:更改注册表项的值

    本示例将 项下 HKCU:\Network 的所有子项中的 RemotePath 注册表项的值更改为大写文本。

    Get-ItemProperty -Path HKCU:\Network\* |
      ForEach-Object {Set-ItemProperty -Path $_.PSPath -Name RemotePath -Value $_.RemotePath.ToUpper();}

    可以使用此格式更改注册表条目值的形式或内容。

    网络 密钥中的每个子项都表示一个映射的网络驱动器,该驱动器在登录时重新连接。 RemotePath 项包含连接的驱动器的 UNC 路径。 例如,如果将 E: 驱动器映射到 \\Server\Share ,则会在 中创建 HKCU:\Network E 子项,并将 RemotePath 注册表值设置为 \\Server\Share

    命令使用 Get-ItemProperty cmdlet 获取 网络 键的所有子项,并使用 Set-ItemProperty cmdlet 更改每个项中 RemotePath 注册表项的值。 Set-ItemProperty 在 命令中, 路径是注册表项的 PSPath 属性的值。 这是 Microsoft .NET Framework 对象的属性,它表示注册表项,而不是注册表项。 命令使用 RemotePath 值的 ToUpper () 方法,这是一个字符串 (REG_SZ) 。

    由于 Set-ItemProperty 更改了每个键的属性, ForEach-Object 因此需要 cmdlet 才能访问属性。

    示例 5:使用$null自动变量

    此示例演示将自动变量管道 $null 连接到 ForEach-Object cmdlet 的效果。

    1, 2, $null, 4 | ForEach-Object {"Hello"}
    Hello
    Hello
    Hello
    Hello

    由于 PowerShell 被视为 $null 显式占位符,因此 ForEach-Object cmdlet 会生成 一 $null 个 值,就像对管道传递给它的其他对象一样。

    示例 6:获取属性值

    此示例使用 cmdlet 的 MemberName 参数获取所有已安装 PowerShell 模块的 ForEach-Object Path 属性的值。

    Get-Module -ListAvailable | ForEach-Object -MemberName Path
    Get-Module -ListAvailable | Foreach Path

    第二个命令等效于第一个命令。 它使用 Foreach cmdlet 的 ForEach-Object 别名,并省略 MemberName 参数的名称,这是可选的。

    cmdlet ForEach-Object 可用于获取属性值,因为它获取值而不更改类型,这与 格式 cmdlet 或 Select-Object cmdlet 不同,后者会更改属性值类型。

    示例 7:将模块名称拆分为组件名称

    此示例演示了将两个以点分隔的模块名称拆分为组件名称的三种方法。 这些命令调用字符串的 Split 方法。 这三个命令使用不同的语法,但它们是等效且可互换的。 这三种情况的输出相同。

    "Microsoft.PowerShell.Core", "Microsoft.PowerShell.Host" | ForEach-Object {$_.Split(".")}
    "Microsoft.PowerShell.Core", "Microsoft.PowerShell.Host" | ForEach-Object -MemberName Split -ArgumentList "."
    "Microsoft.PowerShell.Core", "Microsoft.PowerShell.Host" | Foreach Split "."
    Microsoft
    PowerShell
    Microsoft
    PowerShell
    	

    第一个命令使用传统语法,其中包括脚本块和当前对象运算符 $_。 它使用句点语法来指定该方法,并使用括号将分隔符参数括起来。

    第二个命令使用 MemberName 参数指定 Split 方法和 ArgumentList 参数将点 (.) 标识为拆分分隔符。

    第三个命令使用 cmdlet 的 ForEach-ObjectForeach 别名,并省略 MemberNameArgumentList 参数的名称,这些参数是可选的。

    示例 8:将 ForEach-Object 与两个脚本块配合使用

    在此示例中,我们将位置传递两个脚本块。 所有脚本块都绑定到 Process 参数。 但是,它们被视为已传递给 BeginProcess 参数。

    1..2 | ForEach-Object { 'begin' } { 'process' }
    begin
    process
    process

    示例 9:将ForEach-Object与两个以上的脚本块配合使用

    在此示例中,我们以位置传递四个脚本块。 所有脚本块都绑定到 Process 参数。 但是,它们被视为已传递到 BeginProcessEnd 参数。

    1..2 | ForEach-Object { 'begin' } { 'process A' }  { 'process B' }  { 'end' }
    begin
    process A
    process B
    process A
    process B
    

    第一个脚本块始终映射到块 begin ,最后一个块映射到块 end ,两者之间的块都映射到块 process

    示例 10:为每个管道项运行多个脚本块

    如上一示例所示,使用 Process 参数传递的多个脚本块映射到 BeginEnd 参数。 若要避免此映射,必须为 BeginEnd 参数提供显式值。

    1..2 | ForEach-Object -Begin $null -Process { 'one' }, { 'two' }, { 'three' } -End $null
    three
    three

    示例 11:并行批处理运行慢脚本

    此示例运行一个脚本块,该脚本块计算字符串并休眠一秒钟。

    $Message = "Output:"
    1..8 | ForEach-Object -Parallel {
        "$using:Message $_"
        Start-Sleep 1
    } -ThrottleLimit 4
    Output: 1
    Output: 2
    Output: 3
    Output: 4
    Output: 5
    Output: 6
    Output: 7
    Output: 8

    ThrottleLimit 参数值设置为 4,以便四个批次处理输入。 关键字 $using: 用于将 $Message 变量传递到每个并行脚本块。

    示例 12:并行检索日志条目

    此示例从本地 Windows 计算机上的 5 个系统日志中检索 50,000 个日志条目。

    $logNames = 'Security','Application','System','Windows PowerShell','Microsoft-Windows-Store/Operational'
    $logEntries = $logNames | ForEach-Object -Parallel {
        Get-WinEvent -LogName $_ -MaxEvents 10000
    } -ThrottleLimit 5
    $logEntries.Count
    50000

    Parallel 参数指定为每个输入日志名称并行运行的脚本块。 ThrottleLimit 参数可确保所有五个脚本块同时运行。

    示例 13:作为作业并行运行

    此示例创建一个作业,该作业并行运行一个脚本块,一次运行两个脚本块。

    $job = 1..10 | ForEach-Object -Parallel {
        "Output: $_"
        Start-Sleep 1
    } -ThrottleLimit 2 -AsJob
    $job | Receive-Job -Wait
    Output: 1
    Output: 2
    Output: 3
    Output: 4
    Output: 5
    Output: 6
    Output: 7
    Output: 8
    Output: 9
    Output: 10

    变量 $job 接收收集输出数据和监视运行状态的作业对象。 作业对象通过管道连接到 Receive-JobWait 开关参数,该参数将输出流式传输到控制台,就像 ForEach-Object -Parallel 在没有 AsJob 的情况下运行一样。

    示例 14:使用线程安全变量引用

    此示例并行调用脚本块以收集唯一命名的 Process 对象。

    $threadSafeDictionary = [System.Collections.Concurrent.ConcurrentDictionary[string,object]]::new()
    Get-Process | ForEach-Object -Parallel {
        $dict = $using:threadSafeDictionary
        $dict.TryAdd($_.ProcessName, $_)
    $threadSafeDictionary["pwsh"]
    NPM(K)    PM(M)      WS(M)     CPU(s)      Id  SI ProcessName
     ------    -----      -----     ------      --  -- -----------
         82    82.87     130.85      15.55    2808   2 pwsh

    ConcurrentDictionary 对象的单个实例传递到每个脚本块以收集对象。 由于 ConcurrentDictionary 是线程安全的,因此可以安全地由每个并行脚本进行修改。 此处使用非线程安全对象(如 System.Collections.Generic.Dictionary)是不安全的。

    此示例使用 Parallel 参数的效率非常低。 该脚本只是将输入对象添加到并发字典对象。 这是微不足道的,不值得在单独的线程中调用每个脚本的开销。 ForEach-Object在没有并行开关的情况下正常运行会更高效、更快。 此示例仅用于演示如何使用线程安全变量。

    示例 15:并行执行时写入错误

    此示例并行写入错误流,其中写入错误的顺序是随机的。

    1..3 | ForEach-Object -Parallel {
        Write-Error "Error: $_"
    Write-Error: Error: 1
    Write-Error: Error: 3
    Write-Error: Error: 2

    示例 16:并行执行中的终止错误

    此示例演示一个并行运行的 scriptblock 中的终止错误。

    1..5 | ForEach-Object -Parallel {
        if ($_ -eq 3)
            throw "Terminating Error: $_"
        Write-Output "Output: $_"
    Exception: Terminating Error: 3
    Output: 1
    Output: 4
    Output: 2
    Output: 5

    Output: 3 永远不会写入 ,因为该迭代的并行脚本块已终止。

    即使使用 $using: 关键字,也不支持Foreach-Object -ParallelPipelineVariable 通用参数变量。

    示例 17:在嵌套并行脚本 ScriptBlockSet 中传递变量

    可以在作用域内 scriptblock 外部 Foreach-Object -Parallel 创建变量,并在脚本块中使用该变量和 $using 关键字。

    $test1 = 'TestA'
    1..2 | Foreach-Object -Parallel {
        $using:test1
    TestA
    TestA
    # You CANNOT create a variable inside a scoped scriptblock
    # to be used in a nested foreach parallel scriptblock.
    $test1 = 'TestA'
    1..2 | Foreach-Object -Parallel {
        $using:test1
        $test2 = 'TestB'
        1..2 | Foreach-Object -Parallel {
            $using:test2
    Line |
       2 |  1..2 | Foreach-Object -Parallel {
         |         ~~~~~~~~~~~~~~~~~~~~~~~~~~
         | The value of the using variable '$using:test2' cannot be retrieved because it has not been set in the local session.

    嵌套脚本块无法访问变量 $test2 ,并引发错误。

    示例 18:创建多个并行运行脚本的作业

    ThrottleLimit 参数限制在每个 实例 ForEach-Object -Parallel期间运行的并行脚本数。 它不会限制使用 AsJob 参数时可以创建的作业数。 由于作业本身是并发运行的,因此可以创建多个并行作业,每个作业的运行上限为并发脚本块的限制数。

    $jobs = for ($i=0; $i -lt 10; $i++) {
        1..10 | ForEach-Object -Parallel {
            ./RunMyScript.ps1
        } -AsJob -ThrottleLimit 5
    $jobs | Receive-Job -Wait

    此示例创建 10 个正在运行的作业。 每个作业不能同时运行超过 5 个脚本。 并发运行的实例总数限制为 50 (10 个作业, 即 ThrottleLimit 为 5) 。

    -ArgumentList

    指定方法调用的参数数组。 有关 ArgumentList 行为的详细信息,请参阅 about_Splatting

    已在 Windows PowerShell 3.0 中引入了此参数。

    Type:Object[] Aliases:Args Position:Named Default value:None Accept pipeline input:False Accept wildcard characters:False

    -AsJob

    导致并行调用作为 PowerShell 作业运行。 返回单个作业对象,而不是来自正在运行的脚本块的输出。 作业对象包含运行的每个并行脚本块的子作业。 所有 PowerShell 作业 cmdlet 都可以使用该作业对象来监视运行状态和检索数据。

    此参数是在 PowerShell 7.0 中引入的。

    Type:SwitchParameter Position:Named Default value:None Accept pipeline input:False Accept wildcard characters:False

    -Begin

    指定在此 cmdlet 处理任何输入对象之前运行的脚本块。 此脚本块仅针对整个管道运行一次。 有关 块的详细信息 begin ,请参阅 about_Functions

    Type:ScriptBlock Position:Named Default value:None Accept pipeline input:False Accept wildcard characters:False

    -Confirm

    提示你在运行 cmdlet 之前进行确认。

    Type:SwitchParameter Aliases:cf Position:Named Default value:False Accept pipeline input:False Accept wildcard characters:False

    指定在此 cmdlet 处理所有输入对象后运行的脚本块。 此脚本块仅针对整个管道运行一次。 有关 块的详细信息 end ,请参阅 about_Functions

    Type:ScriptBlock Position:Named Default value:None Accept pipeline input:False Accept wildcard characters:False

    -InputObject

    指定输入对象。 ForEach-Object 在每个输入对象上运行脚本块或操作语句。 输入一个包含对象的变量,或键入可获取对象的命令或表达式。

    InputObject 参数与 一起使用 ForEach-Object时,而不是将命令结果传递给 ForEach-Object时, InputObject 值被视为单个对象。 即使值是命令的结果(如 -InputObject (Get-Process))的集合,也是如此。 由于 InputObject 无法从对象的数组或集合中返回单个属性,因此,如果你使用 ForEach-Object 对具有已定义属性中特定值的对象的集合执行操作,请在 ForEach-Object 管道中使用 ,如本主题中的示例所示。

    Type:PSObject Position:Named Default value:None Accept pipeline input:True Accept wildcard characters:False

    -MemberName

    指定要获取的属性或要调用的方法。

    允许使用通配符,但仅在生成的字符串解析为唯一值时才有效。 例如,如果运行 Get-Process | ForEach -MemberName *Name,则通配符模式匹配多个成员,导致命令失败。

    已在 Windows PowerShell 3.0 中引入了此参数。

    Type:String Position:0 Default value:None Accept pipeline input:False Accept wildcard characters:True

    -Parallel

    指定用于并行处理输入对象的脚本块。 输入描述该操作的脚本块。

    此参数是在 PowerShell 7.0 中引入的。

    Type:ScriptBlock Position:Named Default value:None Accept pipeline input:False Accept wildcard characters:False

    -Process

    指定对每个输入对象所执行的操作。 此脚本块针对管道中的每个对象运行。 有关 块的详细信息 process ,请参阅 about_Functions

    Process 参数提供多个脚本块时,第一个脚本块始终映射到块 begin 。 如果只有两个脚本块,则第二个块映射到块 process 。 如果有三个或更多个脚本块,则第一个脚本块始终映射到 begin 块,最后一个块映射到 块 end ,两者之间的块都映射到 块 process

    Type:ScriptBlock[] Position:0 Default value:None Accept pipeline input:False Accept wildcard characters:False

    -RemainingScripts

    指定 Process 参数不采用的所有脚本块。

    已在 Windows PowerShell 3.0 中引入了此参数。

    Type:ScriptBlock[] Position:Named Default value:None Accept pipeline input:False Accept wildcard characters:False

    -ThrottleLimit

    指定并行的脚本块数。 输入对象被阻止,直到正在运行的脚本块计数低于 ThrottleLimit。 默认值为 5

    ThrottleLimit 参数限制在每个 实例 ForEach-Object -Parallel期间运行的并行脚本数。 它不会限制使用 AsJob 参数时可以创建的作业数。 由于作业本身是并发运行的,因此可以创建多个并行作业,每个作业的运行上限为并发脚本块的限制数。

    此参数是在 PowerShell 7.0 中引入的。

    Type:Int32 Position:Named Default value:5 Accept pipeline input:False Accept wildcard characters:False

    -TimeoutSeconds

    指定等待并行处理所有输入的秒数。 在指定的超时时间过后,所有正在运行的脚本都会停止。 将忽略要处理的任何剩余输入对象。 默认值 0 将禁用超时,并且可以 ForEach-Object -Parallel 无限期运行。 在命令行中键入 Ctrl+C 会停止正在运行 ForEach-Object -Parallel 的命令。 此参数不能与 AsJob 参数一起使用。

    此参数是在 PowerShell 7.0 中引入的。

    Type:Int32 Position:Named Default value:0 Accept pipeline input:False Accept wildcard characters:False

    -UseNewRunspace

    使并行调用为每个循环迭代创建新的运行空间,而不是重用运行空间池中的运行空间。

    此参数是在 PowerShell 7.1 中引入的

    Type:SwitchParameter Position:Named Default value:False Accept pipeline input:False Accept wildcard characters:False

    -WhatIf

    显示运行该 cmdlet 时会发生什么情况。 cmdlet 未运行。

    Type:SwitchParameter Aliases:wi Position:Named Default value:False Accept pipeline input:False Accept wildcard characters:False

    PSObject

    你可以通过管道将任何对象传递给此 cmdlet。

    PSObject

    此 cmdlet 返回由输入确定的对象。

    PowerShell 包含以下别名 ForEach-Object

  • 所有平台:
    • foreach
    • cmdlet ForEach-Object 的工作方式与 Foreach 语句非常类似,只不过不能通过管道将输入传递给 Foreach 语句。 有关 Foreach 语句的详细信息,请参阅 about_Foreach

      从 PowerShell 4.0 开始, WhereForEach 添加了 用于集合的方法。 可在此处阅读有关这些新方法 的详细信息about_arrays

      使用 ForEach-Object -Parallel

    • 参数 ForEach-Object -Parallel 集使用 PowerShell 的内部 API 在新运行空间中运行每个脚本块。 这比 ForEach-Object 按顺序处理正常运行的开销要大得多。 与脚本块执行的工作相比,并行运行的开销较小,请务必使用 Parallel 。 例如:

    • 多核计算机上的计算密集型脚本
    • 花费时间等待结果或执行文件操作的脚本
    • 使用 Parallel 参数可能会导致脚本运行速度比平常慢得多。 尤其是并行脚本是普通脚本时。 使用 Parallel 进行试验,以发现它可能有益的位置。

    • 并行运行时,如果用 ScriptPropertiesScriptMethods 修饰的对象在与最初附加到它们的脚本不同的运行空间中运行,则不能保证它们正常运行。

      脚本块调用始终尝试在其 运行空间中运行,无论实际调用的位置如何。 但是, ForEach-Object -Parallel 会创建在使用后删除的临时运行空间,因此没有运行空间供脚本再执行。

      只要 运行空间仍然存在,此行为就可以正常工作。 但是,如果脚本依赖于仅存在于调用方运行空间而不是 运行空间中的外部变量,则可能无法获得所需的结果。

    • 非终止性错误将写入 cmdlet 错误流,因为它们在并行运行的 scriptblock 中发生。 由于并行 scriptblock 执行顺序是不确定的,因此错误在错误流中的出现顺序是随机的。 同样,写入其他数据流(如警告、详细或信息)的消息将按不确定的顺序写入这些数据流。

      终止错误(如异常)会终止发生这些错误的 scriptblock 的单个并行实例。 一个脚本块中的终止错误不会导致 cmdlet 终止 Foreach-Object 。 其他并行运行的脚本块将继续运行,除非它们也遇到终止错误。 终止错误作为 ErrorRecord 写入错误数据流, 其 FullyQualifiedErrorIdPSTaskException。 终止错误可以使用 PowerShell try/catchtrap 块转换为非终止错误。

    • 即使使用 $using: 关键字,并行方案也不支持 PipelineVariable 通用参数变量。

      参数 ForEach-Object -Parallel 集在单独的进程线程上并行运行脚本块。 关键字 $using: 允许将变量引用从 cmdlet 调用线程传递到每个正在运行的脚本块线程。 由于脚本块在不同的线程中运行,因此必须安全地使用引用传递的对象变量。 通常,从不更改的引用对象读取数据是安全的。 但是,如果要修改对象状态,则必须使用线程安全对象,例如 .NET System.Collection.Concurrent 类型 (请参阅示例 11) 。

  •