Powershell how to get the ParentProcessID by the ProcessID - powershell

I've problems to get the ParentProcessID from a Process where I have the ProcessID. I tried it like this, this is how it works with the ProcessID:
$p = Get-Process firefox
$p.Id
But if I try it with the ParentProcessID, it doesn't work:
$p.ParentProcessId
Is there a way to get the ParentProcessID by the ProcessID?

As mentioned in the comments, the objects returned from Get-Process (System.Diagnostics.Process) doesn't contain the parent process ID.
To get that, you'll need to retrieve an instance of the Win32_Process class:
PS C:\> $ParentProcessIds = Get-CimInstance -Class Win32_Process -Filter "Name = 'firefox.exe'"
PS C:\> $ParentProcessIds[0].ParentProcessId
3816

This worked for me:
$p = Get-Process firefox
$parent = (gwmi win32_process | ? processid -eq $p.Id).parentprocessid
$parent
The output is the following:
1596
And 1596 is the matching ParentProcessID I've checked it with the ProcessExplorer.

In PowerShell Core, the Process object returned by Get-Process cmdlet contains a Parent property which gives you the corresponding Process object for the parent process.
Example:
> $p = Get-Process firefox
> $p.Parent.Id

I wanted to get the PPID of the current running PS process, rather than for another process looked up by name. The following worked for me going back to PS v2. (I didn't test v1...)
$PPID = (gwmi win32_process -Filter "processid='$PID'").ParentProcessId
Write-Host "PID: $PID"
Write-Host "PPID: $PPID"

Related

powershell function to kill child processes on *nix without Get-CimInstance

I have a Powershell function KillChildren that works on Windows, but not in bash (on my Mac). The problem is that it uses the windows-only Get-CimInstance cmdlet.
Here is the windows-centric version (whose authorship is unknown to me):
function KillChildren {
Param (
[Parameter(Position = 0, Mandatory, ValueFromPipeline)]
[int[]]$ProcessId,
[switch]$PassThru
)
process {
foreach ($p in $ProcessId) {
Get-CimInstance -Class Win32_Process -Filter "ParentProcessId = '$p' AND NOT Name LIKE 'conhost%'" -Property ProcessId |
Select-Object -ExpandProperty ProcessId |
KillChildren -PassThru |
ForEach-Object {
Stop-Process -Id $_ -ErrorAction Ignore -Force
}
if ($PassThru -and $PassThru.IsPresent) {
$p
}
}
}
}
How would you write a version of this function that would work in Unix?
The Unix logic-path can't use Get-CimInstance, because that's a windows-specific cmdlet.
This is in an multi-platform repo, and I'm hoping to maintain only a single set of PS scripts, instead of parallel PS and batch scripts.
The following should work on Unix-like platforms to get a given process' child processes (with the given process identified via its PID (process ID), $p) :
Get-Process | Where-Object { $_.Parent.Id -eq $p }
It also works on Windows in principle, assuming you're running PowerShell (Core) 7+ rather than Windows PowerShell (where the Get-Process output objects have no .Parent property), but there's a pitfall (which your Get-CimInstance solution already (mostly) accounts for - see next section):
If the process whose child processes to kill is the current process and you're running in a regular console window (conhost.exe), you must exclude the automatically created conhost child process from the results, as killing it too would close the terminal window and therefore kill the target process as well.
This is not a concern in Windows Terminal.
The following is a cross-edition, cross-platform solution:
# Assumes that $p contains the PID (process ID) of interest.
$(if ($env:OS -eq 'Windows_NT') {
(Get-CimInstance -Class Win32_Process -Filter "ParentProcessId = $p AND Name != 'conhost.exe'" -Property ProcessId).ProcessId
} else {
(Get-Process | Where-Object { $_.Parent.Id -eq $p }).Id
}) # | ...
Note:
At least hypothetically, eliminating all conhost.exe child processes isn't fully robust, because it bears the risk of false positives (excluding child processes that shouldn't be excluded), given that it's possible to explicitly launch conhost.exe child processes with commands such as conhost cmd /k.

Trying to find and kill a process by PowerShell script

I have the following script to find the process "dotnet.exe". In my system, I have many dotnet.exe processes running. But I want to kill the "dotnet.exe" which has command line argument "MyService\Web\argument". I'm trying to do it by the following script. But it doesn't find anything, although I see the process in the Task Manager.
$process = Get-WmiObject Win32_Process | select name, commandline
foreach ($p in $process)
{
if ($p.name -contains "dotnet.exe" -and $p.commandline -contains "web")
{
$kp = Get-Process $p;
$kp.CloseMainWindow();
if (!$kp.HasExited)
{
$kp | Stop-Process -Force
}
}
else
{
Write-Host name: $p.name and param: $p.commandline;
}
}
All you need to do is filter the process list directly via Get-WmiObject and then terminate the matching process(es):
$fltr = "name like '%dotnet.exe%' and commandline like '%web%'"
Get-WmiObject Win32_Process -Filter $fltr | ForEach-Object {
$_.Terminate()
}
You could also call Terminate() directly on the output of Get-WmiObject like this:
(Get-WmiObject Win32_Process -Filter $fltr).Terminate()
However, there are situations where this could fail, e.g. if Get-WmiObject doesn't return any results, or if you're using PowerShell v2 or earlier and Get-WmiObject returns more than one result (passing a method call to the members of an array requires member enumeration, which was introduced with PowerShell v3). Using a ForEach-Object loop is both more robust and backwards-compatible.
The Get-WmiObject cmdlet returns quite useful objects, but you have stripped off everything by selecting only the Name and CommandLine parameters:
$process = Get-WmiObject Win32_Process | select name, commandline
If you remove the | select name, commandline part, you can still loop through each process but also make use of methods like Terminate() that will still be available.
You could do it in one shot, as per #ansgar-wiechers comment, or still make use of the loop and add in more logging, etc. if you wanted:
$process = Get-WmiObject Win32_Process
foreach($p in $process){
if($p.Name -eq "*dotnet.exe" -and $p.CommandLine -like "*web*"){
$p.Terminate()
# and so on...
}
}
Note also the comment from #TheIncorrigible1 about the use of comparison operators. I have used -eq for the process name and -like for the command line.

PowerShell Script Not Working as Expected (foreach loop)

I'm using below PowerShell script to set server power plan to High Performance mode. The issue is, it's making changes only to the server where the I'm executing the script even after passing server names through a text file (Servers.txt). I've used foreach loop to iterate through the server list, but still no luck. Not sure where I'm missing the logic, can someone help with this. Thanks in advance.
$file = get-content J:\PowerShell\PowerPlan\Servers.txt
foreach ( $args in $file)
{
write-host "`r`n`r`n`r`nSERVER: " $args
Try
{
gwmi -NS root\cimv2\power -Class win32_PowerPlan -CN $args | select ElementName, IsActive | ft -a
#Set power plan to High Performance
write-host "`r`n<<<<<Changin the power plan to High Performance mode>>>>>"
$HighPerf = powercfg -l | %{if($_.contains("High performance")) {$_.split()[3]}}
$CurrPlan = $(powercfg -getactivescheme).split()[3]
if ($CurrPlan -ne $HighPerf) {powercfg -setactive $HighPerf}
#Validate the change
gwmi -NS root\cimv2\power -Class win32_PowerPlan -CN $args | select ElementName, IsActive | ft -a
}
Catch
{
Write-Warning -Message "Can't set power plan to high performance, have a look!!"
}
}
The problem is that although a foreach loop is used to iterate all the servers, the names are never used for actual power configuration. That is,
$HighPerf = powercfg -l | %{if($_.contains("High performance")) {$_.split()[3]}}
will always be executed on the local system. Thus, no power plan is changed on remote server.
As a work-around, maybe psexec or Powershell remoting would do, as powercfg doesn't seem to support remote system management.
The MS Scripting Guys have a WMI based solution too, as usual.
From the Gist of your Question,I think you may wanna try running the complete Set of commands in Invoke-Command Invoke-Command Documentation and pass the system name in -ComputerName
$file = get-content J:\PowerShell\PowerPlan\Servers.txt
foreach ( $args in $file)
{
invoke-command -computername $args -ScriptBlock {
write-host "`r`n`r`n`r`nSERVER: " $args
Try
{
gwmi -NS root\cimv2\power -Class win32_PowerPlan -CN $args | select ElementName, IsActive | ft -a
#Set power plan to High Performance
write-host "`r`n<<<<<Changin the power plan to High Performance mode>>>>>"
$HighPerf = powercfg -l | %{if($_.contains("High performance")) {$_.split()[3]}}
$CurrPlan = $(powercfg -getactivescheme).split()[3]
if ($CurrPlan -ne $HighPerf) {powercfg -setactive $HighPerf}
#Validate the change
gwmi -NS root\cimv2\power -Class win32_PowerPlan -CN $args | select ElementName, IsActive | ft -a
}
Catch
{
Write-Warning -Message "Can't set power plan to high performance, have a look!!"
}
}
}

Need help in WMIC Output

How can I format the output file for below wmic command? I need to rework on failed machines
wmic /node:#D:\input.txt /Output:"D:\Result.html" nicconfig where (IPEnabled=TRUE and DHCPEnabled=FALSE) call SetDNSServerSearchOrder ("9.1.1.1","10.1.1.1")
In PowerShell? You collect it in a variable like this:
$output = & wmic '/node:#D:\input.txt' nicconfig where '(IPEnabled=TRUE and DHCPEnabled=FALSE)' call SetDNSServerSearchOrder '("9.1.1.1","10.1.1.1")'
if you're running wmic in the first place. Which you're not.
In PowerShell use the proper cmdlets for WMI operations (e.g. Get-WmiObject):
$dnsServers = '9.1.1.1', '10.1.1.1'
$computers = Get-Content 'D:\input.txt'
$output = Get-WmiObject -Computer $computers -Class Win32_NetworkAdapterConfiguration -Filter 'IPEnabled=True AND DHCPEnabled=False' |
ForEach-Object { $_.SetDNSServerSearchOrder($dnsServers) }

Powershell - how to replace OS Version number with String

I am querying remote servers for their operating system. I know that I can return the Version, but I want to replace these values with the friendly name. The code I have so far is:
$Computer = (gc c:\servers.txt)
$BuildVersion = Get-WmiObject -Class Win32_OperatingSystem -Property Version, CSName -ComputerName $Computer -ErrorAction SilentlyContinue
$Build=$BuildVersion.version
If ({$BuildVersion.Version -match "5.2.3790"})
{$Build="2003"}
Elseif ({$BuildVersion.Version -match "6.1.7601"})
{$Build="2008"}
Elseif ({$BuildVersion.Version -like "6.3.9600"})
{$Build="2012"}
But this doesn't seem to work and only returns "2003" regardless. Please help, I'm fairly new to PS and coding.
thanks
The problem is your if statements. Putting the Boolean expression inside squiggly brackets makes it a script block, and that's going to get cast as a string before being cast as a Boolean. Strings cast to Booleans always evaluate to true unless they're empty.
PS C:\> {$BuildVersion.Version -match "5.2.3790"}
$BuildVersion.Version -match "5.2.3790"
PS C:\> ({$BuildVersion.Version -match "5.2.3790"}) -as [bool]
True
PS C:\> $BuildVersion.Version -match "5.2.3790"
False
PS C:\> ($BuildVersion.Version -match "5.2.3790") -as [bool]
False
So what you're running is essentially:
if ([bool]'$BuildVersion.Version -match "5.2.3790"') [...]
And that's always going to be true.
Try:
$Computer = (gc c:\servers.txt)
$BuildVersion = Get-WmiObject -Class Win32_OperatingSystem -Property Version, CSName -ComputerName $Computer -ErrorAction SilentlyContinue
$Build=$BuildVersion.version
If ($BuildVersion.Version -match "5.2.3790")
{
$Build = "2003"
}
Elseif ($BuildVersion.Version -match "6.1.7601")
{
$Build = "2008"
}
Elseif ($BuildVersion.Version -like "6.3.9600")
{
$Build = "2012"
}
Bottom line is that squiggly brackets are not parentheses and you can't use them like they are.
However, there's also a major logic error here. You're potentially fetching an array for $BuildVersion because you're reading from a file, but then you treat it like a single value. You never loop through $BuildVersion. However, I do not have enough information about what you're actually trying to do with your script (like what you do with $Build) to be able to fix that.
I originally said this, but I've since changed my mind
The reason this is only returning 2003 is that you're only running your If code on a single entry in the list.
Wrong
As TessellatingHeckler says, the reason your if wasn't working is that you had too many curly braces, so PowerShell wasn't actually evaluating your logic.
However, you still need to step through each of the computers to do what you're trying to do. We'll do that by adding in a ForEach loop. I also went ahead and replaced your If {} logic with a Switch statement, which I think is easier to understand for a scenario like this with multiple clauses. If's just get way too verbose.
Finally, I'm assuming you want to output the results too, so I added a custom object here, which is just a way of choosing which properties we want to display.
$Computer = (gc c:\servers.txt)
ForEach ($system in $computer){
$BuildVersion = Get-WmiObject -Class Win32_OperatingSystem -Property Version, CSName -ComputerName $system -ErrorAction SilentlyContinue
$Build=$BuildVersion.version
switch ($build){
"5.2.3790" {$Build="2003"}
"6.1.7601" {$Build="2008"}
"6.3.9600" {$Build="2012"}
}
#output results
[pscustomobject]#{Server=$system;OSVersion=$build;CSName=$buildVersion.CSname}
}#EndOfForEach
Output
>Server OSVersion CSName
------ --------- ------
dc2012 2012 DC2012
sccm1511 2012 SCCM1511
You can use this:
Get-WmiObject -Class Win32_OperatingSystem | Select-Object -ExpandProperty Caption
Additionally you can see everything this WMI object holds like this:
Get-WmiObject -Class Win32_OperatingSystem | fl *
Edit: if you want to remove some text from the string, you can use -replace:
(Get-WmiObject -Class Win32_OperatingSystem |
Select-Object -ExpandProperty Caption) -replace "Microsoft Windows Server ",""