PowerShell script to calculate the memory usage per user - powershell

We have an environment of 240 VMs. Clients are using ICA/RDP connection to connect to these servers. Sometimes users are hogging the memory and causing the slowing and crash on that particular server.
I would like to have a PowerShell script to calculate the memory usage for each user connected to the server. I spent hours and hours searching and trying different scripts but was not successful.
Some scripts giving me the working sets value using Get-WmiObject Win32_Process and GetOwner(). but the calculation is not correct.
What I need is exactly the format that I can see in the users tab in Task Manager. The main information which I need is the memory usage, but it would be nice to have the disk and CPU usage per user as well.
Here is the code which i am using. When i run this script after a minute or two It returns an error which says $.GetOwner() can not be found and in another line it gives me the user name which utilizing the memory more than the others but the calculation is not correct when i compare it with TaskManager user's tab.
$h = #{}
get-wmiobject win32_process | foreach {
$u = $_.getowner().user;
if ( $u -ne $null)
{
if ( !$h.ContainsKey($u) )
{
$h.add( $u, $_.WS);
}
else
{
$h.item($u) = $h.item($u) + $_.WS;
}
}
}
$h.GetEnumerator() | sort value -desc

try this:
get-wmiobject win32_process |
select #{N='User';E={$_.getowner().user}}, WorkingSetSize |
group user |
select Name, #{N='CPU';E={($_.Group.WorkingSetSize | Measure-Object -Sum).Sum / 1Mb }}

gwmi win32_process |
select #{N='User';E={$_.getowner().user}},WorkingSetSize |
group User | select Name,#{N='RAM';E={[math]::Round(($_.Group.WorkingSetSize | Measure-Object -Sum).Sum/1MB) }} |
sort RAM -Descending | select -first 1 ;

Related

Get the Last Logon Time for Each User Profile

I need to get the last logon time for each user profile on a remote computer (not the local accounts). I've tried something similar to the following using Win32_UserProfile & LastLogonTime, however this is not giving accurate results. For example, one this computer, only 1 account has been used in the past year, however LastUpdateTime is showing very recent dates. Some accounts have not even been logged into and should say "N/A", but it doesn't.
$RemoteSB_UserADID = Get-WmiObject win32_userprofile -Property * | Where-Object {$_.LocalPath -like "*users*"} | Sort-Object $_.LastUseTime | ForEach-Object{
$Parts = $_.LocalPath.Split("\")
$ADID = $Parts[$Parts.Length - 1]
if ($ADID -ne "SPECIALPURPOSEACCOUNT1" -and $ADID -ne "SPECIALPURPOSEACCOUNT2"){
$Time = $null
try{
$Time = $_.ConvertToDateTime($_.LastUseTime)
}catch{
$Time = "N/A"
}
"[$ADID | $Time]"
}
}
Example Output
[Acct1 | 03/13/2022 07:18:19]
[Acct2 | 03/15/202214:59:16]
[Acct3 | 03/13/2022 07:18:19]
[Acct4 | 03/16/2022 11:53:17] <--- only "active" account
How can I go about retrieving accurate (or decently accurate) login times for each user profile? Thanks!
It would help to know for what reason you need that, so that I know how to find a (better) solution for you.
If you need to cleanup your profiles not used for a long time at the target system, then take the last changed date of "ntuser.dat". That is the last logon if you define logon like logging on to a new session. If the user was logged on and simply locked the computer or used standby and then relogs then this date won't change.
Use this to get this date from all users you have access to but possibly not getting real user names
Get-ChildItem \\REMOTECOMPUTERNAMEHERE\Users\*\ntuser.dat -Attributes Hidden,Archive | Select #{Name="NameByFolder";Expression={($_.DirectoryName -split "\\")[-1]}},LastWriteTime
Or this a bit more complex version
Invoke-Command -ComputerName REMOTECOMPUTERNAMEHERE -ScriptBlock {$UsersWithProfilePath = #{}
dir "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" |
where {$_.name -like "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-21-*"} |
foreach {$UsersWithProfilePath[([System.Security.Principal.SecurityIdentifier]$_.name.split("\")[-1]).Translate( [System.Security.Principal.NTAccount]).Value] = $_.GetValue("ProfileImagePath")}
foreach ($Name in $UsersWithProfilePath.Keys) {#{$Name =(dir (join-path $UsersWithProfilePath.$Name ntuser.dat) -Attributes Hidden,Archive,System).LastWriteTime}}}
Depending on what you need you need to change it a bit.
Sorry for the long codelines... it is late here.

How can I use powershell to group process names and show the sum of memory used

I am trying to wrap my head around combining powershell options in order to produce a simple table of the top 10 memory users on my system (server, pc, etc). My PC is Windows 7 with no timeline in site for upgrade to Windows 10 due to Covid 19. I cannot add applications to my work PC that has not gone through a vetting process (read, it takes forever) so most of the time I create my own.
I would like to produce a result that looks something like this:
Count Name Memory Sum in MB
10 Firefox 5000
3 javaw 1000
The order I would like to be able to select by changing a property in the powershell options. So for example, sort by count, name or memory. My sample table is not set in stone.
I have come across the following 2 pieces of powershell and have been trying to adapt them but get errors.
(Get-Process | Measure-Object WorkingSet -sum).sum /1gb
Get-Process | Group-Object -Property Name -NoElement | Where-Object {$_.Count -gt 1}
For sake of learning, I don't mind seeing an "ugly" version and an optimized version.
You can use this:
$proc=ps|select -eXp name;$proc2=#()
$proc|%{
if(!("$($_)" -in $proc2)){$proc2+="$($_)"
$mem=0;ps $_|select -eXp workingSet|%{$mem+=$_/1MB}
[pscustomobject][ordered]#{
'Count'=(ps $_ -ea silentlyContinue).Count
'Name'=$_
'Memory in MB'=$mem
}}}
The PSCustomObject accelerator was introduced in PowerShell v3 so I don't know if the the output looks like a table in Windows 7 however the following pipeline returns desired properties even in PowerShell v2:
Get-Process |
Group-Object -Property Name -NoElement |
Where-Object { $_.Count -gt 1 } |
ForEach-Object {
[PSCustomObject]#{
Count= $_.Count
Name = $_.Name
'Memory Sum in MB' = [math]::Round(( Get-Process -Name $_.Name |
Measure-Object WorkingSet -sum).sum /1Mb, 3)
}
} # | Sort-Object -Property 'Memory Sum in MB'

Powershell Process CPU checking

Have the following which works OK, but with an in issue in PowerShell:
$FileName = "E:\Work\ps\Inventory.htm"
$serverlist = "E:\Work\ps\Monitored_computers.txt"
foreach ($server in Get-Content $serverlist)
{
$servern=$server.split(",")[0]
$ip=$server.split(",")[1]
$cpu = gwmi Win32_PerfFormattedData_PerfProc_Process -Computer $servern -filter "Name <> '_Total' and Name <> 'Idle'" | Sort-Object PercentProcessorTime -Descending | where { $_.PercentProcessorTime -gt 0 }| select -First 1
if ($cpu.PercentProcessorTime -ge "92") {
write-host $servern ' ' $cpu.Name ' ' $cpu.PercentProcessorTime
}
}
I have seen some other code in PowerShell, that takes an Average but almost seems like an "average of an average" - which is meaningless. And, this is for overall CPU Usage
gwmi win32_processor | Measure-Object -property LoadPercentage -Average | Foreach {$_.Average}
Now, if we can take the same logic and apply for our process issue:
gwmi Win32_PerfFormattedData_PerfProc_Process | Sort-Object PercentProcessorTime -Descending | where { $_.PercentProcessorTime -gt 0 } | select -First 1 | Measure-Object -property PercentProcessorTime -Average | Foreach {$_.PercentProcessorTime}
What am trying to ask is: I do get the CPU Percentage, which seems to be a "point in time". How do locate the true CPU Percentage? This is why I am pointing out the average. I really want to get around the "point in time" part of this.
The point being, when we have seen on several occasions, a high CPU per process on a server, we login to the server and the high CPU has subsided. This is not to say, this has been each time, but we know that sometimes a CPU will spike and then quiet down.
Thanks for any insight!
First issue, you are stuck at a Point In Time because when you execute your script it captures a snapshot of what is happening right then and there. What you are looking for is historical data, so you can figure out the average CPU usage of processes over a set amount of time, and pinpoint the process that's bogging down your CPU. Do you have performance monitors in place to track CPU usage for individual processes? You may need to setup performance logging if you want to be able to get the numbers you're looking for after the fact.
Secondly, I think that you misunderstand how Measure-Object works. If you run Get-Help on the cmdlet and check the Output you'll see that it outputs a GenericMeasureInfo object. This object will have a property for the relevant stat that you are looking for, which in your case is the Average property. It is not an average of an average, the most common usage I see for it is to calculate something, like a Sum or Average, and then output the value of that property.
Let's try a simple example...
Find the average size of the files in a folder. First we use Get-ChildItem to get a collection of files, and pipe it to Measure-Object. We will specify the -Average argument to specify that we want the Average calculated, and -Property length, so that it knows what to average:
GCI C:\Temp\* -file | Measure-Object -Average -Property length
This outputs a GenericMeasureInfo object like this:
Count : 30
Average : 55453155
Sum :
Maximum :
Minimum :
Property : Length
That lets me know that it had 30 files piped to it, and it found the Average for the Length property. Now, sometime you want to calculate more than one thing, so you can use more than one argument, such as -Sum and -Maximum, and those values will be populated as well:
Count : 30
Average : 55453155
Sum : 1663594650
Maximum : 965376000
Minimum :
Property : Length
So it looks like my average file is ~55MB, but out of the 1.6GB in the whole folder I've got one file that's 965MB! That file is undoubtedly skewing my numbers. With that output I could find folders that have multiple files, but one file is taking up over half of the space for the folder, and find anomalies... such as the ISO that I have saved to my C:\temp folder for some reason. Looks like I need to do some file maintenance.
Thanks to #TheMadTechnician I have been able to sort this out. I had a wrong component with
$_.Average
where I had
$_.PercentProcessorTime
and that would never work. Here is the correct script:
$serverlist = "D:\Work\ps\Monitored_computers.txt"
foreach ($server in Get-Content $serverlist) {
$servern=$server.split(",")[0]
$ip=$server.split(",")[1]
$cpu = gwmi Win32_PerfFormattedData_PerfProc_Process -Computer $ip | `
Where-Object {$_.Name -like "*tomcat*"} | `
Measure-Object -property PercentProcessorTime -Average | `
Foreach {$_.Average}
if ($cpu -ge "20") {
write-host $servern $cpu ' has a tomcat process greater than 20'
}
}

Using PoSH to get a value, strange results

I am using PoSH to query our Exchange server to return the largest mailbox by users.
My PoSH is:
$test = Get-MailboxDatabase MBX_* -Status | select Name,#{Name="NumberofUsers";Expression={(Get-Mailbox -resultsize unlimited -Database $_.name).Count}} | Sort -Property NumberofUsers | Select Name -First 1
This works in console and returns:
Name
----
MBX_2
However when I run it like this in a script and return the value, it is like this:
$test = Get-MailboxDatabase -Status MBX_*| select Name,#{Name="NumberofUsers";Expression={(Get-Mailbox -resultsize unlimited -Database $_.name).Count}} | Sort -Property NumberofUsers | Select Name -First 1
Write-Host "Using $test"
Using #<Name=MBX_2>
Why is it including the #<Name=> characters and how can I prevent it from doing that? I need to be able to grab just the returned value of "MBX_2" for the next part of my script and I am confused on how to go about this...
You are selecting a single property of the object, but still passing an object. What you need to do is use the -ExpandProperty parameter of the Select-Object cmdlet. Change your Select command to this:
| Select -ExpandProperty Name -First 1
That should give you the results that you desire.

Mount Point comparison

I have a PowerShell function that pulls the free space on a mounted volume. Ideally I would like to take the results and do a If comparison on the value.
If the results equal true I would write that to a text file. This will allow me to use REGEX through a monitoring tool to create a trouble ticket.
I am having issues doing any comparisons on the results. Here is a snip of the code.
$FreePerc = #{name="Free(%)";expression={[Math]::Round(((($_.FreeSpace / 1073741824)/($_.Capacity / 1073741824)) * 100),0)}}
function Get-Mountpoints {
$volumes = Get-WmiObject -Computer localhost Win32_Volume |
Where-Object {$_.DriveLetter -eq $null}
$volumes | Select $FreePerc | Write-Output
}
The Get-Mountpoints function returns the correct results but I have been unable to run any comparison on it.