Skip to content

Instantly share code, notes, and snippets.

@santisq
Last active May 4, 2026 14:37
Show Gist options
  • Select an option

  • Save santisq/218c577bb14e48699a05a5f5ab05d933 to your computer and use it in GitHub Desktop.

Select an option

Save santisq/218c577bb14e48699a05a5f5ab05d933 to your computer and use it in GitHub Desktop.
one time use garbage

COMPILE

Place the .csproj in the same folder as MeasureScriptBlockMemoryCommand.cs (assuming folder is src in this example):

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>library</OutputType>
    <AssemblyName>MeasureScriptBlockMemoryCommand</AssemblyName>
    <TargetFramework>net10.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <LangVersion>latest</LangVersion>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="10.0.7" />
    <PackageReference Include="System.Management.Automation" Version="7.6.1" PrivateAssets="all" />
  </ItemGroup>
</Project>

Compile it using the dotnet CLI, you need .NET SDK 10+:

PS ..\pwsh> cd .\src
PS ..\src> dotnet publish --configuration Release -o .\lib

USAGE

PS ..\pwsh> Import-Module .\src\lib\MeasureScriptBlockMemoryCommand.dll
PS ..\pwsh> Measure-ScriptBlockMemory { Get-ChildItem C:\ -Depth 4 -EA 0 } | FT 

TimeStamp            Span             InvocationMemoryInMb ProcessMemoryInMb Invocation
---------            ----             -------------------- ----------------- ----------
5/4/2026 11:29:48 AM 00:00:00.0042701                 0.00            158.92 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:48 AM 00:00:00.5092726                 2.67            161.59 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:49 AM 00:00:01.0192534                14.51            173.43 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:49 AM 00:00:01.5590699                88.12            247.04 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:50 AM 00:00:02.0711037               198.14            357.06 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:50 AM 00:00:02.5808020               241.89            400.81 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:51 AM 00:00:03.0914375               308.49            467.41 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:51 AM 00:00:03.6022986               338.77            497.69 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:52 AM 00:00:04.1183466               348.41            507.33 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:52 AM 00:00:04.6334695               355.06            513.98 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:53 AM 00:00:05.1504214               369.18            528.09 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:53 AM 00:00:05.6611558               376.88            535.80 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:54 AM 00:00:06.1735121               387.57            546.48 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:54 AM 00:00:06.6855683               403.80            562.72 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:55 AM 00:00:07.1983418               414.84            573.75 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:55 AM 00:00:07.7126693               426.56            585.48 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:56 AM 00:00:08.2267385               435.40            594.32 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:56 AM 00:00:08.7506555               444.45            603.37 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:57 AM 00:00:09.2676887               466.34            625.25 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:57 AM 00:00:09.7787815               473.64            632.56 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:58 AM 00:00:10.2898625               522.69            681.61 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:59 AM 00:00:10.7998353               535.00            693.91 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:29:59 AM 00:00:11.3124713               541.42            700.34 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:30:00 AM 00:00:11.8262417               555.22            714.14 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:30:00 AM 00:00:12.3388784               565.68            724.60 Get-ChildItem C:\ -Depth 4 -EA 0
5/4/2026 11:30:01 AM 00:00:12.8507531               581.93            740.85 Get-ChildItem C:\ -Depth 4 -EA 0
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Management.Automation;
using System.Threading.Tasks;
internal sealed class MemoryCounterSampleFactory(PerformanceCounter counter, ScriptBlock invocation)
: IDisposable
{
private const long Mb = 1_048_576;
private readonly PerformanceCounter _counter = counter;
private readonly DateTime _start = DateTime.Now;
private readonly string _invocation = invocation.ToString().Trim();
private readonly float _base = counter.NextValue();
internal MemoryCounterSample Next()
{
DateTime now = DateTime.Now;
float nextValue = _counter.NextValue();
return new MemoryCounterSample()
{
TimeStamp = now,
Span = now - _start,
InvocationMemoryInMb = Math.Max(nextValue - _base, 0) / Mb,
ProcessMemoryInMb = nextValue / Mb,
Invocation = _invocation
};
}
public void Dispose() => _counter.Dispose();
}
public struct MemoryCounterSample
{
public DateTime TimeStamp;
public TimeSpan Span;
public float InvocationMemoryInMb;
public float ProcessMemoryInMb;
public string? Invocation;
}
[Cmdlet(VerbsDiagnostic.Measure, "ScriptBlockMemory")]
public sealed class MeasureScriptBlockMemoryCommand : PSCmdlet
{
[Parameter(Mandatory = true, Position = 0)]
public ScriptBlock ScriptBlock { get; set; } = null!;
[Parameter(Position = 1)]
[ValidateNotNull]
public TimeSpan Interval { get; set; } = TimeSpan.FromMilliseconds(500);
protected override void EndProcessing()
{
using MemoryCounterSampleFactory factory = new(GetCounter(), ScriptBlock);
using BlockingCollection<MemoryCounterSample> output = [];
Task.Run(async () =>
{
while (!output.IsAddingCompleted)
{
output.Add(factory.Next());
await Task.Delay(Interval);
}
}).ConfigureAwait(false);
Task.Run(() =>
{
try
{
_ = ScriptBlock.Invoke();
}
finally
{
output.CompleteAdding();
}
}).ConfigureAwait(false);
foreach (MemoryCounterSample sample in output.GetConsumingEnumerable())
WriteObject(sample);
}
private static PerformanceCounter GetCounter()
{
PerformanceCounterCategory category = new("process");
using PerformanceCounter counter = new("process", "id process", true);
foreach (string c in category.GetInstanceNames())
{
counter.InstanceName = c;
if (counter.RawValue == Environment.ProcessId)
{
counter.CounterName = "private bytes";
return counter;
}
}
throw new OperationCanceledException(
$"Could not find performance counter instance for PID '{Environment.ProcessId}'.");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment