Powershell 7.x and Get-Counter as a Background Job

Hello, I’m in the process of migrating our code base to be at least compatible with PowerShell 7 (7.0.3 to be exact). Some of our Powershell code collects perfmon counters via logman and I’m running into an issue converting that to be PS7 friendly.

The code flow is:

[Start Counter Collection as a Job] --> [Do a task] --> [End Counter Collection] --> [Process Counters]

The problem is this:

We were using logman.exe to start/stop the task. This creates a BLG file that can be imported via Import-Counter. However, the Import-Counter cmdlet appears to have been deprecated in PS7. I wrote code to use Get-Counter to start the counter collection, but unfortunately, when you start/stop it via Start-Job scriptblock, the return data gets de-serialized and useless.

I’ve also tried using the PSCompatSession PSSession via Invoke-Command, but it also de-serialized the return data.

The only solutions I’ve read about involves writing the data to disk in CSV format and then importing the data back. However, that does not seem really efficient… especially when dealing with large data sets.

No code usually leads to delayed or no answers.

I wrote code to use Get-Counter to start the counter collection

This would be a good piece of code to share for us to get started helping you.

Apologies, here are some code sniplets

Attempt #1: Using Start-Job scriptblock for Get-Counter cmdlet
Result: $data return from Receive-Job is de-serialized and counter data can’t be used

$counterNames = "\Processor(*)\% Interrupt Time"
$SampleInterval = 1
$MaxSamples = 30

$CurrentJob = Start-Job -ScriptBlock {
    Param($counterNames, $SampleInterval, $MaxSamples)
    Get-Counter -Counter $counterNames -SampleInterval $SampleInterval -MaxSamples $MaxSamples
} -ArgumentList $counterNames, $SampleInterval, $MaxSamples

# Not the actual function name, but basically we do some processor intensive work here
Invoke-SomeWork 

$CurrentJob | Wait-Job -Timeout 60
$data = $CurrentJob | Receive-Job -Wait -AutoRemoveJob

 

Attempt #2: Using WinPSCompatSession to use Import-Counter cmdlet under PS7.
Result: $data return from Invoke-Command is de-serialized and counter data can’t be used

# Where $BlgFile is the path to the counter file, e.g. C:\Temp\MyCounter.blg

if ($Global:PSVersionTable.PSVersion.Major -ge 7) {
    Write-DebugTime "Detected PS7 or newer"
    $s = Get-PSSession -Name WinPSCompatSession
    $data = Invoke-Command -Session $s -ScriptBlock {
        Param($BlgFile)
        Import-Counter -Path $BlgFile
    } -ArgumentList $BlgFile
} else {
    $data = Import-Counter -Path $BlgFile
}

 

So if all you’re after is the counter data, couldn’t this work for you?

$counterNames = "\Processor(*)\% Interrupt Time"
$SampleInterval = 1
$MaxSamples = 30
 
$CurrentJob = Start-Job -ScriptBlock {
    Param($counterNames, $SampleInterval, $MaxSamples)
    Get-Counter -Counter $counterNames -SampleInterval $SampleInterval -MaxSamples $MaxSamples |
        Select-Object -ExpandProperty CounterSamples | Select -Property *
} -ArgumentList $counterNames, $SampleInterval, $MaxSamples
 
$CurrentJob | Wait-Job -Timeout 60
$data = $CurrentJob | Receive-Job -Wait -AutoRemoveJob

You’ll have all the properties that are normally available under CounterSamples

Path             : \\wks38\processor(_total)\% interrupt time
InstanceName     : _total
CookedValue      : 0.775770038028371
RawValue         : 41814414062
SecondValue      : 132490826317850285
MultipleCount    : 1
CounterType      : Timer100Ns
Timestamp        : 2020-11-05 2:43:51 PM
Timestamp100NSec : 132490610317850000
Status           : 0
DefaultScale     : 0
TimeBase         : 10000000
RunspaceId       : ad0dd9a2-32c0-4ecd-971f-db555f61f414

If you don’t want to see the runspaceid just change the Receive-Job line to

$data = $CurrentJob | Receive-Job -Wait -AutoRemoveJob | Select -Property * -ExcludeProperty RunspaceID

Thank you for the help.

I settled on keeping the CounterSamples ArrayList and ended up with this:

$CurrentJob = Start-Job -ScriptBlock {
    Param($counterNames, $SampleInterval, $MaxSamples)
    Get-Counter -Counter $counterNames -SampleInterval $SampleInterval -MaxSamples $MaxSamples | `
        Select-Object -Property *
} -ArgumentList $counterNames, $SampleInterval, $MaxSamples