ForEach-Object
针对输入对象集合中的每个项执行操作。
语法
ForEach-Object
[-InputObject <PSObject>]
[-Begin <ScriptBlock>]
[-Process] <ScriptBlock[]>
[-End <ScriptBlock>]
[-RemainingScripts <ScriptBlock[]>]
[-WhatIf]
[-Confirm]
[<CommonParameters>]
ForEach-Object
[-InputObject <PSObject>]
[-MemberName] <String>
[-ArgumentList <Object[]>]
[-WhatIf]
[-Confirm]
[<CommonParameters>]
ForEach-Object
-Parallel <scriptblock>
[-InputObject <psobject>]
[-ThrottleLimit <int>]
[-TimeoutSeconds <int>]
[-AsJob]
[-UseNewRunspace]
[-WhatIf]
[-Confirm]
[<CommonParameters>]
说明
ForEach-Object
cmdlet 针对输入对象集合中的每个项执行操作。 可通过管道将输入对象传递给 cmdlet,或使用 InputObject 参数指定输入对象。
从 Windows PowerShell 3.0 开始,可使用两种不同的方法构造 ForEach-Object
命令。
脚本块。 你可以使用某个脚本块来指定操作。 在脚本块中,使用
$_
变量来表示当前对象。 脚本块是 Process 参数的值。 脚本块可以包含任何 PowerShell 脚本。例如,以下命令将获取计算机上每个进程的 ProcessName 属性值。
Get-Process | ForEach-Object {$_.ProcessName}
ForEach-Object
支持 about_functions 中所述的begin
、process
和end
块。注意
脚本块在调用方的作用域内运行。 因此,这些块可以访问该作用域中的变量,并可以在 cmdlet 完成后创建保留在该作用域内的新变量。
操作语句。 你还可以编写操作语句,它更像自然语言。 你可以使用该操作语句来指定属性值或调用方法。 Windows PowerShell 3.0 中引入了操作语句。
例如,以下命令还将获取计算机上每个进程的 ProcessName 属性值。
Get-Process | ForEach-Object ProcessName
并行运行的脚本块。 从 PowerShell 7.0 开始,可以使用第三个参数集并行运行每个脚本块。 ThrottleLimit 参数限制一次运行的并行脚本数量。 与以前一样,使用
$_
变量来表示脚本块中的当前输入对象。 使用$using:
关键字将变量引用传递给正在运行的脚本。在 PowerShell 7 中,将为每个循环迭代创建新的运行空间,以确保最大隔离。 如果与创建新的运行空间相比,你所做的工作很少,或者有大量执行重要工作的迭代,那么这可能会对性能和资源造成很大的影响。 从 PowerShell 7.1 起,默认情况下会重复使用运行空间池中的运行空间。 ThrottleLimit 参数设置运行空间池大小。 默认运行空间池大小为 5。 你仍可以使用 UseNewRunspace 开关为每个迭代创建新的运行空间。
默认情况下,并行脚本块使用启动并行任务的调用方的当前工作目录。
有关详细信息,请参阅本文的注释部分。
示例
示例 1:将整数除以数组
此示例接受一个包含三个整数的数组,然后将其中每个整数除以 1024。
30000, 56798, 12432 | ForEach-Object -Process {$_/1024}
29.296875
55.466796875
12.140625
示例 2:获取目录中所有文件的长度
此示例处理 PowerShell 安装目录 $PSHOME
中的文件和目录。
Get-ChildItem $PSHOME |
ForEach-Object -Process {if (!$_.PSIsContainer) {$_.Name; $_.Length / 1024; " " }}
如果对象不是目录,则脚本块将获取该文件名、将其 Length 属性的值除以 1024,并添加一个空格 (" ") 以将其与下一个条目隔开。 该 cmdlet 使用 PSISContainer 属性来确定对象是否为目录。
示例 3:对最新的系统事件进行操作
此示例将 1000 个最新事件从系统事件日志写入文本文件。 当前时间显示在处理事件之前和之后。
Get-EventLog -LogName System -Newest 1000 |
ForEach-Object -Begin {Get-Date} -Process {
Out-File -FilePath Events.txt -Append -InputObject $_.Message
} -End {Get-Date}
Get-EventLog
从系统事件日志中获取 1000 个最新事件,并将其 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()
}
可以使用此格式更改注册表条目值的形式或内容。
Network 项中的每个子项都表示将在登录时重新连接的映射网络驱动器。 RemotePath 项包含连接的驱动器的 UNC 路径。 例如,如果将驱动器映射到E:
其中,则会在将 RemotePath 注册表值设置为 \\Server\Share
的情况下HKCU:\Network
创建 E 子\\Server\Share
项。
该命令使用 Get-ItemProperty
cmdlet 获取 Network 项的所有子项,并使用 Set-ItemProperty
cmdlet 更改每项中的 RemotePath 注册表项的值。 在 Set-ItemProperty
命令中,路径为该注册表项的 PSPath 属性的值。 这是表示该注册表项的 Microsoft .NET Framework 对象的一个属性,而不是一个注册表条目。 该命令使用 RemotePath 值的 ToUpper() 方法,该值是一个字符串 REG_SZ。
因为 Set-ItemProperty
更改每一项的属性,所以需要 ForEach-Object
mdlet 访问该属性。
示例 5:使用 $null 自动变量
此示例显示了通过管道将 $null
自动变量传递给 ForEach-Object
cmdlet 的效果。
1, 2, $null, 4 | ForEach-Object {"Hello"}
Hello
Hello
Hello
Hello
由于 PowerShell 将 $null
视为显式占位符,因此 ForEach-Object
cmdlet 会为 $null
生成一个值,就像为通过管道连接到它的其他对象生成值一样。
示例 6:获取属性值
此示例使用 ForEach-Object
cmdlet 的 MemberName 参数获取所有已安装 PowerShell 模块的 Path 属性的值。
Get-Module -ListAvailable | ForEach-Object -MemberName Path
Get-Module -ListAvailable | Foreach Path
第二个命令等效于第一个命令。 该命令使用 ForEach-Object
cmdlet 的 Foreach
别名,并省略 MemberName 参数(可选)的名称。
ForEach-Object
cmdlet 可用于获取属性值,因为它获取值而不更改类型,这与 Format 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
Core
Microsoft
PowerShell
Host
第一个命令使用传统语法,包括脚本块和当前的对象运算符 $_
。 它使用句点语法来指定该方法,并使用括号将分隔符参数括起来。
第二个命令使用 MemberName 参数指定 Split 方法,并使用 ArgumentName 参数将句点 (.
) 标识为拆分分隔符。
第三个命令使用 ForEach-Object
cmdlet 的 Foreach 别名,并省略 MemberName 和 ArgumentList 参数(可选)的名称。
示例 8:将 ForEach-Object 与两个脚本块配合使用
在此示例中,我们按位置传递两个脚本块。 所有脚本块都绑定到 Process 参数。 但是,它们被视为已传递到 Begin 和 Process 参数。
1..2 | ForEach-Object { 'begin' } { 'process' }
begin
process
process
示例 9:对两个以上的脚本块使用 ForEach-Object
在此示例中,我们按位置传递四个脚本块。 所有脚本块都绑定到 Process 参数。 但是,它们被视为已传递到 Begin、Process 和 End 参数。
1..2 | ForEach-Object { 'begin' } { 'process A' } { 'process B' } { 'end' }
begin
process A
process B
process A
process B
end
注意
第一个脚本块始终映射到 begin
块,最后一个块映射到 end
块,两个中间块映射到 process
块。
示例 10:为每个管道项运行多个脚本块
如前面的示例所示,使用 Process 参数传递的多个脚本块将映射到 Begin 和 End 参数。 若要避免此映射,必须为 Begin 和 End 参数提供显式值。
1..2 | ForEach-Object -Begin $null -Process { 'one' }, { 'two' }, { 'three' } -End $null
one
two
three
one
two
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:以作业的形式并行运行
此示例创建了一个并行运行脚本块的作业,一次运行两个脚本块。
PS> $job = 1..10 | ForEach-Object -Parallel {
"Output: $_"
Start-Sleep 1
} -ThrottleLimit 2 -AsJob
PS> $job
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
23 Job23 PSTaskJob Running True PowerShell …
PS> $job.ChildJobs
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
24 Job24 PSTaskChildJob Completed True PowerShell …
25 Job25 PSTaskChildJob Completed True PowerShell …
26 Job26 PSTaskChildJob Running True PowerShell …
27 Job27 PSTaskChildJob Running True PowerShell …
28 Job28 PSTaskChildJob NotStarted False PowerShell …
29 Job29 PSTaskChildJob NotStarted False PowerShell …
30 Job30 PSTaskChildJob NotStarted False PowerShell …
31 Job31 PSTaskChildJob NotStarted False PowerShell …
32 Job32 PSTaskChildJob NotStarted False PowerShell …
33 Job33 PSTaskChildJob NotStarted False PowerShell …
ThrottleLimit 参数限制一次运行的并行脚本块数。 AsJob 参数会导致 ForEach-Object
cmdlet 返回作业对象,而不是将输出流式传输到控制台。 该 $job
变量接收收集输出数据和监视运行状态的作业对象。 该 $job.ChildJobs
属性包含运行并行脚本块的子作业。
示例 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)并不安全。
注意
此示例是并行参数的低效使用。 该脚本将输入对象添加到并发字典对象。 这并不重要,不值得在单独的线程中调用每个脚本。 在没有并行交换机的情况下运行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
永远不会写入,因为该迭代的并行脚本块已终止。
注意
PipelineVariable 常见参数变量在 Foreach-Object -Parallel
方案中不受支持,即使使用 $using:
关键字也是如此。
示例 17:在嵌套并行脚本 ScriptBlockSet 中传递变量
可以在 Foreach-Object -Parallel
范围的 scriptblock 外部创建一个变量,并通过 $using
关键字在 scriptblock 内部使用它。
$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' can't be retrieved because it has
| not been set in the local session.
嵌套 scriptblock 无法访问 $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 个作业乘以 5(ThrottleLimit 值))。
参数
-ArgumentList
指定方法调用的参数数组。 有关 ArgumentList 的行为的详细信息,请参阅 about_Splatting。
已在 Windows PowerShell 3.0 中引入了此参数。
类型: | Object[] |
别名: | Args |
Position: | Named |
默认值: | None |
必需: | False |
接受管道输入: | False |
接受通配符: | False |
-AsJob
导致并行调用作为 PowerShell 作业运行。 将返回单个作业对象,而不是运行脚本块的输出。 该作业对象包含运行的每个并行脚本块的子作业。 可以将作业对象与任何 PowerShell 作业 cmdlet 一起使用,以查看正在运行的状态并检索数据。
此参数是在 PowerShell 7.0 中引入的。
类型: | SwitchParameter |
Position: | Named |
默认值: | None |
必需: | False |
接受管道输入: | False |
接受通配符: | False |
-Begin
指定在此 cmdlet 处理任何输入对象之前运行的脚本块。 此脚本块仅针对整个管道运行一次。 有关 begin
块的详细信息,请参阅 about_Functions。
类型: | ScriptBlock |
Position: | Named |
默认值: | None |
必需: | False |
接受管道输入: | False |
接受通配符: | False |
-Confirm
提示你在运行 cmdlet 之前进行确认。
类型: | SwitchParameter |
别名: | cf |
Position: | Named |
默认值: | False |
必需: | False |
接受管道输入: | False |
接受通配符: | False |
-End
指定在此 cmdlet 处理所有输入对象之后运行的脚本块。 此脚本块仅针对整个管道运行一次。 有关 end
块的详细信息,请参阅 about_Functions。
类型: | ScriptBlock |
Position: | Named |
默认值: | None |
必需: | False |
接受管道输入: | False |
接受通配符: | False |
-InputObject
指定输入对象。 ForEach-Object
在每个输入对象上运行脚本块或操作语句。 输入一个包含对象的变量,或键入可获取对象的命令或表达式。
将 InputObject 参数用于 ForEach-Object
时,InputObject 值将被视为单个对象,而不是通过管道将命令结果传递给 ForEach-Object
。 即使值是命令的结果(如 -InputObject (Get-Process)
),也是如此。
由于 InputObject 无法返回来自数组或对象集合的单独属性,如果你使用 ForEach-Object
在对象集合上为在定义的属性中具有特定值的对象执行操作,则建议你在管道中使用 ForEach-Object
,如本主题中的示例所示。
类型: | PSObject |
Position: | Named |
默认值: | None |
必需: | False |
接受管道输入: | True |
接受通配符: | False |
-MemberName
指定要获取的成员属性的名称或要调用的成员方法。 成员必须是实例成员,而不是静态成员。
允许使用通配符,但仅在生成的字符串解析为唯一值时才有效。
例如,如果运行 Get-Process | ForEach -MemberName *Name
,则通配符模式匹配多个成员,导致命令失败。
已在 Windows PowerShell 3.0 中引入了此参数。
类型: | String |
Position: | 0 |
默认值: | None |
必需: | True |
接受管道输入: | False |
接受通配符: | True |
-Parallel
指定要用于并行处理输入对象的脚本块。 输入描述该操作的脚本块。
此参数是在 PowerShell 7.0 中引入的。
类型: | ScriptBlock |
Position: | Named |
默认值: | None |
必需: | True |
接受管道输入: | False |
接受通配符: | False |
-Process
指定对每个输入对象所执行的操作。 此脚本块针对管道中的每个对象运行。 有关 process
块的详细信息,请参阅 about_Functions。
向 Process 参数提供多个脚本块时,第一个脚本块始终映射到 begin
块。 如果只有两个脚本块,则第二个块将映射到 process
块。 如果有三个或更多个脚本块,则第一个脚本块始终映射到 begin
块,最后一个块映射到 end
块,中间块映射到 process
块。
类型: | ScriptBlock[] |
Position: | 0 |
默认值: | None |
必需: | True |
接受管道输入: | False |
接受通配符: | False |
-RemainingScripts
指定 Process 参数未采用的所有脚本块。
已在 Windows PowerShell 3.0 中引入了此参数。
类型: | ScriptBlock[] |
Position: | Named |
默认值: | None |
必需: | False |
接受管道输入: | False |
接受通配符: | False |
-ThrottleLimit
指定并行运行的脚本块数。 输入对象将被阻止,直到正在运行的脚本块计数低于 ThrottleLimit。 默认值是 5
。
ThrottleLimit 参数限制在 ForEach-Object -Parallel
的每个实例期间运行的并行脚本数。 它不会限制使用 AsJob 参数时可以创建的作业数。 由于作业本身会同时运行,因此可以创建多个并行作业,每个作业都运行到并发脚本块的限制数。
此参数是在 PowerShell 7.0 中引入的。
类型: | Int32 |
Position: | Named |
默认值: | 5 |
必需: | False |
接受管道输入: | False |
接受通配符: | False |
-TimeoutSeconds
指定等待并行处理所有输入的秒数。 超过指定的超时时间后,所有正在运行的脚本都将停止。 要处理的任何剩余输入对象都将被忽略。 默认值 0
会禁用超时,ForEach-Object -Parallel
可以无限期运行。 在命令行中键入 Ctrl+C 会停止运行 ForEach-Object -Parallel
命令。 此参数不能与 AsJob 参数一起使用。
此参数是在 PowerShell 7.0 中引入的。
类型: | Int32 |
Position: | Named |
默认值: | 0 |
必需: | False |
接受管道输入: | False |
接受通配符: | False |
-UseNewRunspace
导致并行调用为每个循环迭代创建新的运行空间,而不是重用运行空间池中的运行空间。
此参数是在 PowerShell 7.1 中引入的
类型: | SwitchParameter |
Position: | Named |
默认值: | False |
必需: | False |
接受管道输入: | False |
接受通配符: | False |
-WhatIf
显示运行该 cmdlet 时会发生什么情况。 cmdlet 未运行。
类型: | SwitchParameter |
别名: | wi |
Position: | Named |
默认值: | False |
必需: | False |
接受管道输入: | False |
接受通配符: | False |
输入
你可以通过管道将任何对象传递给此 cmdlet。
输出
此 cmdlet 返回由输入确定的对象。
备注
PowerShell 包含 ForEach-Object
的以下别名:
- 所有平台:
%
foreach
ForEach-Object
cmdlet 的工作方式与 Foreach 语句非常相似,不同之处在于不能通过管道将输入传递给 Foreach 语句。 有关 Foreach 语句的详细信息,请参阅 about_Foreach。
从 PowerShell 4.0 开始,添加了用于集合的 Where
和 ForEach
方法。 可以在此处 about_arrays 阅读有关这些新方法的详细信息
使用 ForEach-Object -Parallel
:
ForEach-Object -Parallel
在新运行空间中运行每个脚本块。 新的运行空间比使用顺序处理运行的ForEach-Object
开销要高得多。 使用 Parallel 非常重要,与脚本块执行的工作相比,并行运行的开销很小。 例如:- 在多核计算机上计算密集型脚本
- 花时间等待结果或执行文件操作的脚本
使用 Parallel 参数可能会导致脚本运行速度比正常情况慢得多。 尤其是当并行脚本并不重要时。 使用 Parallel 进行试验,以发现它在哪些方面有用。
当并行运行时,无法保证使用 ScriptProperties 或 ScriptMethods 装饰的对象可以正常运行(如果它们在与脚本最初附加到的运行空间不同的空间中运行)。
Scriptblock 调用始终会尝试在其主运行空间中运行,而不考虑实际调用的位置。 但是,
ForEach-Object -Parallel
会创建临时运行空间,它们在使用后会被删除,因此不再有可供脚本执行的运行空间。只要主运行空间仍然存在,此行为就可以正常工作。 但是,如果脚本依赖于仅存在于调用方运行空间中的外部变量,而不是主运行空间,则可能无法获得所需的结果。
非终止错误将写入 cmdlet 错误流,因为它们在并行运行的 scriptblock 中出现。 由于并行 scriptblock 执行顺序是不确定的,因此错误在错误流中出现的顺序也是随机的。 同样,写入到其他数据流的消息(如警告、详细或信息)将按不确定的顺序写入这些数据流。
终止错误(如异常)会终止出现错误的 scriptblock 的单个并行实例。 一个 scriptblock 中的终止错误可能会导致终止
Foreach-Object
cmdlet。 其他 scriptblock(并行运行)将继续运行,除非它们也遇到终止错误。 终止错误以 ErrorRecord 的形式写入错误数据流,其中包含PSTaskException
的 FullyQualifiedErrorId。 可以使用 PowerShelltry
/catch
或trap
块将终止错误转换为非终止错误。PipelineVariable 常见参数变量在并行方案中不受支持,即使使用
$using:
关键字也是如此。重要
ForEach-Object -Parallel
参数集在单独的进程线程上并行运行脚本块。$using:
关键字允许将变量引用从 cmdlet 调用线程传递到每个正在运行的脚本块线程。 由于脚本块在不同的线程中运行,因此必须安全地使用引用传递的对象变量。 通常,从不会发生更改的引用对象进行读取是安全的。 如果需要修改对象状态,则必须使用线程安全对象,例如 .NET System.Collection.Concurrent 类型(请参阅示例 14)。