Elimine a árvore de processos programaticamente em C#
-
29-10-2019 - |
Pergunta
Estou iniciando o Internet Explorer programaticamente com um código semelhante a este:
ProcessStartInfo startInfo = new ProcessStartInfo("iexplore.exe");
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.Arguments = "http://www.google.com";
Process ieProcess = Process.Start(startInfo);
Isso gera 2 processos visíveis no Gerenciador de Tarefas do Windows.Então, tento encerrar o processo com:
ieProcess.Kill();
Isso resulta no encerramento de um dos processos do Gerenciador de Tarefas e o outro permanece.Tentei verificar se havia alguma propriedade que tivesse processos filhos, mas não encontrei nenhuma.Como posso matar o outro processo também?De maneira mais geral, como você elimina todos os processos associados a um processo iniciado com Process.Start?
Solução
Isso funcionou muito bem para mim:
/// <summary>
/// Kill a process, and all of its children, grandchildren, etc.
/// </summary>
/// <param name="pid">Process ID.</param>
private static void KillProcessAndChildren(int pid)
{
// Cannot close 'system idle process'.
if (pid == 0)
{
return;
}
ManagementObjectSearcher searcher = new ManagementObjectSearcher
("Select * From Win32_Process Where ParentProcessID=" + pid);
ManagementObjectCollection moc = searcher.Get();
foreach (ManagementObject mo in moc)
{
KillProcessAndChildren(Convert.ToInt32(mo["ProcessID"]));
}
try
{
Process proc = Process.GetProcessById(pid);
proc.Kill();
}
catch (ArgumentException)
{
// Process already exited.
}
}
Atualização 26/04/2016
Testado em Visual Studio 2015 Update 2
sobre Win7 x64
.Ainda funciona tão bem agora como há 3 anos.
Atualização 14/11/2017
Adicionada verificação de processo ocioso do sistema if (pid == 0)
Atualização 02/03/2018
Precisa adicionar uma referência ao System.Management
namespace, veja o comentário de @MinimalTech abaixo.Se você tiver o ReSharper instalado, ele se oferecerá para fazer isso automaticamente.
Atualização 10/10/2018
O caso de uso mais comum para isso é eliminar qualquer processo filho que nosso próprio processo C# tenha iniciado.
Nesse caso, uma solução melhor é usar chamadas Win32 em C# para tornar qualquer processo gerado um processo filho.Isso significa que quando o processo pai é encerrado, todos os processos filhos são fechados automaticamente pelo Windows, o que elimina a necessidade do código acima.Por favor, deixe-me saber se você deseja que eu poste o código.
Outras dicas
Não sou fã de nenhuma das soluções apresentadas aqui.
Aqui está o que eu descobri:
private static void EndProcessTree(string imageName)
{
Process.Start(new ProcessStartInfo
{
FileName = "taskkill",
Arguments = $"/im {imageName} /f /t",
CreateNoWindow = true,
UseShellExecute = false
}).WaitForExit();
}
Como usar:
EndProcessTree("chrome.exe");
Se alguém precisa de uma solução central dotnet, dotnet cli veio com uma implementação baseada em taskill como mencionado acima e recursiva pgrep / kill para sistemas baseados em Unix. Implementação completa pode ser encontradano github .Infelizmente, a classe é interna, então você terá que copiá-la em sua base de código.
Listar processos filho (deve ser feito recursivamente):
$"pgrep -P {parentId}"
Kill no processo:
$"kill -TERM {processId}"
Você deve chamar Process.CloseMainWindow()
que enviará uma mensagem para a janela principal do processo.Pense nisso como se o usuário clicasse no botão "X" para fechar ou Arquivo | Sair do item de menu.
É mais seguro enviar uma mensagem para o Internet Explorer para fechar-se, do que ir e matar todos os seus processos.Esses processos podem estar fazendo qualquer coisa e você precisa deixar o IE fazer seu trabalho e terminar antes de simplesmente matá-lo no meio de algo que pode ser importante para execuções futuras.Isso vale para qualquer programa que você mata.
Se alguém estiver interessado, peguei uma das respostas da outra página e modifiquei um pouco.Agora é uma classe independente com métodos estáticos.Ele não possui gerenciamento ou registro de erros adequado.Modifique para usar de acordo com suas necessidades.Fornecer seu processo raiz para KillProcessTree fará isso.
class ProcessUtilities
{
public static void KillProcessTree(Process root)
{
if (root != null)
{
var list = new List<Process>();
GetProcessAndChildren(Process.GetProcesses(), root, list, 1);
foreach (Process p in list)
{
try
{
p.Kill();
}
catch (Exception ex)
{
//Log error?
}
}
}
}
private static int GetParentProcessId(Process p)
{
int parentId = 0;
try
{
ManagementObject mo = new ManagementObject("win32_process.handle='" + p.Id + "'");
mo.Get();
parentId = Convert.ToInt32(mo["ParentProcessId"]);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
parentId = 0;
}
return parentId;
}
private static void GetProcessAndChildren(Process[] plist, Process parent, List<Process> output, int indent)
{
foreach (Process p in plist)
{
if (GetParentProcessId(p) == parent.Id)
{
GetProcessAndChildren(plist, p, output, indent + 1);
}
}
output.Add(parent);
}
}
Outra solução é usar o comando taskill.Eu uso o próximo código em meus aplicativos:
public static void Kill()
{
try
{
ProcessStartInfo processStartInfo = new ProcessStartInfo("taskkill", "/F /T /IM your_parent_process_to_kill.exe")
{
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
UseShellExecute = false,
WorkingDirectory = System.AppDomain.CurrentDomain.BaseDirectory,
RedirectStandardOutput = true,
RedirectStandardError = true
};
Process.Start(processStartInfo);
}
catch { }
}
Você está usando o IE8 ou o IE9?Isso com certeza iniciaria mais de um processo devido à sua nova arquitetura de multiprocessos.De qualquer forma, dê uma olhada em este outroresposta por obter uma árvore de processo e eliminá-la.
Outra abordagem que pode ser muito útil é usar o API do Windows para objetos de trabalho . Um processo pode ser atribuído a um objeto de trabalho. Os processos filho de tal processo são atribuídos automaticamente ao mesmo objeto de trabalho.
Todos os processos atribuídos a um objeto de trabalho podem ser eliminados de uma vez, por exemplo, com TerminateJobObject que :
Termina todos os processos atualmente associados ao trabalho.
O exemplo C # nesta resposta ( com base nesta resposta ) usa o JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE em vez disso, que:
Faz com que todos os processos associados ao trabalho sejam encerrados quando o último identificador do trabalho for fechado.
Como fechar corretamente o Internet Explorer quando iniciado de PowerShell?
Vários dos que comentaram no tópico acima que isso é causado por um bug no Win7 (já que não parece ocorrer para usuários que estão usando outras versões do Windows). Muitas páginas na Internet, incluindo a página da microsoft, afirmam o erro do usuário e dizem a você para simplesmente usar o método quit disponível no objeto do IE, que SUPOSTA fechará também todos os processos filho (e supostamente fecha no Win8 / XP etc)
Devo admitir, de minha parte, foi um erro do usuário. Estou no win7 e o motivo pelo qual o método de parada não estava funcionando para mim foi um erro na codificação. Ou seja, eu estava criando o objeto IE na declaração e, em seguida, criando outro (anexado ao mesmo objeto) mais tarde no código ... Eu quase terminei de hackear a rotina de assassinato pai-filho para funcionar para mim quando percebi o problema.
Por causa de como o IE funciona, o processID que você gerou como pai pode ser anexado a outras janelas / subprocessos que você NÃO criou. Use quit e lembre-se de que, dependendo das configurações do usuário (como cache vazio ao sair), pode levar alguns minutos para que os processos concluam suas tarefas e fechem.
Com o .NET Core 3.0, existe um método exatamente para isso, a saber nova sobrecarga do método Process.Kill()
já existente.IOW, fazer process.Kill(true)
na variável process
do tipo Process
mata o processo com todos os seus descendentes.Isso é multiplataforma, naturalmente.