如何加速PowerShell foreach循环

如何加速PowerShell foreach循环,powershell,cmd,Powershell,Cmd,我有一个PowerShell脚本,可以连接到数据库并提取用户数据列表。我获取这些数据并创建一个foreach循环来运行数据脚本 这是可行的,但速度很慢,因为结果可能是1000多个条目,并且它必须先完成用户A的脚本.bat,然后才能启动用户B。单个用户的Script.bat独立于其他用户,每个用户需要约30秒 有没有办法加快速度?我一直在玩-Parallel,ForEach对象和工作流,但我无法让它工作,可能是因为我是PS中的一个noob foreach ($row in $Dataset.tab

我有一个PowerShell脚本,可以连接到数据库并提取用户数据列表。我获取这些数据并创建一个
foreach
循环来运行数据脚本

这是可行的,但速度很慢,因为结果可能是1000多个条目,并且它必须先完成
用户A的
脚本.bat
,然后才能启动
用户B
。单个用户的
Script.bat
独立于其他用户,每个用户需要约30秒

有没有办法加快速度?我一直在玩
-Parallel
ForEach对象
工作流
,但我无法让它工作,可能是因为我是PS中的一个noob

foreach ($row in $Dataset.tables[0].rows)
{
   $UserID=$row.value
   $DeviceID=$row.value1
   $EmailAddress=$row.email_address

   cmd.exe /c "`"$PSScriptRoot`"\bin\Script.bat -c `" -Switch $UserID`" >> `"$PSScriptRoot`"\${FileName3}_REST_${DateTime}.txt 2> nul";
}

您自己说过,您的瓶颈在于脚本中的批处理文件,而不是循环本身。(与之相反)已经是PowerShell中更快的
foreach
循环机制调查批处理文件,找出为什么需要30秒才能完成,并尽可能优化它


使用作业 注意:
Start Job
将在另一个进程下运行作业。如果您有PowerShell Core,则可以使用
Start-ThreadJob
cmdlet代替
Start-Job
。这将作为同一进程的另一个线程的一部分启动作业,而不是启动另一个进程

如果不能优化批处理脚本或优化它以满足您的需要,那么可以考虑使用异步启动作业来执行,然后检查结果并从中获得任何输出。例如:

# Master list of jobs you need to check the result of later
$jobs = New-Object System.Collections.Generic.List[System.Management.Automation.Job]

# Run your script for each row
foreach ($row in $Dataset.tables[0].rows)
{
   $UserID=$row.value
   $DeviceID=$row.value1
   $EmailAddress=$row.email_address

   # Use Start-Job here to kick off the script and store the job information
   # for later retrieval.
   # The $using: scope modifier allows you to make use of variables that were
   # defined in the session calling Start-Job
   $job = Start-Job -ScriptBlock { cmd.exe /c "`"${using:PSScriptRoot}`"\bin\Script.bat -c `" -Switch ${using:UserID}`" >> `"${using:PSScriptRoot}`"\${using:FileName3}_REST_${DateTime}.txt 2> nul"; }

   # Add the execution to the $jobs list to check the result of later
   # Casting to void here prevents the Add method from returning the object
   # we've added.
   [void]$jobs.Add($job)
}

# Wait for the jobs to be done
Write-Host 'Waiting for all jobs to complete...'
while( $jobs | Where-Object { $_.State -eq 'Running' } ){
  Start-Sleep -s 10
}

# Retrieve the output of the jobs
foreach( $j in $jobs ) {
  Receive-Job $j
}

注意:由于您需要执行1000次脚本,所以您可能需要考虑编写逻辑,每次只能运行一定数量的作业。我上面的示例启动所有必要的作业,而不考虑一次可以执行的数量


有关作业以及可以在正在运行/已完成的作业上检查的属性的详细信息,请查看以下链接:

  • *
*文档中指出,只能在使用远程会话时声明
using
作用域,即使作业是本地的


因此,您说希望加快速度,但唯一提到的瓶颈似乎是所讨论的.BAT文件。蝙蝠是干什么的?那需要半分钟?似乎这将是需要调查的相关领域,而不是上面的代码片段。forloop是我需要纠正的问题。.bat文件预计需要30秒。问题是forloop需要一个接一个地调用.bat文件1000次。我试图找到一种方法来调用bat文件并启动下一个循环,而无需powershell等待.bat的执行完成。尝试将forloop转换为多任务:)尝试使用作业:我刚刚尝试了这个方法,但似乎在ScriptBlock中,``$job=Start job-ScriptBlock{}``之外的变量不可用。根据您的示例,当作业中的命令运行时,$UserID为空。我更新了答案。你必须把这些参数传递到作业中。我的示例参数在
$jobArgs
对象上设置
UserID
,但您也可以添加
$DateTime
$FileName3
的值。请注意,我的示例负载使用字符串表示这些值,但它们可以是任何对象类型。我还修改了
cmd
字符串以使用子表达式而不是字符串插值,因此我们可以从字符串中的
$args
对象中获取属性。实际上,我做了另一个测试,看起来
Start Job
允许您使用
$using:
修饰符从父会话访问变量,即使您不是在远程计算机上执行。我已经更新了我的答案,在这里使用
修饰符来推荐
$。我开始工作了。在脚本块中添加了
$job=Start job-ScriptBlock{}-ArgumentList$UserID、$DeviceID、$EmailAddress、$Track1、$Track2、$PSScriptRoot、$FileName3、$DateTime
,我使用$args[0]、$args[1],等等,这就是为什么我更喜欢使用我最初添加的hashmap技巧,或者使用
范围修饰符。在我看来,必须传入命名参数并按位置引用它们不是很容易理解的。