Trying to find and kill a process by PowerShell script - powershell

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.

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.

Nesting foreach state

I have the following script to list the running vms on hyper-V servers:
$VirtualHosts = Get-content "C:\scripts\Hosts.txt"
ForEach ($Guest in $VirtualHosts)
{Get-VM -ComputerName $Guest | Where State -eq Running | FT Name}
I want to add the functionality of rebooting the computers using the Restart-Computer cmdlet. To do this, I plan on using a nested foreach statement. Can you help me with the nested statement?
Nesting two loop constructs is pretty straight-forward in PowerShell - just make sure the inner/nested loop is entirely contained within the body of the outer loop:
$VirtualHosts = Get-content "C:\scripts\Hosts.txt"
foreach($vHost in $VirtualHosts)
{
foreach($runningVM in Get-VM -ComputerName $vHost | Where State -eq Running)
{
$runningVM |Restart-VM
}
}
You can also skip the inner loop completely and just pipe the output from Get-VM directly to Restart-VM:
foreach($vHost in $VirtualHosts)
{
Get-VM -ComputerName $vHost | Where State -eq Running | Restart-VM
}

Taking input from one PSSession and sending it to another

Like many others, my background is in Linux with no powershell experience. So this object oriented programming is messing me up.
I need to search through VMware Horizon for VMs with users assigned to them, then check if they are disabled in AD. If they are disabled in AD I want to recycle the VM.
At the moment I am pulling the SIDs for the users from VMware Horizon, but when I try to use these in an invoke-command against AD I receive the following error
"Object reference not set to an instance of an object"
The Script so far
function getlist() {
$temp=Invoke-Command -ComputerName $vdiserver -ScriptBlock { add-pssnapin vmware.view.broker; get-desktopvm | select user_sid }
$list=$temp | Select-Object user_sid
#$list
}
$vdi1="server1"
$vdi2="server2"
$test=Test-Connection -ComputerName $vdi1 -Quiet
$test2=Test-Connection -ComputerName $vdi2 -Quiet
if ($test -eq "True"){
$vdiserver=$vdi1
getlist
}
elseif ($test2 -eq "True"){
$vdiserver=$vdi2
getlist
}
else {echo "No servers to connect to"}
ForEach ($user in $list) #{
#echo $user
#sleep 1
#}
{Invoke-Command -ComputerName domaincontroller -ScriptBlock {param($p1) get-aduser -identity $p1 } -argumentlist $user}
So this object oriented programming is messing me up.
So you're trying to revert to shell script, and writing twice as much code to do achieve half as much work.
The most important bit you're missing is to imagine an object as a collection of things - like, imagine you're working with /etc/passwd and each line has a user ID and a group ID and a home directory and a login shell.. and you're passing the entire line around at once, that's your analogous object.
An object has many properties, just like that (but overall more capable).
When you Select user_sid you're choosing that field to stay in the 'line', but the line is still something like :::user_sid:::: with the other fields now empty. (Approximately). But they're still there and in the way. To work with it directly, you have to get it out of the 'line' entirely - throw the container away and just have the user_sid outside of it.
get-desktopvm | select user_sid
->
get-desktopvm | select -expandproperty user_sid
which makes "sid1", "sid2", "sid3", but no containers for each sid.
This
function getlist() {
$temp=Invoke-Command -ComputerName $vdiserver -ScriptBlock { add-pssnapin vmware.view.broker; get-desktopvm | select user_sid }
$list=$temp | Select-Object user_sid
}
is essentially saying
function getlist() {
#do any amount of work here, and throw it all away.
}
Because the function returns nothing, and it doesn't change any data on disk or anything, so when the function finishes, the variables are cleared out of memory, and you can't use them afterwards.
This:
if ($test -eq "True"){
is a bit of a nonsense. It might work, but it's not working how you expect because it's happenstance that "a string with content" compared to a boolean True is True, regardless of the string containing the English word "True" or not. But it's also redundant - $test is itself true or false, you don't need to compare True with anything. if ($test). Or even if (Test-Connection -ComputerName $vdi -Quiet)
But stillll, so much work. Just connect to them all, and let it fail for the ones it can't contact. Maybe add -ErrorAction SilentlyContinue if you don't want to see the error.
$VMs = Invoke-Command -ComputerName Server1,Server2 -ScriptBlock {
Add-PsSnapin vmware.view.broker
Get-DesktopVm
}
Now you have all the VMs, get the user enabled/disabled state
foreach ($VM in $VMs) {
$Sid = $VM.user_sid
$AdEnabled = Invoke-Command -ComputerName domaincontroller -ScriptBlock {
(Get-AdUser -Identity $using:Sid).Enabled
}
$VM| Add-Member -NotePropertyName 'AdEnabled' -NotePropertyValue $AdEnabled
}
Now you should ideally have $VM as an array of objects, each one having all the VM Desktop properties - and also the True/False state of the AD Enabled property for that user account.
$VM | Out-Gridview
or
$VM | Export-Csv Report.csv
or
$VM | Where-Object { -not $_.AdEnabled }

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 ",""

Powershell how to get the ParentProcessID by the ProcessID

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"