Help tweaking my parameter input

Hello folks,
I have this script that Im pretty happy with, but would like to make 2 changes, neither of which work when i try them.

I would like to be able to run the script passing in multiple computers on the command line, like so:
Get-MKProcesses -computer computer1, computer2, etc

As it is I can run it with various other params like these:
Get-MKProcesses (for local computer) -number 10
Get-MKProcesses -computer remotecomputer -number 2
Get-MKProcesses -number 6 -computer c:\temp\inputfiles\servers.txt

and they all work great. When I try and tweak for the comma separated list, I end up breaking one of those other input methods.

The other thing was , my starttime column shows multiple entries for some, anyway to only have 1 start time per process?

Any help would be great, thanks!

Here is the script:

<#
Get-MKProcesses
function to get X number of top processes by cpu%
usage:
Get-MKProcesses (for local computer) -number 10
Get-MKProcesses -computer remotecomputer -number 2
Get-MKProcesses -number 6 -computer c:\temp\inputfiles\servers.txt
#>
function Get-MKProcesses {
    param (
        $computer = $env:COMPUTERNAME,
        $number = 5
    )

    # Check if $computer is a file path
    if (Test-Path $computer -ErrorAction SilentlyContinue) {
        $computerNames = Get-Content $computer
    }
    else {
        $computerNames = @($computer)
    }

    foreach ($computerName in $computerNames) {
        $scriptblock = {
            param($localNumber)
            $i = 0
            
            # Get the top processes by CPU usage
            $topProcesses = Get-Process -IncludeUserName | Sort-Object CPU -Descending | Select-Object -First $localNumber
            # Get the total CPU usage
            $totalCPU = (Get-Counter '\Processor(_Total)\% Processor Time').CounterSamples.CookedValue
            # Get the hostname
            $hostname = $env:COMPUTERNAME

            # Output information for each process as PSCustomObject
            $processInfo = foreach ($process in $topProcesses) {
                $i++
                $processuser = $process.username
                $processName = $process.ProcessName
                $processid = $process.Id
                $processcputime = $process.cpu
                $cpuCounter = (Get-Counter "\Process($($processName))\% Processor Time").CounterSamples.CookedValue
                $cpuUsage = "{0:N2}%" -f $cpuCounter
                $path = if ($process.Path) { $process.Path } else { "N/A" }
                $memoryUsage = "{0:N2} MB" -f ($process.WorkingSet / 1MB)
                $processInfoInstance = Get-CimInstance -ClassName Win32_Process -Filter "Name='$processName.exe'"
                $startTime = $processInfoInstance.CreationDate
             

                [PSCustomObject]@{
                    Number         = $i
                    ProcessName    = $processName
                    Path           = $path
                    PID            = $processid
                    CPUUsage       = $cpuUsage
                    MemoryUsage    = $memoryUsage
                    ProcessCPUTime = $processcputime
                    ProcessUser    = $processuser
                    Hostname       = $hostname
                    StartTime      = $startTime
                } 
            }         
            # Output the process information
            $processInfo | Format-Table -AutoSize
            Write-Host "Total CPU usage $totalCPU`r`n" -NoNewLine -ForegroundColor Yellow
            Write-Host "ProcessCPUTime shows CPU time consumed by the process in seconds" -ForegroundColor Red
        }

        if ($computerName -eq $env:COMPUTERNAME) {
            # Run the script block locally
            . $scriptblock -localNumber $number
        }
        else {
            # Run the script block on a remote computer
            Invoke-Command -ComputerName $computerName -ScriptBlock $scriptblock -ArgumentList $number
        }
    }
}

And the output:

 get-mkProcesses

Number ProcessName                Path                                                                                       PID CPUUsage MemoryUsage ProcessCPUTime ProcessUser          Hostname StartTime
------ -----------                ----                                                                                       --- -------- ----------- -------------- -----------          -------- ---------
     1 System                     N/A                                                                                          4 10.82%   5.10 MB            9242.73                      MK-1ST
     2 msedge                     C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe                             44940 3.08%    193.02 MB          8265.14 MK-1ST\Matt          MK-1ST   {11/16/2023 6:59:59 AM, 11/16/2023 6:59:59…
     3 CefSharp.BrowserSubprocess C:\Program Files (x86)\Razer\Razer Services\Razer Central\CefSharp.BrowserSubprocess.exe 22472 0.00%    102.21 MB          6989.70 MK-1ST\Matt          MK-1ST   {11/15/2023 7:24:20 AM, 11/15/2023 7:24:20…
     4 dwm                        C:\WINDOWS\system32\dwm.exe                                                               1516 0.00%    95.37 MB           6587.55 Window Manager\DWM-1 MK-1ST   11/15/2023 7:06:22 AM
     5 EADesktop                  C:\Program Files\Electronic Arts\EA Desktop\EA Desktop\EADesktop.exe                     20568 3.10%    185.13 MB          6392.92 MK-1ST\Matt          MK-1ST   11/15/2023 7:24:21 AM

I am curious. What have you tried to make it work like you want it to? :thinking:

Have you tried calling your script in a loop feeding it your list of computernames? Like so …

'Computer1','Computer2','Computer3' |
    Foreach-Object {
        Get-MKProcesses -number 6 -computer $_
    }

Let’s start with reading the help …

The paragraph about static parameters already shows how to make a parameter accepting arrays of string values. :wink:

In my opinion that’s a very bad approach since it would be unexpected for a parameter named -Computer to accept a file path. And I would use the parameter name ComputerName instead as this is very common. You could use -Computer as an alias if you like or need for backwards compatibility.

If you have multiple processes you also have multiple start times … that’s logical, isn’t it? :thinking:
You will have to come up with an idea how you like to handle multiple processes with the same name. Especially for browsers that’s a very common thing to launch multiple processes even when you just started them and didn’t even open more than one tab. :man_shrugging:t3:

Thanks Olaf. I didnt really think about the -computer taking a path not making sense lol, and turns out changing that bit solves my problem with the string input.

<#
Get-MKProcesses
function to get X number of top processes by cpu%
usage:
Get-MKProcesses (for local computer) -number 10
Get-MKProcesses -computer remotecomputer1,remotecomputer2 -number 2
Get-MKProcesses -number 6 -computer c:\temp\inputfiles\servers.txt
#>
function Get-MKProcesses {
    param (
        [string[]]$computer = $env:COMPUTERNAME,
        $number = 5,
		$path
    )

    # Check if $computer is a file path
    if (Test-Path $path -ErrorAction SilentlyContinue) {
        $computernames = Get-Content $path
    }
    else {
        $computernames = @($computer)
    }

    foreach ($computername in $computernames) {
        $scriptblock = {
            param($localNumber)
            $i = 0
            
            # Get the top processes by CPU usage
            $topProcesses = Get-Process -IncludeUserName | Sort-Object CPU -Descending | Select-Object -First $localNumber
            # Get the total CPU usage
            $totalCPU = (Get-Counter '\Processor(_Total)\% Processor Time').CounterSamples.CookedValue
            # Get the hostname
            $hostname = $env:COMPUTERNAME

            # Output information for each process as PSCustomObject
            $processInfo = foreach ($process in $topProcesses) {
				$processInfoInstance = Get-CimInstance -ClassName Win32_Process -Filter "Name='$processName.exe'"
                $startTime = $processInfoInstance.CreationDate
                $i++
				$processuser = $process.username
                $processName = $process.ProcessName
                $numProcesses = (Get-Process -Name $processName).Count
				$processid = $process.Id
                $processcputime = $process.cpu
                $cpuCounter = (Get-Counter "\Process($($processName))\% Processor Time").CounterSamples.CookedValue
                $cpuUsage = "{0:N2}%" -f $cpuCounter
                $path = if ($process.Path) { $process.Path } else { "N/A" }
                $memoryUsage = "{0:N2} MB" -f ($process.WorkingSet / 1MB)
                								
                [PSCustomObject]@{
                    Number         = $i
                    ProcessName    = $processName
                    Path           = $path
                    PID            = $processid
                    CPUUsage       = $cpuUsage
                    MemoryUsage    = $memoryUsage
                    ProcessCPUTime = $processcputime
                    ProcessUser    = $processuser
                    Processes      = $numProcesses
                    StartTime      = $startTime
                } 
            }         
            # Output the process information
            $processInfo | Format-Table -AutoSize
            Write-Host "Total CPU usage $totalCPU`r`n" -NoNewLine -ForegroundColor Yellow
            Write-Host "ProcessCPUTime shows CPU time consumed by the process in seconds" -ForegroundColor Red
        }

        if ($computername -eq $env:COMPUTERNAME) {
            # Run the script block locally
            . $scriptblock -localNumber $number
        }
        else {
            # Run the script block on a remote computer
            Invoke-Command -ComputerName $computername -ScriptBlock $scriptblock -ArgumentList $number
        }
    }
}

I also took your advice and changed $computer to $computername. here is the final version:

<#
Get-MKProcesses
function to get X number of top processes by cpu%
usage:
Get-MKProcesses (for local computer) -number 10
Get-MKProcesses -computer remotecomputer1,remotecomputer2 -number 2
Get-MKProcesses -number 6 -filepath c:\temp\inputfiles\servers.txt
#>
function Get-MKProcesses {
    param (
        [string[]]$computername = $env:COMPUTERNAME,
        $number = 5,
        $filepath
    )

    # Check if $computer is a file path
    if (Test-Path $filepath -ErrorAction SilentlyContinue) {
        $computernames = Get-Content $filepath
    }
    else {
        $computernames = @($computername)
    }

    foreach ($computer in $computernames) {
        $scriptblock = {
            param($localNumber)
            $i = 0
            
            # Get the top processes by CPU usage
            $topProcesses = Get-Process -IncludeUserName | Sort-Object CPU -Descending | Select-Object -First $localNumber
            # Get the total CPU usage
            $totalCPU = (Get-Counter '\Processor(_Total)\% Processor Time').CounterSamples.CookedValue
            # Get the hostname
            $hostname = $env:COMPUTERNAME

            # Output information for each process as PSCustomObject
            $processInfo = foreach ($process in $topProcesses) {
                $processInfoInstance = Get-CimInstance -ClassName Win32_Process -Filter "Name='$processName.exe'"
                $startTime = $processInfoInstance.CreationDate
                $i++
                $processuser = $process.username
                $processName = $process.ProcessName
                $numProcesses = (Get-Process -Name $processName).Count
                $processid = $process.Id
                $processcputime = $process.cpu
                $cpuCounter = (Get-Counter "\Process($($processName))\% Processor Time").CounterSamples.CookedValue
                $cpuUsage = "{0:N2}%" -f $cpuCounter
                $path = if ($process.Path) { $process.Path } else { "N/A" }
                $memoryUsage = "{0:N2} MB" -f ($process.WorkingSet / 1MB)
                								
                [PSCustomObject]@{
                    Number         = $i
                    ProcessName    = $processName
                    Path           = $path
                    PID            = $processid
                    CPUUsage       = $cpuUsage
                    MemoryUsage    = $memoryUsage
                    ProcessCPUTime = $processcputime
                    ProcessUser    = $processuser
                    Processes      = $numProcesses
                    StartTime      = $startTime
                } 
            }         
            # Output the process information
            $processInfo | Format-Table -AutoSize
            Write-Host "Total CPU usage $totalCPU`r`n" -NoNewLine -ForegroundColor Yellow
            Write-Host "ProcessCPUTime shows CPU time consumed by the process in seconds" -ForegroundColor Red
        }

        if ($computer -eq $env:COMPUTERNAME) {
            # Run the script block locally
            . $scriptblock -localNumber $number
        }
        else {
            # Run the script block on a remote computer
            Invoke-Command -ComputerName $computer -ScriptBlock $scriptblock -ArgumentList $number
        }
    }
}

What if a user of your script/function provides both - a computername or a list of computer names AND a file path to an input file with computernames? :thinking: :smirk: :stuck_out_tongue_winking_eye: :point_up:t3:

hmm good question! as it is it fails by only returning the -filepath params it seems?

I was thinking of something like this:

param(
    [Parameter(ParameterSetName="Computer")]
    [string[]]$computername = $env:COMPUTERNAME,

    [Parameter(ParameterSetName="FilePath")]
    $filepath,

    [Parameter(ParameterSetName="Computer")]
    [Parameter(ParameterSetName="FilePath")]
    [switch]$number = 5
)

but its not exactly working.

Any thoughts on how to handle that?

Thank You.

maybe something like this:

# Check if both $computername and $filepath are provided
    if ($computername -and $filepath) {
        Write-Error "Please choose either a remote computer or a file as input, not both."
        return
    }

I would use parameter sets to keep exclusive parameters from conflicting

1 Like

… a good start …

What exactly do you mean by this? :thinking:

To be honest - since you already have a way to prvide a list of computers to the function I would drop the filepath parameter completely. Instead I would make the parameter -ComputerNamepipeline aware” using ValueFromPipeline or even ValueFromPipelineByPropertyName. This way you could easily do something like this:

Get-Content -Path '<Path\To\Your\Input\File.txt>' | 
    Get-MKProcesses 
1 Like