Foreach-ObjectFast.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
function Foreach-ObjectFast
{
  <#
      .SYNOPSIS
      Faster Foreach-Object
 
      .DESCRIPTION
      Foreach-ObjectFast can replace the built-in Foreach-Object and improves pipeline speed considerably.
      Foreach-ObjectFast supports only the most commonly used parameters -Begin, -Process, and -End, so you can replace
     
      1..100 | Foreach-Object { 'Server{0:d3}' -f $_ }
     
      with
     
      1..100 | Foreach-ObjectFast { 'Server{0:d3}' -f $_ }
     
      but you cannot currently replace instances of Foreach-Object that uses the less commonly used parameters,
      like -RemainingScripts, -MemberNames, and -ArgumentList
 
      Foreach-ObjectFast has a performance benefit per iteration, so the more objects
      you send through the pipeline, the more significant performace benefits you will see.
 
      Foreach-ObjectFast is using a steppable pipeline internally which performs better.
      However because of this, the debugging experience will be different, and internal
      variables such as $MyInvocation may yield different results. For most every-day tasks,
      these changes are not important.
 
      A complete explanation of what Where-ObjectFast does can be found here:
      https://powershell.one/tricks/performance/pipeline
 
      .EXAMPLE
      $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
 
      $result = 1..1000000 | Foreach-ObjectFast -Process {
        "I am at $_"
      }
 
      $report = '{0} elements in {1:n2} seconds'
      $report -f $result.Count, $stopwatch.Elapsed.TotalSeconds
       
      Demos the speed improvements. Run this script to see how well it performs,
      then replace Foreach-ObjectFast with the default Foreach-Object, and check out
      the performace difference. $result is the same in both cases.
 
      .LINK
      https://powershell.one/tricks/performance/pipeline
      https://github.com/TobiasPSP/Modules.PSOneTools/blob/master/PSOneTools/1.2/Foreach-ObjectFast.ps1
  #>

  
  param
  (
    # executes for each pipeline element
    [ScriptBlock]
    $Process,
    
    # executes once before the pipeline is started.
    # can be used for initialization routines
    [ScriptBlock]
    $Begin,
    
    # executes once after all pipeline elements have been processed
    # can be used to do cleanup work
    [ScriptBlock]
    $End
  )
  
  begin
  {
    # construct a hard-coded anonymous simple function from
    # the submitted scriptblocks:
    $code = @"
& {
  begin
  {
    $Begin
  }
  process
  {
    $Process
  }
  end
  {
    $End
  }
}
"@

    # turn code into a scriptblock and invoke it
    # via a steppable pipeline so we can feed in data
    # as it comes in via the pipeline:
    $pip = [ScriptBlock]::Create($code).GetSteppablePipeline($myInvocation.CommandOrigin)
    $pip.Begin($true)
  }
  process 
  {
    # forward incoming pipeline data to the custom scriptblock:
    $pip.Process($_)
  }
  end
  {
    $pip.End()
  }
}