Powershell - monitoring new processes with ExecutionPath and CommandLine - powershell

I wonder if you can assist me with a script (as per title)
So I've been playing around with the code below:
Register-CimIndicationEvent -ClassName Win32_ProcessStartTrace -SourceIdentifier "ProcessStarted"
The output with Get-Event returns what I need:
Get-Event | select timegenerated, #{N='ProcessName'; E = {$_.sourceeventargs.newevent.processname}}
TimeGenerated ProcessName
------------- -----------
21-Feb-20 12:58:29 PM UpdateTrustedSites.exe
21-Feb-20 12:58:31 PM backgroundTaskHost.exe
21-Feb-20 12:58:33 PM pwrgate.exe
21-Feb-20 12:58:33 PM chrome.exe
But I have no idea how to join it with win32_Process (Get-WMIObject win32_Process).CommandLine and .ExecutablePath)
Any help would be appreciated.

Register-CimIndicationEvent
The Register-CimIndicationEvent cmdlet subscribes to indications using an indication class name or a query expression. Use the SourceIdentifier parameter give a name to the subscription
And the SourceIdentifier parameter you are using is Win32_ProcessStartTrace.
Win32_ProcessStartTrace
It only has the following properties you can access
[AMENDMENT]
class Win32_ProcessStartTrace : Win32_ProcessTrace
{
uint8 SECURITY_DESCRIPTOR[];
uint64 TIME_CREATED;
uint32 ProcessID;
uint32 ParentProcessID;
uint8 Sid[];
string ProcessName;
uint32 SessionID;
};
That said, if you want to look up the Command and the Path for the process, you will have to look up the process information separately for each process.
foreach($event in Get-Event) {
$TimeGen = $event.timegenerated
$ProcessName = $event.sourceeventargs.newevent.processname
$Process = Get-WmiObject Win32_Process -Filter "Name LIKE '$ProcessName'" | select -First 1
$ProcessCMD = ($Process | select CommandLine).CommandLine
$processPath = ($Process | select ExecutablePath).ExecutablePath
$out = [pscustomobject]#{
Time=$TimeGen
Name=$ProcessName
Path=$processPath
Command=$ProcessCMD
}
$out
}
You can combine the $out paramter by use of an array or hashtable and query that further as you need.

Related

How to exclude part of an output in Powershell?

i am writing a script that accepts the device ID as an argument to check the used percentage of a disk. Here is my code.
$device_id = $args[0]
Get-WmiObject -Class Win32_LogicalDisk |
Select-Object -Property DeviceID,
#{label='UsedPercentage'; expression={[Math]::Round((($_.Size - $_.FreeSpace)/$_.Size) * 100, 2)}} |
findstr $device_id
Here is my output. i am passing an argument to see usage of the device by device ID.
PS D:\Development\Powershell> .\disk-usage.ps1 D:
D: 57.69
What i want to do is to just output that number. How do i do this?
There's no need to use findstr to filter the output. Instead, use the parameter argument to filter your WMI query:
$device_id = $args[0]
# use argument to filter WMI query
Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceID = '$device_id'" |ForEach-Object {
# output the free space calculation, nothing else
[Math]::Round((($_.Size - $_.FreeSpace)/$_.Size) * 100, 2)
}
You can add the 'used percentage' as a property to the WMI object you get back from your query:
$deviceID = args[0]
$diskUsage = Get-WmiObject -Query "SELECT FreeSpace, Size FROM Win32_LogicalDisk WHERE DeviceID = '$deviceID'" |
Add-Member -MemberType ScriptProperty -Name 'UsedPercentage' -Value {[Math]::Round((($this.Size - $this.FreeSpace)/$this.Size) * 100, 2)} -PassThru
Now, $diskUsage is a WMI object with Size, FreeSpace and UsedPercentage properties (as well as some WMI metadata properties you can ignore). You can output the value of any of them by refering to the one you want:
$diskUsage.UsedPercentage
15.3
Or show them in a neat table:
$diskUsage | Format-Table Size, FreeSpace, UsedPercentage -AutoSize
Size FreeSpace UsedPercentage
---- --------- --------------
1013310287872 858247196672 15.3

PowerShell property expression increases execution time by 4-5 times

Scroll down for TL;DR
I need to get the following properties for every process as quickly as possible, ideally 5 seconds, maximum 10 seconds: ID, Name, Description, Path, Company, Username, Session ID, StartTime, Memory, CPU (percentage, not time)
To get this data, I put together the following snippet which (I think) is functionally perfect:
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
#{Name='Id';Expression={[int]$_.Id}},
#{Name='Name';Expression={[string]$_.Name}},
#{Name='Description';Expression={[string]$_.Description}},
#{Name='Path';Expression={[string]$_.Path}},
#{Name='Company';Expression={[string]$_.Company}},
#{Name='Username';Expression={[string]$_.UserName}},
#{Name='SessionId';Expression={[string]$_.SessionId}},
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
#{Name='CPUPercent';Expression={
[int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
}}
The issue is that its taking 18-22 seconds to execute, caused by this line (which adds about 16 seconds):
#{Name='CPUPercent';Expression={
[int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
}}
PS C:\Windows\system32> Measure-Command -Expression {
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
#{Name='Id';Expression={[int]$_.Id}},
#{Name='Name';Expression={[string]$_.Name}},
#{Name='Description';Expression={[string]$_.Description}},
#{Name='Path';Expression={[string]$_.Path}},
#{Name='Company';Expression={[string]$_.Company}},
#{Name='Username';Expression={[string]$_.UserName}},
#{Name='SessionId';Expression={[string]$_.SessionId}},
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
#{Name='CPUPercent';Expression={
[int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
}}
}
TotalSeconds : 19.061206
When I remove the slow property expression noted above and keep the WMI query, execution takes about 4.5 seconds:
Measure-Command -Expression {
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
#{Name='Id';Expression={[int]$_.Id}},
#{Name='Name';Expression={[string]$_.Name}},
#{Name='Description';Expression={[string]$_.Description}},
#{Name='Path';Expression={[string]$_.Path}},
#{Name='Company';Expression={[string]$_.Company}},
#{Name='Username';Expression={[string]$_.UserName}},
#{Name='SessionId';Expression={[string]$_.SessionId}},
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}}
}
TotalSeconds : 4.5202906
I thought that by getting all of the required data in a single query and referring back to the $ProcessCPU array would be fast - but I appreciate I'm iterating through each of the 250 arrays stored in $Processes.
TL;DR:
Is there a more performant method of joining two objects on a common property rather than using iteration as I have above? I.E. $ProcessCPU.IDProcess on $Processes.Id?
I tried the following block to test $Output = $ProcessCPU + $Processes | Group-Object -Property Id, it executed in just 3 seconds, but the output wasn't acceptable:
PS C:\Windows\system32> Measure-Command -Expression {
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object #{Name='Id';Expression={[int]$_.IDProcess}}, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
#{Name='Id';Expression={[int]$_.Id}},
#{Name='Name';Expression={[string]$_.Name}},
#{Name='Description';Expression={[string]$_.Description}},
#{Name='Path';Expression={[string]$_.Path}},
#{Name='Company';Expression={[string]$_.Company}},
#{Name='Username';Expression={[string]$_.UserName}},
#{Name='SessionId';Expression={[string]$_.SessionId}},
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}}
$Output = $ProcessCPU + $Processes | Group-Object -Property Id
}
TotalSeconds : 2.9656969
Use CIM to build up a hashtable that maps process IDs (PIDs) to their CPU percentages first.
Then make the calculated property passed to Select-Object consult that hashtable for efficient lookups:
Get-CimInstance Win32_PerfFormattedData_PerfProc_Process |
ForEach-Object -Begin { $htCpuPctg=#{} } `
-Process { $htCpuPctg[$_.IdProcess] = $_.PercentProcessorTime } #`
Get-Process -IncludeUserName |
Select-Object Id,
Name,
Description,
Path,
Company,
UserName,
SessionId,
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
#{Name='CPUPercent';Expression={ $htCpuPctg[[uint32] $_.Id] }}
Note:
Get-CimInstance rather than Get-WimObject is used, because the CIM cmdlets superseded the WMI cmdlets in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell Core, where all future effort will go, doesn't even have them anymore. For more information, see this answer.
There is usually no need to use calculated properties such as #{Name='Id';Expression={[int]$_.Id}} to simply extract a property as-is - just use the property's name - Id - as a Select-Object -Property argument (but you've since clarified that you're using calculated properties because you want explicit control over the property's data type for sending data to an IoT Gateway via JSON).
Note that CIM reports PIDs as [uint32]-typed values, whereas Get-Process uses [int] values - hence the need to cast to [uint32] in the hashtable lookup.

How to get individual process data from list of process data in PowerShell

I am getting the process name ,user name, process time using the below script in PowerShell
$owners = #{}
gwmi win32_process |% {$owners[$_.handle] = $_.getowner().user}
$data = #{}
get-process | select processname,Id,PrivilegedProcessorTime,UserProcessorTime,#{l="Owner";e={$owners[$_.id.tostring()]}}
It is giving the data as
ProcessName : UpdaterUI
Id : 6824
PrivilegedProcessorTime : 00:00:04.6332297
UserProcessorTime : 00:00:04.1964269
Owner : VM82958
I would like to get data of individual process I tried the below code
foreach($val in $data) {
$processname = $val.ProcessName
$processname = $val.Owner
$processname = $val.PrivilegedProcessorTime
$processname = $val.UserProcessorTime
}
But I am not getting any information. can any one help me how to get the information of individual process data ,and PrivilegedProcessorTime,UserProcessorTime data in TotalMilliseconds.
You are almost there. Just assign the result to $data:
$owners = #{}
gwmi win32_process |% {$owners[$_.handle] = $_.getowner().user}
$data = get-process | select processname,Id,PrivilegedProcessorTime,UserProcessorTime,#{l="Owner";e={$owners[$_.id.tostring()]}}
In the first example you populate the hashtable $owners within the ForEach-Object pipeline (alias %). Whereas you don't populate $data anywhere.

Full output hidden on console

I don't get full output of the following code I made.
For Example:
DriveSpace : {174, 0, 98, 171...}
Notice the ellipses (...) after 171. It is skipping the rest of the output after that. You can run the following script to see my output.
#Start of script
$cpu = gwmi -Class Win32_Processor | Select-Object NumberOfCores,NumberOfLogicalProcessors
$memory = gwmi -class win32_physicalmemory | Select-Object {[math]::truncate($_.capacity / 1GB)}
$HostDescription= gwmi -Class Win32_OperatingSystem
$fqdn = "$env:computername.$env:userdnsdomain"
$OS = (gwmi Win32_OperatingSystem)
$OSarchitecture = (gwmi Win32_OperatingSystem)
$disk = gwmi Win32_LogicalDisk | Select-Object DeviceID, volumeName, {[math]::truncate($_.size / 1GB)}
$timezone = [System.TimeZone]::CurrentTimeZone
$fire = netsh advfirewall show publicprofile | select-string state
$firematch = $fire -match "off"
$slmgrResult = cscript c:\windows\system32\slmgr.vbs /dli | Select-string "License Status"
$activation = $slmgrResult -match "Licensed"
$apps = gp HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* |Select DisplayName, DisplayVersion, Publisher, InstallDate
$network = Get-WmiObject win32_networkadapterconfiguration -filter "ipenabled = 'True'" | select-object IPAddress, DefaultIPGateway, DNSDomain, IPSubnet
$props = #{
NumberOfCores = $cpu.NumberOfCores
NumberOfLogicalProcessors = $cpu.NumberOfLogicalProcessors
Memory = $memory.{[math]::truncate($_.capacity / 1GB)}
HostDescription = $HostDescription.Description
FQDN = "$env:computername.$env:userdnsdomain"
OS = (gwmi Win32_OperatingSystem).Name
OSarchitecture = $OSarchitecture.OSArchitecture
DriveLetters = $disk.DeviceID
DriveLabels = $disk.volumeName
DriveSpace = $disk.{[math]::truncate($_.size / 1GB)}
timezone = [System.TimeZone]::CurrentTimeZone.StandardName
FirewallDisabled = $firematch
Licensed = $activation
Applications = $apps
IPAddress_Gateway_DNSDomain_subnet = $network.IPAddress, $network.DefaultIPGateway, $network.DNSDomain, $network.IPSubnet
}
New-Object PSObject -Property $props
#End of script
This is not an official answer as I think the OP needs to be clear on what output he is expecting. This is a start nonetheless
While I have not found official documentation to support this you are just seeing how PowerShell handles console output. Consider the following example which is a collection of varying sizes of arrays.
[pscustomobject]#{
data = "1","2","3","4","5"
}
Would produce the following list style output.
data : {1, 2, 3, 4...}
Notice the fifth element of the 5 property now has the ellipses. The data is still there. It has just been truncated on the console to make the output more terse and easier to read. In this case it seems folly to do so but with some objects complicated output PowerShell has to draw the line somewhere.
Prevent the ellipses
As PetSerAL pointed out you can just use the following line of code before your output.
$FormatEnumerationLimit=-1
If you look at about_Preference_Variables you will see that, by default, this is set to 4. That would support the output you are seeing. Set that value to something higher or -1 and see if it helps.
Other Potential Issues
Like in my comments I want to draw attention to the variable you created called $disk. The output is below. Note this is from my own machine and wont match yours. Still, you should get the picture
DeviceID volumeName [math]::truncate($_.size / 1GB)
-------- ---------- -------------------------------
C: 111
D: Data 499
E: Multimedia 1362
F: 0
G: CentOS 7 x86_64 3
M: Media 2794
Z: 0
Without any other information I can only assume that you want a series of free space values to display. Given that we could break those results out of the array by casting them to string. Also want to update the line that populates the variable.
$disk = gwmi Win32_LogicalDisk | Select-Object DeviceID, volumeName, #{Label="Size(GB)";Expression={[math]::truncate($_.size / 1GB)}}
Gives us the following in $disk`
DeviceID volumeName Size(GB)
-------- ---------- --------
C: 111
D: Data 499
E: Multimedia 1362
F: 0
G: CentOS 7 x86_64 3
M: Media 2794
Z: 0
Then when you build your hashtable you can cast the array to a single space delimited string like this:
DriveSpace = [string]($disk."Size(GB)")
Fairly sure there will be more questions to come from this but it is at least a start. Welcome to SO. It is always a good idea when possible to show us desired output in cases like this so we know what you are trying to achieve. Even if you think it is obvious.
Side notes
You have other properties other that $disk that might have the same issues like Applications which is a complex object. If you do have issues with those as well solving this one might get you in the right direction.
You have many calls to gwmi Win32_OperatingSystem. You should save the results of that into a variable that you can refer to whenever you need it. Right now you are losing time calling it and getting the same results. For example:
$wmiOS = gwmi Win32_OperatingSystem
This is the default formatting of Powershell at work, as provided by Out-Default. It is truncating the DriveSpace array to display in a table in your console, but the information is still there. For example, if you type:
$props.DriveSpace
... you will see the full array displayed. The default formatting behaves differently when it's handling a simple array as opposed to when it's handling a complex object like the $props one you've created.
See also:
How Powershell Outputting and Formatting REALLY works

Powershell Get a specific process counter with id process

I want to get specific counters for processes that I have process id's for. However I can't think of a way to use where-object to match the process for the counter.
Like
Where Gc '\process(*)\id process -eq 456 gc '\process($name)\working set'
So use the process id to retrieve the name and get the working set (or something to that effect).
It seems a bit convoluted to get the correct performance counter path for a process with multiple instances of the same process name:
$proc_id=6580
$proc_path=((Get-Counter "\Process(*)\ID Process").CounterSamples | ? {$_.RawValue -eq $proc_id}).Path
Get-Counter ($proc_path -replace "\\id process$","\% Processor Time")
Timestamp CounterSamples
--------- --------------
11/20/2014 5:39:15 PM \\myhost\process(conhost#2)\% processor time :
0
You can get counters for a process name so first get the process name by using its Id and then embed the process name in the counter. For example:
$id = # your process id
$proc = (Get-Process -Id $id).Name
Get-Counter -Counter "\Process($proc)\% Processor Time"
If you want a solution that also include process with multiple instance IDs you can use :
$p = $((Get-Counter '\Process(*)\ID Process' -ErrorAction SilentlyContinue).CounterSamples | % {[regex]$a = "^.*\($([regex]::Escape($_.InstanceName))(.*)\).*$";[PSCustomObject]#{InstanceName=$_.InstanceName;PID=$_.CookedValue;InstanceId=$a.Matches($($_.Path)).groups[1].value}})
# In french, use '\processus(*)\id de processus' for the counter name
$id = # your process id
$p1 = $p | where {$_.PID -eq $id}
Get-Counter -Counter "\Process($($p1.InstanceName+$p1.InstanceId))\% Processor Time"
# In french, use "\Processus($($p1.InstanceName+$p1.InstanceId))\% temps processeur" for the counter name
Or if you avoid to use Get-Counter and wait the sample interval, try use WMI:
$id = YourProcessIdHere
(gwmi -class Win32_PerfRawData_PerfProc_Process -Namespace "root\CIMV2" | ? {$_.IdProcess -eq $id}).Name;
It is possible to obtain some performance information with the Get-Process commandlet directly and avoid the need to resolve an instance ID.
For the case of the memory working set, just filter the output for the process id you want using where-object, then select the parameters you're interested in:
get-process | where-object{ $_.id -eq 456 } | select name,workingset