Public/Get-ProcessByMemory.ps1

function Get-ProcessByMemory {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string[]]$ComputerName,

        [Parameter(Mandatory = $false, Position = 1)]
        [string]$ExportCsvPath,

        [Parameter(Mandatory = $false)]
        [double]$MinPctThreshold = 0.0,

        [Parameter(Mandatory = $false)]
        [int]$MaxThreads = 10
    )

    begin {
        $allComputers = [System.Collections.Generic.List[string]]::new()
    }

    process {
        foreach ($computer in $ComputerName) {
            $allComputers.Add($computer)
        }
    }

    end {
        Write-Verbose "Initializing Multi-Threaded Runspace Pool with a maximum of $MaxThreads threads..."

        # 1. Define the code that each thread will execute
        $scriptBlock = {
            param($targetComputer, $threshold)
            try {
                # Note: CIM sessions are used here to query remote machines over standard WSMAN protocol
                $sessionParams = @{ ComputerName = $targetComputer; ErrorAction = 'Stop' }
                
                $os = Get-CimInstance Win32_OperatingSystem -Property TotalVisibleMemorySize, FreePhysicalMemory @sessionParams
                $totalMemoryBytes = $os.TotalVisibleMemorySize * 1KB
                $inUseMemoryBytes = ($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) * 1KB
                
                $grouped = Get-CimInstance Win32_Process -Filter "Name <> 'System Idle Process'" @sessionParams | 
                    Group-Object -Property Name

                $results = foreach ($item in $grouped) {
                    $stat = $item.Group | Measure-Object -Property WorkingSetSize -Sum
                    $pct = if ($inUseMemoryBytes -gt 0) { [math]::Round(($stat.Sum / $inUseMemoryBytes) * 100, 2) } else { 0 }

                    if ($pct -ge $threshold) {
                        [pscustomobject]@{
                            ComputerName  = $targetComputer
                            ProcessName   = $item.Name
                            InstanceCount = $item.Count
                            TotalMemoryGB = [math]::Round($totalMemoryBytes / 1GB, 2)
                            WorkingSetGB  = [math]::Round($stat.Sum / 1GB, 2)
                            PctUsedMemory = $pct
                        }
                    }
                }
                # Return the top 10 for this machine
                $results | Sort-Object -Property PctUsedMemory -Descending | Select-Object -First 10
            }
            catch {
                Write-Error "Thread failed for $targetComputer. Error: $_"
            }
        }

        # 2. Setup the Runspace Pool
        $runspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads)
        $runspacePool.Open()

        # 3. Create tracking objects for our running threads
        $threads = [System.Collections.Generic.List[pscustomobject]]::new()
        $finalOutput = [System.Collections.Generic.List[pscustomobject]]::new()

        # 4. Launch a thread for every computer in parallel
        foreach ($computer in $allComputers) {
            $psInstance = [powershell]::Create().AddScript($scriptBlock).AddArgument($computer).AddArgument($MinPctThreshold)
            $psInstance.RunspacePool = $runspacePool
            
            # BeginInvoke starts the thread asynchronously
            $handle = $psInstance.BeginInvoke()
            
            $threads.Add([pscustomobject]@{
                Instance = $psInstance
                Handle   = $handle
                Computer = $computer
            })
        }

        Write-Verbose "All threads spun up. Collecting data as they complete..."

        # 5. Monitor and collect results from the threads
        while ($threads.Count -gt 0) {
            $completedThreads = [System.Collections.Generic.List[pscustomobject]]::new()

            foreach ($thread in $threads) {
                if ($thread.Handle.IsCompleted) {
                    # Extract the collected custom objects from the thread
                    $data = $thread.Instance.EndInvoke($thread.Handle)
                    if ($data) { $finalOutput.AddRange($data) }
                    
                    # Clean up the system memory for this thread
                    $thread.Instance.Dispose()
                    $completedThreads.Add($thread)
                }
            }

            # Remove completed tasks from active tracking loop
            foreach ($completed in $completedThreads) {
                [void]$threads.Remove($completed)
            }

            # Tiny pause to prevent CPU spiking while waiting
            Start-Sleep -Milliseconds 100
        }

        # 6. Tear down the pool safely
        $runspacePool.Close()
        $runspacePool.Dispose()

        # 7. Output processing logic
        if ($PSBoundParameters.ContainsKey('ExportCsvPath') -and $finalOutput.Count -gt 0) {
            $finalOutput | Export-Csv -Path $ExportCsvPath -NoTypeInformation -Encoding utf8 -Append
            Write-Verbose "Thread results successfully merged and exported to $ExportCsvPath"
        } else {
            $finalOutput
        }
    }
}