Get Memory (Private Working Set) of Process - c#

My problem is what i am getting for WorkingSet is very different than Task manager Memory (Private Working Set). I have tried various solutions written on NET but the values are far too away from matching. Kindly help me get Memory (Private Working Set) from task manager.
script += string.Format(#"$Processes = Get-Process -ComputerName {0} | Sort-Object WorkingSet -desc | Select-Object;", remoteMachineName);
script += #"$ProcessArray= #();";
script += #"foreach ($process in $Processes) {";
script += #"$ProcessName = $process.ProcessName;";
script += #"$ProcessSize = $process.WorkingSet/1KB;";
script += #"$objAverage = New-Object System.Object;";
script += #"$objAverage | Add-Member -type NoteProperty -name Name -value $ProcessName;";
script += #"$objAverage | Add-Member -type NoteProperty -name Memory -value $ProcessSize;";
script += #"$ProcessArray +=$objAverage; }; ";

What is shown in Process Manager as Memory (Private Working Set) is the value of the performance counter \Process\working Set - Private.
You can retrieve this value with:
$ProcessPrivateSet = Get-Counter "\Process(instancename)\Working Set - Private"
$WSPrivateKiloBytes = $ProcessPrivateSet.CounterSamples[0].CookedValue / 1KB
$WSPrivateKiloBytes is now the same value as what you see in Process Manager.
The problem with retrieving this value for a distinct process is that performance counters name process instances by process name + instance count, rather than by process ID.
So, if you launch 1 instance of a Java application, you may retrieve the counter for the java.exe process, like so:
Get-Counter "\Process(java)\Working Set - Private"
Now, if you launch another one, you'll need to reference that one like this:
Get-Counter "\Process(java#1)\Working Set - Private"
and so on and on.
You can change this behavior by setting the ProcessNameFormat for performance counter objects on the local system like this:
$RegPath = "HKLM:\SYSTEM\CurrentControlSet\Services\PerfProc\Performance\"
Set-ItemProperty $RegPath -Name ProcessNameFormat -Value 2 -Type DWord
A value of 2 means "include process ID in instance names", a value of 1 (default) would mean "use an instance counter" (like shown above).
The new format will be processname_id
After changing the ProcessNameFormat, you can now retrieve performance counters for a specific Process ID, like so:
$javap = Get-Process -Name java | Select -First 1
Get-Counter "\Process(java_$($javap.Id))\"
Since you now have a distinct correlation between Get-Process output and the performance counters, you can now retrieve the "Private Working Set" value for each process, with a single Select-Object statement using a calculated property:
Get-Process java | Select Name,Id,#{Name="WSPrivate(KB)";Expression = {(Get-Counter "\Process($($_.Name)_$($_.Id))\Working Set - Private").CounterSamples[0].CookedValue / 1KB}}
It will take some time to retrieve each individual counter sample, so if you plan on doing this often or for a large set of processes, you might want to use a wildcard (*) and retrieve \Process(*)\Working Set - Private and then look at the InstanceName in each entry in CounterSamples

Related

How to open SolidWorks sldprt files as read-only with PowerShell?

I built this open-file function in PowerShell for a GUI I wrote that lets you find and open various files on a server. I mainly use it for opening SolidWorks files as read-only, but also for PDF files and it should work for just about any other file if there is a file association for it.
The problem is that sometimes it doesn't work when opening the sldprt files. SolidWorks will either ignore the open file request or it wont open as read-only. I think this is mostly just a solidworks issue as sometimes it wont open files when double clicked on from windows explorer.
Anyway my solution is to set the file attribute to read-only. start a job that opens the file in SolidWorks, and then waits for the SolidWorks process to go idle before removing the read-only attribute. It does this through an event that watches for the job state to change. Since this is running through a GUI it has to be done in the background to prevent the GUI from locking up.
Is there a simpler way to open files as read-only with PowerShell?
I think it might be possible using the SolidWorks .dll files, but they are meant to be loaded in C# or VB-script and I have no idea what i'm doing in either of those languages.
function open-File{
param(
[parameter(Mandatory=$true)]$file,
[bool]$readOnly = $true,
$processName=$null
)
[scriptblock]$openFileScriptBlock = {
param(
$file,
$readOnly,
$processName=$null
)
#initiate variables
$loaded = $false
$file = get-item $file
$processLastCpu = 0
$timeout = 0
if ($readonly -and !$file.isReadOnly){
$file.isReadOnly = $true
#call file with default application
$attempts = 0
while ($true){
try{$startedProcess = start-process "$($file.fullname)" -PassThru; break}
catch{
$attempts++
if ($attempts -eq 3){return "cannot open file: $file, Error:$_"}
}
}
start-sleep -seconds 2
if ($processName){
$processName = $startedProcess.name
if ($processName -eq "SWSHEL~1"){$processName = "SLDWORKS"}
}
#wait until process shows up in the process manager
while ($loaded -eq $false -and $timeout -lt 25 ){
try {
$process = get-process -name $processName -erroraction 'stop'
if ($?){$loaded = $true; $timeout = 0} else {throw}
}catch{start-sleep -milliseconds 200; $timeout++}
}
start-sleep -seconds 2
#wait for process to go idle
while ($process.cpu -ne $processLastCpu -and $timeout -lt 10){
$processLastCpu = $process.cpu
start-sleep -milliseconds 500
$timeout++
}
$file.isreadonly = $false
} else {start-process "$($file.fullname)"}
return ,$file
}
if (!(test-path -path $file)){update-message "File not found: $file"; return}
$openFileJob = start-job -name 'openfile' -scriptblock $openFileScriptBlock -argumentlist $file, $readOnly, $processName
Register-ObjectEvent $OpenFileJob StateChanged -Action {
$jobResult = $sender | receive-job
$sender | remove-job -Force
unregister-event -sourceIdentifier $event.sourceIdentifier
remove-job -name $event.sourceIdentifier -force
try{update-message "opened file $($jobResult.name)"}
catch{update-message $jobResult}
} | out-null
}
I know its a old question, but i was wondering if you ever managed to get a solution?
If not, there is a few things you could try: first off, if your code is opening any other file just fine, it does not seem to be there the problem is.
File association with all SLD-files are working most of the time; but we do see it going bad from time to time (often related to updates), in that case, double-check that all SLD-file types are set to open with 'Solidworks-Launcher' (and not Solidworks directly).
Using the launcher, will ensure Solidworks does not try to open a file, into an already running instance of Solidworks.
Also, try to check the following: Solidworks Options -> Collaboration ->
'Enable Multi-user environments'... is this set?
whatever state it is in; try changing is to the opposite.
That checkmark is allowing multiple Solidworks-users to open the same file at the same time, and it does so by changing the read-state of the file, back and fourth.
(it could be it is interfering with your code)
Both of these things will be PC-specific, so if you change them on one machine, they might also need to be changed on other machines.

(Powershell & C#) ExchangeOnline Cmdlets partially working

The issue is a little awkward to explain but essentially I have a piece of C# code that connects & logs into ExchangeOnline via powershell (using System.Management.Automation.Powershell) from that I wish to run some more commands specific to the ExchangeOnline instance.
I've found that certain commands do not work such as "Get-Mailbox" or "Get-DistributionList". The error stream I have captured simply says the cmdlet does not exist. However if I try these commands in a powershell window it works.
The apparently problematic code I have running is pasted below (mix of both powershell and C#):
string FullTenantScript = #"
[string]$SizeOfAllMailboxes = ((get-exomailbox -ResultSize Unlimited | get-exomailboxstatistics).TotalItemSize.Value.ToMB() | measure-object -sum).sum
$TenantProperties = [pscustomobject]#{
'Primary Domain' = Get-AzureADDomain | Where-Object IsDefault -Match 'True' | Select -ExpandProperty Name
'Tenant ID' = Get-AzureADTenantDetail | select -ExpandProperty ObjectId
'Number of users' = #(Get-AzureADUser -all $true).count
'Number of Mailboxes' = #(get-exomailbox -ResultSize unlimited).Count
'Number of Devices' = #(Get-AzureADDevice -all $true).Count
'Number of Subscriptions' = #(Get-AzureADSubscribedSku).Count
'Number of Distribution Lists' = #(Get-DistributionGroup).Count
'Number of Dynamic DLs' = #(Get-DynamicDistributionGroup).Count
'Number of 365 Groups' = #(Get-UnifiedGroup).Count
'Size of all mailboxes' = $SizeOfAllMailboxes + ' MB'
}
$TenantProperties
";
PowerShell getTenantinfo = PowerShell.Create().AddScript(FullTenantScript);
Collection<PSObject> TenantInfoResult = getTenantinfo.Invoke();
I've read online that some of the older cmlets are being phased out (for example Get-Mailbox), so instead I use (get-exomailbox). I just don't get why I can run them in a powershell window but not in my program (as they should do essentially the same thing).
Please also note my program connects into Azure also (using Connect-AzureAD).
Any help or advice as to why this is happening would be greatly appreciated & any further info required I will provide.
Thanks,
Mouse

PowerShell Set Drive Labels Persisting And Unchangeable Until Reboot

Our software needs to map a network drive depending on which database the User logs in to.
The software first checks that the drive isn't already mapped, if it is then it skips the mapping step.
If the drive isn't mapped, or it is mapped to a different share (i.e. the User was previously logged in to a different database), then it clears any existing drive mapping, and maps the required drive.
It does this by generating and then running a PowerShell script.
Remove-SmbMapping -LocalPath "R:" -Force -UpdateProfile;
Remove-PSDrive -Name "R" -Force;
net use "R" /delete /y;
$password = ConvertTo-SecureString -String "Password" -AsPlainText -Force;
$credential = New-Object System.Management.Automation.PSCredential -ArgumentList "Username", $password;
New-PSDrive -Name "R" -PSProvider "FileSystem" -Root "\\server\share" -Credential $credential -Persist;
$a = New-Object -ComObject shell.application;
$a.NameSpace( "R:" ).self.name = "FriendlyName";
The first three lines remove any existing mapping on that drive letter. They all theoretically do the same thing, however thanks to Microsoft it's entirely random which line will actually work. It only consistently works if all three lines are run.
The middle three lines map the new drive.
The last two lines change the drive label of the new drive to something more user-friendly than the default \\server\share label
The first time someone logs in after a reboot the above script works perfectly. The new drive is mapped, and the label is changed.
However, if the User then logs out and logs into a different database, the label will not change.
For example, the User first logs in to 'Database A', and the drive is mapped with the label 'DatabaseAFiles'. All well and good.
But if the User then logs out, and logs in to 'Database B', the drive is correctly mapped and points to the correct share, but the label still says 'DatabaseAFiles' and not 'DatabaseBFiles'.
If the User reboots their PC, however, and logs in to 'Database B', then the label will correctly say 'DatabaseBFiles', but any subsequent log ins to other databases again won't change the label.
Reboot
Log in to Database A, label is DatabaseAFiles
Log out and into Database B, label is still DatabaseAFiles
Reboot
Log in to Database B, label is now DatabaseBFiles
This is not dependent on the last two script lines being present (the two that set the label), I actually added those to try to fix this issue. If those two lines are removed, the label is the default \\server\share label, and still doesn't change correctly, i.e.
Reboot
Log in to Database A, label is \\servera\sharea
Log out and into Database B, label is still \\servera\sharea
Reboot
Log in to Database B, label is now \\serverb\shareb
Regardless of the label, the drive is always correctly mapped to the correct share, and using it has all the correct directories and files.
Everything works correctly, it's just the label that is incorrect after the first login per reboot.
The script is run from within a C# program in a created PowerShell instance
using (PowerShell PowerShellInstance = PowerShell.Create())
{
PowerShellInstance.AddScript(script);
IAsyncResult result = PowerShellInstance.BeginInvoke();
while (result.IsCompleted == false)
{
Thread.Sleep(1000);
}
}
As it maps a drive, it cannot be run in Adminstrator mode (the drive won't be mapped for the actual User), it has to be run in normal mode, so there is a check earlier up for that.
If I take a copy of the script and run it in a PowerShell session outside the C# program, I get exactly the same results (everything works but the label is wrong after the first login), so it's not that it's being run from within the C# program.
It's entirely possible that the issue is with either File Explorer or with Windows, either caching the label somewhere and reusing it could be the problem, of course.
Anyone have any suggestions of things I can try please?
A time ago, I have had to rename file shares and therefor I wrote this function. Maybe this is helpful for you.
#--------------------------------------
function Rename-NetworkShare {
#--------------------------------------
param(
[string]$sharePattern,
[string]$value
)
$regPath = Get-ChildItem 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2'
$propertyName = '_LabelFromReg'
foreach( $child in $regPath ) {
if( $child.PSChildName -like ('*' + $sharePattern + '*') ) {
if( !$child.Property.Contains( $propertyName ) ) {
New-ItemProperty $child.PSPath -Name $propertyName -PropertyType String | Out-Null
}
Set-ItemProperty -Path $child.PSPath -Name $propertyName -Value $value | Out-Null
}
}
}
Rename-NetworkShare -sharePattern 'patternOldName' -value 'NewFriendlyName'
It's not ideal, there's one bit I'm not happy about, but this is the best I've been able to come up with so far. If I come up with something better I'll post that instead.
Firstly, I check if there is already a drive mapped to the letter I want to use:-
// Test if mapping already exists for this database
var wrongMapping = false;
var drives = DriveInfo.GetDrives();
foreach (var drive in drives)
{
var driveLetter = drive.RootDirectory.ToString().Substring(0, 1);
if (driveLetter == mappingDetails.DriveLetter && Directory.Exists(drive.Name))
{
wrongMapping = true; // Assume this is the wrong drive, if not we'll return from the method before it's used anyway
var unc = "Unknown";
using (RegistryKey key = Registry.CurrentUser.OpenSubKey("Network\\" + driveLetter))
{
if (key != null)
{
unc = key.GetValue("RemotePath").ToString();
}
}
if (unc == mappingDetails.Root)
{
View.Status = #"Drive already mapped to " + mappingDetails.DriveLetter + ":";
ASyncDelay(2000, () => View.Close());
return; // Already mapped, carry on with login
}
}
}
If we already have the correct path mapped to the correct drive letter, then we return and skip the rest of the mapping code.
If not, we'll have the variable wrongMapping, which will be true if we have a different path mapped to the drive letter we want. This means that we'll need to unmap that drive first.
This is done via a Powershell script run the by C# program, and contains the bit I'm not happy about:-
Remove-PSDrive mappingDetails.DriveLetter;
Remove-SmbMapping -LocalPath "mappingDetails.DriveLetter:" -Force -UpdateProfile;
Remove-PSDrive -Name "mappingDetails.DriveLetter" -Force;
net use mappingDetails.DriveLetter /delete /y;
Stop-Process -ProcessName explorer;
The first four lines are different ways to unmap a drive, and at least one of them will work. Which one does work seems to be random, but between all four the drives (so far) always get unmapped.
Then we get this bit:
Stop-Process -ProcessName explorer;
This will close and restart the Explorer process, thus forcing Windows to admit that the drive we just unmapped is really gone. Without this, Windows won't fully release the drive, and most annoyingly it will remember the drive label and apply it to the next drive mapped (thus making a mapping to CompanyBShare still say CompanyAShare).
However, in so doing it will close any open File Explorer windows, and also briefly blank the taskbar, which is not good.
But, given that currently no Company sites have more than one share, and it's only the Developers and Support that need to remove existing drives and map new ones, for now we'll put up with it.
Once any old drive is unmapped, we then carry on and map the new drive, which again is done via a PowerShell script run from the C# code.
$password = ConvertTo-SecureString -String "mappingDetails.Password" -AsPlainText -Force;
$credential = New-Object System.Management.Automation.PSCredential -ArgumentList "mappingDetails.Username", $password;
New-PSDrive -Name "mappingDetails.DriveLetter" -PSProvider "FileSystem" -Root "mappingDetails.Root" -Credential $credential -Persist;
$sh=New_Object -com Shell.Application;
$sh.NameSpace('mappingDetails.DriveLetter:').Self.Name = 'friendlyName';
New-Item –Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\" –Name "foldername";
Remove-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\foldername" -Name "_LabelFromReg";
New-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\foldername" -Name "_LabelFromReg" -Value "friendlyName" -PropertyType "String\";
The first part maps the drive:
$password = ConvertTo-SecureString -String "mappingDetails.Password" -AsPlainText -Force;
$credential = New-Object System.Management.Automation.PSCredential -ArgumentList "mappingDetails.Username", $password;
New-PSDrive -Name "mappingDetails.DriveLetter" -PSProvider "FileSystem" -Root "mappingDetails.Root" -Credential $credential -Persist;
The middle part changes the name directly:
$sh=New_Object -com Shell.Application;
$sh.NameSpace('mappingDetails.DriveLetter:').Self.Name = 'friendlyName';
And the end part changes the name in the Registry:
New-Item –Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\" –Name "foldername";
Remove-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\foldername" -Name "_LabelFromReg";
New-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\foldername" -Name "_LabelFromReg" -Value "friendlyName" -PropertyType "String\";
Firstly, it creates a key for this path (it the key already exists it'll fail but the script will carry on)
Then it removes the existing property _LabelFromReg (if it doesn't exist it'll fail but the script will carry on)
Then it (re)creates the property _LabelFromReg with the new friendlyname.
So, again doing the same thing two ways, but between the two it works.
I'd like to find some alternative to having to kill and restart the Explorer process, it's really tacky, but it seems to be the only way to get Windows to acknowledge the changes.
And at least I now get the correct labels on the drives when mapped.

WPF Progress Bar PowerShell

I have been trying to get a WPF progress bar in to my PowerShell script for awhile now, but i can not figure it out. I have tried probably 13 different ways but to no avail. Here is my code:
$WPFRunQuick_Button.Add_Click({
<# Add-Type -AssemblyName System.Windows.Forms
$Form.Text = "Processing"
$Label = New-Object System.Windows.Forms.Label
$Font = New-Object System.Drawing.Font("Times New Roman",16,[System.Drawing.FontStyle]::Bold)
$form.Font = $Font
$Form.Controls.Add($Label)
$Label.Text = "You have run the quick test, please be patient as it runs."
$Label.AutoSize = $True
$form.autosize = $true
$form.AutoScroll = $true
$form.autosizemode = "GrowAndShrink"
$Form.MinimizeBox = $False
$Form.MaximizeBox = $False
$Form.SizeGripStyle = "Hide"
$form.startposition = "CenterScreen"
$Form.Visible = $True
$Form.Update() #>
$max = 100
For($i = 1; $i -le $max; $i++){
Write-progress -Activity “Device Check” -Status “Testing, please wait.” `
-percentcomplete ($i / $max*100) -id 1
sleep 1
}
# Quick Test Run Function
Quick_Test_Run
# Prompt to send logs to associate support
$a = new-object -comobject wscript.shell
$intAnswer = $a.popup("Do you want to send these results?", 0,"Results",4)
if ($intAnswer -eq 6) {
$a.popup("Results Sent")
# Create email
$EmailSubject = "Tasa Test"
$EmailTo = "usupportlogs#test.com"
$EmailBody = [IO.File]::ReadAllText("C:\Temp\PMCS_TicketLogs.log")
# Send email to usupportlogs inbox
Send-MailMessage -From "PMCS_No_Reply#test.com" -To $EmailTo -subject $EmailSubject -Body $EmailBody -SmtpServer "SMTPRR.Cerner.Com"
} else {
$a.popup("Sending Cancelled")
}
$Form.Close()
})
When i try to put one in before i call the quick test function, it will load all the way then launch the test, and if i put it in after it will do the test and then launch the progress bar. I just want a simple progress bar for this script. Any help or insight would be appreciated, thanks!
In a script I created recently, my GUI application would check availability of a series of computers in our corporate estate by iterating through a user-submitted list of IP Addresses (a System.Windows.Forms.TextBox named in my script as $boxIpInput) and perform a Test-Connection conditional statement on each one. To allow the users to see what progress the scripts had made on the ping tests, I used a System.Windows.Forms.ProgressBar to communicate the progress of the script; I found this out by googling for "Powershell Forms Progress Bar" and happened upon this link.
To create the ProgressBar object, I used the following code;
$barPingProgress= New-Object System.Windows.Forms.ProgressBar
$barPingProgress.Size= New-Object System.Drawing.Size(274,20);
$barPingProgress.Location= New-Object System.Drawing.Size(8,440);
$barPingProgress.Style= 'Continuous'
$barPingProgress.Value= 0
$objForm.Controls.Add($barKioskPingProgress);
Upon clicking the $btnRunIpCheck Button to perform the ping test, I used the input from $boxIpInput and piped it into a ForEach-Object loop to incrementally increase the count of the progress bar in a similar way to your For loop before the Quick_Test_Run function (I have removed all irrelevant code from the below snippet for brevity);
//other button initialisation code (removed)
...
$btnRunIpCheck.Add_Click({
//some code to validate entries (removed)
...
$arrTmpIpAddresses= $boxIpInput.Text.Split(" `n",[System.StringSplitOptions]::RemoveEmptyEntries)
//some code to check size of list and warn users of the time it would take (removed)
...
$arrTmpIpAddresses | ForEach-Object {
$intOrderCount++
[int]$tmpProgNum= ($intOrderCount/$arrTmpIpAddresses.Count) * 100
$barPingProgress.Value= $tmpProgNum
//ping test code (removed)
}
}
}
Personally, I would recommend using the System.Windows.Forms.ProgressBar object over the Write-Progress method you're using at present; in the case of your code, you can either add the ProgressBar object as a part of the Add_Click event or have it created when you generate the main $Form object and then begin the Value incrementation during the Add_Click event.
To further help you, I'd need to understand what it is that you're counting the progress against - I can't really see what you're counting against (aside from the "Device Check" part that is) so I don't feel comfortable incorporating an example of how the ProgressBar Form would work in the context of your code. If you want me to help you further, you'll need to give me a little more detail. :)

How to handle Powershell Format-List output in C#

PowerShell psh = PowerShell.Create();
//psh.AddScript("Get-Service | Format-List -Property Name, Status");
psh.AddScript("Get-Service");
Collection<PSObject> result = psh.Invoke();
foreach (PSObject msg in result)
{
Console.WriteLine(msg.Properties["Name"].Value);
}
In above example, if I use "Get-Service" alone, I am able to get the name and status of the service on the system. But if I use the same with "Get-Service | Format-List -Property Name, Status" get an exception.
When you run a command in PowerShell, the results are typically returned as CLR objects. So, your Get-Service command returns an object of type ServiceController, which is why you can query the name and status.
When you pass the output to Format-List, that command converts the objects into a list of objects that are designed for display of information: if you examine the results of Format-List you will see that it's a mixed array containing mostly FormatEntryData objects. Knowing this, it's obvious that you can't find the Status property on the output of Format-List: you no longer have a service object!
You can see the difference by running these two snippets, which will display the types of the objects in your results:
Get-Service | % { $_.GetType().FullName }
Get-Service | Format-List | % { $_.GetType().FullName }

Categories