Measure-MemoryUsage.ps1
<#PSScriptInfo
.Version 0.0.1 .Guid 19631007-c2aa-4441-9061-f131fff11b19 .Author Ronald Bode (iRon) .CompanyName PowerSnippets.com .Copyright Ronald Bode (iRon) .Tags Measure Memory Usage Command ScriptBlock .LicenseUri https://github.com/iRon7/Measure-MemoryUsage/LICENSE .ProjectUri https://github.com/iRon7/Measure-MemoryUsage .IconUri https://raw.githubusercontent.com/iRon7/Measure-MemoryUsage/master/Measure-MemoryUsage.png .ExternalModuleDependencies .RequiredScripts .ExternalScriptDependencies .ReleaseNotes .PrivateData #> <# .SYNOPSIS Measure memory usage of a command or script block. .DESCRIPTION This cmdlet tries to determine the memory usage of a command or script block by executing it a new session and comparing the memory usage before and the peak usage after the execution of the command or script block. .INPUTS ScriptBlock or Expression .OUTPUTS PSCustomObject .EXAMPLE # Measure the memory usage of the `Get-Service` command Measure-MemoryUsage Get-Service WorkingSet PageMemory VirtualMemory Duration ---------- ---------- ------------- -------- 48.0 KB 64.0 KB 192 KB 00:00:01.2212212 .EXAMPLE # Measure the memory usage of ScriptBlock for Windows PowerShell 5.1 and PowerShell 7 Memory used under PowerShell 7 (launched from PowerShell 7): Measure-MemoryUsage { Get-ChildItem -Recurse *.ps1 } WorkingSet PageMemory VirtualMemory Duration ---------- ---------- ------------- -------- 32.0 KB 36.0 KB 0 bytes 00:00:00.0692499 Memory used under Windows PowerShell 5.1: Measure-MemoryUsage -PSVersion 5.1 { Get-ChildItem -Recurse *.ps1 } WorkingSet PageMemory VirtualMemory Duration ---------- ---------- ------------- -------- 572 KB 1.16 MB 188 KB 00:00:00.2387508 .EXAMPLE # Measure the memory usage of different ways to output a list of objects $Syntaxes = [Ordered]@{ PlusIs = { $a = @() 0..50000 | ForEach-Object { $a += [PSCustomObject]@{ Index = $_; Name = "Name$_" } } $a | Export-Csv .\Test.csv } List = { $a = [System.Collections.Generic.List[Object]]::new() 0..50000 | ForEach-Object { $a.Add([PSCustomObject]@{ Index = $_; Name = "Name$_" }) } $a | Export-Csv .\Test.csv } Assign = { $a = 0..50000 | ForEach-Object { [PSCustomObject]@{ Index = $_; Name = "Name$_" } } $a | Export-Csv .\Test.csv } Pipeline = { 0..50000 | ForEach-Object { [PSCustomObject]@{ Index = $_; Name = "Name$_" } } | Export-Csv .\Test.csv } } Foreach ($SyntaxName in $Syntaxes.Keys) { $Syntax = $Syntaxes[$SyntaxName] $Properties = [Ordered]@{ Syntax = $SyntaxName } Foreach ($PSVersion in '5.1', '7') { $Params = @{ Command = $Syntax } if ($PSVersion -eq '5.1') { $Params.PSVersion = $PSVersion } $MemoryUsage = Measure-MemoryUsage @Params $Properties["PowerShell $PSVersion"] = $MemoryUsage.WorkingSet } [PSCustomObject]$Properties } Yields: Syntax PowerShell 5.1 PowerShell 7 ------ -------------- ------------ PlusIs 11.3 MB 48.8 MB List 1.42 MB 28.0 KB Assign 1.44 MB 28.0 KB Pipeline 39.0 MB 24.0 KB > [!WARNING] > The results of measured memory usage is an indication of the memory used by a specific command > but might be erratic and even negative due to the (automatic) garbage collector and other factors. > Besides, the results might be inconclusive or misleading due to the actual memory definitions for > [private bytes, virtual bytes and working sets][1] .PARAMETER Command The command or script block to measure the memory usage of. .PARAMETER PSVersion The version of PowerShell to use. Default is the current version./ This parameter is only available in PowerShell 7 and later. .LINK [1]: https://stackoverflow.com/a/1986486/1701026 "private bytes, virtual bytes and working sets" #> Using namespace System.Management.Automation [CmdletBinding()][OutputType([PSCustomObject])] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)]$Command, [Version]$PSVersion ) begin { # https://stackoverflow.com/a/57535522/1701026 Class ByteSize { hidden Static $Shlwapi Static ByteSize() { $TypeParams = @{ Name = 'ShlwapiFunctions' Namespace = 'ShlwapiFunctions' MemberDefinition = '[DllImport("Shlwapi.dll", CharSet=CharSet.Auto)]public static extern int StrFormatByteSize(long fileSize, System.Text.StringBuilder pwszBuff, int cchBuff);' PassThru = $true } [ByteSize]::Shlwapi = Add-Type @TypeParams } hidden $_Value ByteSize($Value) { $this._Value = $Value } [String]ToString() { $Bytes = New-Object Text.StringBuilder 20 $Return = [ByteSize]::Shlwapi::StrFormatByteSize($this._Value, $Bytes, $Bytes.Capacity) if (-not $Return) { throw "Failed to convert $($this._Value) format byte size." } return $Bytes.ToString() } } } process { # https://stackoverflow.com/questions/1984186/what-is-private-bytes-virtual-bytes-working-set $MemoryUsage = try { $Params = @{ ScriptBlock = { $Process0 = Get-Process -Id $PID $Process0.Refresh() $DateTime0 = [DateTime]::Now $null = Invoke-Expression $Args[0] $DateTime1 = [DateTime]::Now $Process1 = Get-Process -Id $PID [PSCustomObject]@{ StartPageMemory = $Process0.PagedMemorySize64 StartPrivateMemorySize = $Process0.PrivateMemorySize64 StartVirtualMemorySize = $Process0.VirtualMemorySize64 StartWorkingSet = $Process0.WorkingSet64 FinalPageMemory = $Process1.PagedMemorySize64 FinalPrivateMemorySize = $Process1.PrivateMemorySize64 FinalVirtualMemorySize = $Process1.VirtualMemorySize64 FinalWorkingSet = $Process1.WorkingSet64 FinalPeakPageMemory = $Process1.PeakPagedMemorySize64 FinalPeakVirtualMemorySize = $Process1.PeakVirtualMemorySize64 FinalPeakWorkingSet = $Process1.PeakWorkingSet64 DeltaWorkingSet = $Process1.PeakWorkingSet64 - $Process0.WorkingSet64 DeltaPagedMemory = $Process1.PeakPagedMemorySize64 - $Process0.PagedMemorySize64 DeltaVirtualMemory = $Process1.PeakVirtualMemorySize64 - $Process0.VirtualMemorySize64 DeltaDateTime = $DateTime1 - $DateTime0 } } ArgumentList = [String]$Command } if ($PSBoundParameters.ContainsKey('PSVersion')) { $Params['PSVersion'] = $PSVersion } Start-Job @Params | Receive-Job -AutoRemoveJob -Wait } catch [ParameterBindingException] { $PSCmdlet.ThrowTerminatingError($_) } $MemoryUsage.PSObject.Properties.Add([PSScriptProperty]::new('WorkingSet', { [String][ByteSize]$this.DeltaWorkingSet })) $MemoryUsage.PSObject.Properties.Add([PSScriptProperty]::new('PageMemory', { [String][ByteSize]$this.DeltaPagedMemory })) $MemoryUsage.PSObject.Properties.Add([PSScriptProperty]::new('VirtualMemory', { [String][ByteSize]$this.DeltaVirtualMemory })) $MemoryUsage.PSObject.Properties.Add([PSScriptProperty]::new('Duration', { [String]$this.DeltaDateTime })) $DefaultDisplaySet = 'WorkingSet', 'PageMemory', 'VirtualMemory', 'Duration' $DefaultDisplayPropertySet = [PSPropertySet]::new('DefaultDisplayPropertySet', [string[]]$DefaultDisplaySet) $PSStandardMembers = [PSMemberInfo[]]@($DefaultDisplayPropertySet) $MemoryUsage | Add-Member MemberSet PSStandardMembers $PSStandardMembers $MemoryUsage } |