I do a lot of workshops with developers and administrators (or DevOps if you like to call them that way). During one of the last workshops, one of the admins asked me a clever question: “Why do you pipe so much? Why don’t you use .ForEach and .Where instead? This should be faster!”.
So we took an existing function (One I use to clean up all my event log entries, and decided to refactor it. To see the difference, we also created a wrapper script that can handle all the measurement for us.
#requires -Version 4.0
<#
.SYNOPSIS
Compare a old and a refactored function to get any Performance differences
.DESCRIPTION
This script compares a simple function (That deletes all Windows Eventlog Entries) with an refacored one.
The request came up during a workshop: I was asked why I use pipes so much and if there is another way, without pipes.
The refactored version was created during the workshop as a prototype.
And to make it easier to compare them, I created this test script.
.EXAMPLE
PS C:\> .\Clear-EnAllEventLogs_TESTS.ps1
.NOTES
Version: 1.0.0
GUID: a0a633ca-6fd1-4806-a160-05bf1f76342b
Author: Joerg Hochwald
Companyname: enabling Technology
Copyright: Copyright (c) 2ß18-2019, enabling Technology - All rights reserved.
License: https://opensource.org/licenses/BSD-3-Clause
Releasenotes:
1.0.0 2019-07-24 Initial Version
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
Dependencies:
NONE
.LINK
https://www.enatec.io
#>
[CmdletBinding(ConfirmImpact = 'None')]
[OutputType([psobject])]
param ()
#region VersionOfJosh
function Clear-EnAllEventLogs
{
<#
.SYNOPSIS
Delete all Windows event log entries
.DESCRIPTION
Delete all Windows event log entries, without any further interaction.
I use this only after I do some tests on a virtual machine.
Please Note:
It Might be dangerous! It might delete more than you like.
Warning:
All security related will also be removed completely.
If there were any issues, you might never find any information about it!
.PARAMETER ComputerName
Computer Name as String. Multi Value is possible
.EXAMPLE
PS C:\> Clear-EnAllEventLogs
Delete all Windows EventLog Entries on the local Computer.
.EXAMPLE
PS C:\> Clear-EnAllEventLogs -ComputerName FRADC01
Delete all Windows EventLog Entries on the Computer with the name FRADC01.
.EXAMPLE
PS C:\> Clear-EnAllEventLogs -ComputerName 'FRADC01', 'FRADC02'
Delete all Windows EventLog Entries on the Computers with the names FRADC01 and FRADC02.
.NOTES
Version: 1.2.3
GUID: a0a633ca-6fd1-4806-a160-05bf1f76342b
Author: Joerg Hochwald
Companyname: enabling Technology
Copyright: Copyright (c) 2ß18-2019, enabling Technology - All rights reserved.
License: https://opensource.org/licenses/BSD-3-Clause
Releasenotes:
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
Dependencies:
TNONE
.LINK
https://www.enatec.io
.LINK
about_foreach
.LINK
Foreach-Object
.LINK
Get-EventLog
.LINK
Clear-EventLog
#>
[CmdletBinding(ConfirmImpact = 'Medium',
SupportsShouldProcess)]
param
(
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[string[]]
$ComputerName = "$env:COMPUTERNAME"
)
process
{
foreach ($SingleComputerName in $ComputerName)
{
if ($pscmdlet.ShouldProcess($SingleComputerName, 'Cleanup All EventLogs'))
{
$paramGetEventLog = @{
ComputerName = $SingleComputerName
List = $true
}
$null = (Get-EventLog @paramGetEventLog | ForEach-Object -Process {
if ($_.Entries)
{
$paramClearEventLog = @{
LogName = $_.Log
Confirm = $false
ErrorAction = 'SilentlyContinue'
}
$null = (Clear-EventLog @paramClearEventLog)
}
})
}
}
}
}
#endregion VersionOfJosh
#region RefactoredVersion
function Clear-EnAllEventLogsv2
{
<#
.SYNOPSIS
Delete all Windows event log entries
.DESCRIPTION
Delete all Windows event log entries, without any further interaction.
I use this only after I do some tests on a virtual machine.
Please Note:
It Might be dangerous! It might delete more than you like.
Warning:
All security related will also be removed completely.
If there were any issues, you might never find any information about it!
.PARAMETER ComputerName
Computer Name as String. Multi Value is possible
.EXAMPLE
PS C:\> Clear-EnAllEventLogsv2
Delete all Windows EventLog Entries on the local Computer.
.EXAMPLE
PS C:\> Clear-EnAllEventLogsv2 -ComputerName FRADC01
Delete all Windows EventLog Entries on the Computer with the name FRADC01.
.EXAMPLE
PS C:\> Clear-EnAllEventLogsv2 -ComputerName 'FRADC01', 'FRADC02'
Delete all Windows EventLog Entries on the Computers with the names FRADC01 and FRADC02.
.NOTES
Version: 2.0.0
GUID: 0b2fcd13-0b5e-48df-9970-7c5fab649ee7
Author: Joerg Hochwald
Companyname: enabling Technology
Copyright: Copyright (c) 2ß18-2019, enabling Technology - All rights reserved.
License: https://opensource.org/licenses/BSD-3-Clause
Releasenotes:
2.0.0 2019-07-23: Refactored version
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
Dependencies:
NONE
.LINK
https://www.enatec.io
.LINK
about_foreach
.LINK
Foreach-Object
.LINK
Get-EventLog
.LINK
Clear-EventLog
#>
[CmdletBinding(ConfirmImpact = 'Medium',
SupportsShouldProcess)]
param
(
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[string[]]
$ComputerName = "$env:COMPUTERNAME"
)
process
{
foreach ($SingleComputerName in $ComputerName)
{
if ($pscmdlet.ShouldProcess($SingleComputerName, 'Cleanup All EventLogs'))
{
$paramGetEventLog = @{
ComputerName = $SingleComputerName
List = $true
}
$null = ((Get-EventLog @paramGetEventLog).Where({
if ($_.Entries)
{
$_
}
}).ForEach({
$paramClearEventLog = @{
LogName = $_.Log
Confirm = $false
ErrorAction = 'SilentlyContinue'
}
$null = (Clear-EventLog @paramClearEventLog)
}))
}
}
}
}
#endregion RefactoredVersion
#region CreateTestData
function Invoke-CreateTestData
{
<#
.SYNOPSIS
Crete 10.000 a few Dummy entries
.DESCRIPTION
Crete 10.000 a few Dummy entrie
.EXAMPLE
PS C:\> Invoke-CreateTestData
.NOTES
Internal Helper Function to create some useless Test Data
Version: 1.0.1
GUID: bb8bf55b-c3f3-4046-a6e8-2e389fd525d7
Author: Joerg Hochwald
Companyname: enabling Technology
Copyright: Copyright (c) 2ß18-2019, enabling Technology - All rights reserved.
License: https://opensource.org/licenses/BSD-3-Clause
Releasenotes:
1.0.1 2019-07-23: Splat the parameters for better radability
1.0.0 2019-07-23: Initial Version
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
Dependencies:
NONE
.LINK
https://www.enatec.io
.LINK
Write-EventLog
.LINK
about_foreach
.LINK
Foreach-Object
#>
[CmdletBinding(ConfirmImpact = 'None')]
param ()
begin
{
# Splat the parameters
$paramWriteEventLog = @{
LogName = 'Application'
EventId = 2001
EntryType = 'Information'
Source = 'HAL9000'
Message = 'I think you know what the problem is just as well as I do.'
ErrorAction = 'SilentlyContinue'
}
}
process
{
# Change the number to fit your needs
1 .. 1000 | ForEach-Object -Process {
$null = (Write-EventLog @paramWriteEventLog)
}
}
}
#endregion CreateTestData
# Initial Cleanup
$null = (Clear-EnAllEventLogs -ErrorAction SilentlyContinue)
# Create a few new objects
$OldWayAverage = @()
$OldWaySum = @()
$NewWayAverage = @()
$NewWaySum = @()
# Create the new Eventlog
$null = (New-EventLog -LogName Application -Source 'HAL9000' -ErrorAction SilentlyContinue)
#region OldWay
$null = (1..10 | ForEach-Object {
# Create some Test Data
$null = (Invoke-CreateTestData -ErrorAction SilentlyContinue)
#region OldWaySingle
$OldWaySingle = (Measure-Command -Expression {
$null = (Clear-EnAllEventLogs -ErrorAction SilentlyContinue)
})
#endregion OldWaySingle
$OldWaySum += $OldWaySingle
})
$OldWayAverage = (($OldWaySum | Measure-Object -Property TotalMilliseconds -Average).Average)
#endregion OldWay
#region NewWay
$null = (1..10 | ForEach-Object {
# Create some Test Data
$null = (Invoke-CreateTestData -ErrorAction SilentlyContinue)
#region NewWaySingle
$NewWaySingle = (Measure-Command -Expression {
$null = (Clear-EnAllEventLogsv2 -ErrorAction SilentlyContinue)
})
#endregion NewWaySingle
$NewWaySum += $NewWaySingle
})
$NewWayAverage = (($NewWaySum | Measure-Object -Property TotalMilliseconds -Average).Average)
#endregion NewWay
#Region DumpData
Write-Verbose -Message 'Time measured in milliseconds' -Verbose
[pscustomobject]@{
OldWay = $OldWayAverage
NewWay = $NewWayAverage
}
#endregion DumpData
The refactored version is a bit faster.
This is also part of my open-source repository.