2

I want to get the CPU and memory stats of a process and want to add it in the CSV at regular intervals. Following is the powershell script I wrote for that:

# Specify the process names
$process1Name = "abcd"
$csvFilePath = "C:\Users\johndoe\Stats.csv"
# Create an array to store process stats
$processStats = @()
# Loop to monitor processes every second
$exportIntervalInSeconds = 60  # Set the desired export interval
$iterationCount = 0
# Function to get CPU and Memory usage of a process
function Get-ProcessStats {
    param (
        [string]$processName
    )
    
    $process = Get-Process -Name $processName -ErrorAction SilentlyContinue
    
    if ($process -ne $null) {
        $cpuUsage = $process.CPU
        $memoryUsage = $process.WorkingSet64
        Write-Output "$processName - CPU Usage: $cpuUsage%, Memory Usage: $memoryUsage bytes"
        # Create an object with process stats
        $statsObject = [PSCustomObject]@{
            ProcessName = $processName
            CPUUsage = $cpuUsage
            MemoryUsage = $memoryUsage
        }
        Write-Output "Stats: $statsObject"
        # Add the stats object to the array
        $processStats += $statsObject       
        Write-Output "ProcessStats:"
        $processStats | Format-Table
        $processStats.Count
    } else {
        Write-Output "$processName not found."
    }
}

# Loop to monitor processes every second
while ($true) {
    Get-ProcessStats -processName $process1Name
    Start-Sleep -Seconds 1
    $iterationCount++
    # Clear-Host  # Optional: Clear the console for better readability
    if ($iterationCount -ge $exportIntervalInSeconds) {
        # Export the process stats to CSV
        Write-Output "Reached the iteration count greater than export interval"
        Write-Output "Exporting process stats to CSV:"
        $processStats | Format-Table
        Write-Output "Exporting..."
        $processStats | Export-Csv -Force -Path $csvFilePath -NoTypeInformation

        # Reset iteration count
        $iterationCount = 0
        $processStats = @()
    }
}

I dont see anything getting added in Stats.csv after regular intervals. In fact, the processStats does not contain anything. Interesting thing is the value of statsObject gets printed and the processStats value in the function Get-ProcessStats also gets printed but contains only 1 value at that moment. Not sure what is going wrong and why all the values are not getting printed and stored in CSV file.

1
  • Which debugger are you using? After hitting Start-Sleep, what is in the global array variable $processStats? Commented Feb 17, 2024 at 17:37

2 Answers 2

5
  • You cannot populate the $processStats array you've initialized in the script scope from a function, because functions run in a child scope of the caller (unless dot-sourced).

  • To get this to work, you'd have to reference the variable directly in the caller's scope, which in your case means: $script:processStats += $statsObject

  • However, from a code-maintenance perspective it is better not to modify variables across scope boundaries, and the PowerShell-idiomatic solution is to make the function output the object you want to add as an array element.


As for why your approach didn't work:

  • While your function can get the caller's $processStats variable (value), setting it creates a local variable of the same name that exists independently of the caller's variable, and goes out of scope when the function exits.
    Therefore, the caller doesn't see any modifications made to the (accidentally local) variable inside the function.[1]

  • This behavior is somewhat counter-intuitive, especially with operators such as +=, where the new, local variable value is created based on the original variable's value.

  • For a concise overview of scoping in PowerShell, see this answer.


[1] Note that it is situationally possible to modify an object that a variable from an ancestral scope references, but this is unrelated to variable scopes. Specifically, if an ancestral variable happens to (a) contain an instance of a .NET reference type and (b) that type happens to have methods that mutate the state of an instance.
An example would be to create a [System.Collections.Generic.List[object]] instance in your script scope, and then use its .Add() method from inside the function to add to it.
Even though arrays (instances of System.Array) are .NET reference types too, they are fixed-size, so this approach cannot work; what PowerShell's += operator does when "growing" arrays is to create a new array behind the scenes, formed from the original elements plus the one(s) being "appended". It is for this reason that using += with arrays is generally best avoided - see this answer.

Sign up to request clarification or add additional context in comments.

Comments

1

In addition to the variable scope, function output and array modification mentioned by @mklement0, I'd like to note these changes:

  • A usage of foreach instead of if for $processList to catch more than 1 value (process item) at that moment.
  • Write-Output was replaced with Write-Host to avoid sending debug messages to the pipeline.
  • Export was changed:
    • To alter the CSV file only when there was at least one process captured.
    • To -Append output to the file so it contains information from all iterations.
# Specify the process names
$process1Name = "abcd"
$csvFilePath = "C:\Users\johndoe\Stats.csv"
# Loop to monitor processes every second
$exportIntervalInSeconds = 60  # Set the desired export interval

# Function to get CPU and Memory usage of a process
function Get-ProcessStats {
    param (
        [string]$processName
    )
    
    $processList = Get-Process -Name $processName -ErrorAction SilentlyContinue
    
    if ($processList.Count -eq 0) {
        Write-Host "$processName not found."
        return
    }

    foreach ($process in $processList) {
        $cpuUsage = $process.CPU
        $memoryUsage = $process.WorkingSet64
        Write-Host "$processName - CPU Usage: $cpuUsage%, Memory Usage: $memoryUsage bytes"
        
        # Create an object with process stats
        # It will be added to the function return array
        [PSCustomObject]@{
            ProcessName = $processName
            CPUUsage = $cpuUsage
            MemoryUsage = $memoryUsage
        }
    }
}

# Loop to monitor processes every second
while ($true) {
    $iterationCount = 0
    $processStats = while ($iterationCount -lt $exportIntervalInSeconds) {
        # Output of this function will be added to the $processStats list
        Get-ProcessStats -processName $process1Name
        Start-Sleep -Seconds 1
        $iterationCount++
    }
    Write-Host "Reached the iteration count greater than export interval"
    
    # Export process stats if found
    if ($processStats.Count -gt 0) {
        Write-Host "Exporting process stats to CSV:"
        $processStats | Format-Table | Out-Host
        Write-Host "Exporting..."
        $processStats | Export-Csv -Force -Path $csvFilePath -NoTypeInformation -Append
    }
}

1 Comment

Great, thanks! I've edited the code to use Out-Host instead.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.