invoke-parallel -scriptblock 的效率和速度提升

efficiency and speed increase of invoke-parallel -scriptblock

有没有办法加快这个命令的速度并使其更有效地使用资源?

如果文件不需要提取并且可以通过 evtx 文件格式解析,我愿意转换为 python3.5。

我有一个脚本,我是 运行 与脚本块并行调用的 cmdlet。 $files 是我根据用户名和事件 ID 过滤的 10000 个 evtx 文件的列表。

我试图从脚本块中调用 powershell.exe 和 运行 get-winevent cmdlet 的新实例,但这会实例化到许多进程而不会关闭。我没有尝试过工作,但不确定在这种情况下该去哪里。

$files | Invoke-Parallel -ImportModules -ScriptBlock{ 
Get-WinEvent -FilterHashtable @{Path=$_;id=4624;data="ANONYMOUS LOGON","user.name1", "user.name2" } | 
    Select-Object  -Property MachineName,RecordId, TimeCreated,Id,
    @{Name="SubjectUserSid"; Expression={$_.Properties[0].Value}},
    @{Name="SubjectUserName";Expression={$_.Properties[1].Value}},
    @{Name="SubjectDomainName";Expression={$_.Properties[2].Value}},
    @{Name="SubjectLogonId";Expression={$_.Properties[3].Value}},
    @{Name="TargetUserSid";Expression={$_.Properties[4].Value}},
    @{Name="TargetUserName"; Expression={$_.Properties[5].Value}},
    @{Name="TargetDomainName";Expression={$_.Properties[6].Value}},
    @{Name="TargetLogonId";Expression={$_.Properties[7].Value}},
    @{Name="LogonType";Expression={$_.Properties[8].Value}},
    @{Name="LogonProcessName";Expression={$_.Properties[9].Value}},
    @{Name="AuthenticationPackageName";Expression={$_.Properties[10].Value}},
    @{Name="WorkstationName";Expression={$_.Properties[11].Value}},
    @{Name="LogonGuid";Expression={$_.Properties[12].Value}},
    @{Name="TransmittedServices";Expression={$_.Properties[13].Value}},
    @{Name="LmPackageName";Expression={$_.Properties[14].Value}},
    @{Name="KeyLength";Expression={$_.Properties[15].Value}},
    @{Name="ProcessId";Expression={$_.Properties[16].Value}},
    @{Name="ProcessName";Expression={$_.Properties[17].Value}},
    @{Name="IP"; Expression={$_.Properties[18].Value}},
    @{Name="IpPort";Expression={$_.Properties[19].Value}}} -throttle 100 |
     Export-Csv -path "C:\users\username\Desktop\folder\full.csv"   

您可能会考虑设置 Runspaces and RunspacePools。它们的设置有点棘手,但它们的工作方式非常酷且非常高效。池允许您设置各种限制。 RunspaceFactory 将一个作业加载到 x 个插槽中的 1 个中,然后一旦完成,它就会将另一个作业放入该插槽中。 的优点之一是开销——或者说没有开销。 Runspaces 不需要另一个 PowerShell 实例,而 Jobs 需要。

因此,如果您有一个宽度为 50 的 RunspacePool 和 1000 个作业要处理,那么其中 50 个作业将在任何时候 运行。超级酷

Check out this example from mjolinor。 诚然,这是一个相当复杂的例子。为了访问各种数据流,他经历了很多麻烦。这使得监视池中的进程成为可能。

下面是根据我的主要安全审计事件日志文件 (20MB) 测得的约 50 倍 speed-up 的单个作业。

你的代码的问题是由标准的 PowerShell 东西引起的,它在处理大量数据时效率非常低。

  • Select-Object 具有 20 个计算属性,为每条记录创建 20 个 ScriptBlock 上下文。与内部实际的简单代码相比,PS 中的上下文创建需要花费大量时间。

  • Get-WinEvent 为每个事件创建自定义对象,其中包含 20 多个 NoteProperty 对象,
    每个都需要时间来创建。

  • Export-CSV 除了使用缓慢的 PS 管道

  • 之外还需要访问每个 NoteProperty
  • | 流水线比 foreach(不是 cmdlet)等流控制语句慢,而.

让我们开始使用 .NET 3.5+ 和 PS3+ 手动完成所有操作:

$CollectLogonsInCsv = {
param(
    [ValidateScript({ Test-Path -literal $_ })]
    [string]$eventLogPath,

    [Parameter(Mandatory)]
    [string[]]$users,

    [ValidateScript({ Test-Path -IsValid -literal $_ })]
    [string]$outputPath = ($eventLogPath -replace '[^.]+$', 'csv')
)
    $query = '*[System/EventID=4624 and EventData[' +
        ($users -replace '^.+', 'Data[@Name="TargetUserName"]="$&"' -join ' or ') + ']]'
    $reader = [Diagnostics.Eventing.Reader.EventLogReader]::new(
        [Diagnostics.Eventing.Reader.EventLogQuery]::new($eventLogPath,
            [Diagnostics.Eventing.Reader.PathType]::FilePath, $query)
    )
    $writer = [IO.StreamWriter]::new($outputPath, $false, [Text.Encoding]::UTF8, 16MB)
    $writer.WriteLine('MachineName, RecordId, TimeCreated, Id,' +
        'SubjectUserSid, SubjectUserName, SubjectDomainName, SubjectLogonId, ' +
        'TargetUserSid, TargetUserName, TargetDomainName, TargetLogonId, ' +
        'LogonType, LogonProcessName, AuthenticationPackageName, WorkstationName, ' +
        'LogonGuid, TransmittedServices, LmPackageName, KeyLength, ' +
        'ProcessId, ProcessName, IP, IpPort')
    while ($e = $reader.ReadEvent()) {
        $p = $e.properties
        $writer.WriteLine('"' +
            [string]::Join("`0",
                $($e.MachineName, $e.RecordId, $e.TimeCreated, $e.Id; $p[0..19].value)
            ).replace('"', '""').replace("`0", '","') + '"'
        )
    }
    $writer.close()
    [GC]::Collect()
}

现在使用 RunSpaces 并行调用它以进一步改进:

$outputDir = 'C:\Users\Administrator\Desktop\folder68'

Get-Content C:\users\Administrator\Desktop\fullfiles.csv |
    Invoke-Parallel -throttle 100 -ImportModules -ImportVariables -ScriptBlock {
        $outputCsv = Join-Path $outputDir ((Get-Item -literal $_).BaseName + '.csv')
        & $CollectLogonsInCsv $_ @(
            'user.name.1'
            'user.name.2'
            'user.name.3'
            'user.name.4'
        ) $outputCsv
    }