45

How would I go about starting a new process without it being the child of the calling process.

Example:

Main Program (Caller.exe)

process.start("file.exe")

Image:

enter image description here

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Andrew Paglusch
  • 767
  • 1
  • 7
  • 17
  • 1
    Just of curiosity, what are the implications of it being a child process and not a parent? – MichaelS Dec 08 '11 at 17:11
  • If a user were to select "kill process tree" in the taskmanager, my entire application would not die. – Andrew Paglusch Dec 09 '11 at 12:26
  • 1
    Still smells like a [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem "What is the XY problem? - Meta Stack Exchange"). What is the real point of new process not being child of current, or circumvent "kill process tree"? – Stéphane Gourichon Mar 07 '18 at 09:36
  • 1
    You might want to create a "launcher" that starts a process and killing the "launcher" won't kill all of the processes that it launched. I think the solution was posted by Josh -- create stub launcher that (by-design) starts a process detached from the caller. – NeoH4x0r Feb 02 '22 at 21:24
  • Here is a "better" version of this question which describes things in more detail: https://stackoverflow.com/questions/12068647/creating-a-new-process-thats-not-a-child-of-the-creating-process/70967133 – Coconut Feb 03 '22 at 07:12

8 Answers8

26

If the spawning process (parent) ends before the spawned process (child) does, then the parent-child chain is broken. To make use of this, you'd have to use an intermediate stub-process like so:

Caller.exe → Stub.exe → File.exe.

Here Stub.exe is simple launcher program that ends just after starting File.exe.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Josh
  • 903
  • 8
  • 15
  • Thank you very much. I'm going to use this solution – Andrew Paglusch Dec 08 '11 at 21:12
  • Thank you for this. I ended up in a weird situation where I had a browser rendering a Java applet, which called an exe application, which in turn rendered an embedded IE component through `Webbrowser` control, and a target third party blackbox-type webpage running in this component was hanging until parent browser either gained back focus or was terminated. De-parenting .exe app from Java solved this completely. – Timekiller Jan 26 '17 at 13:42
  • The detached program can no longer be retrieved with `Process.GetProcesses()`? – Altiano Gerung Jul 31 '18 at 07:12
  • This does not answer the question. It is only teaching how to break the parent-child chain, not how to start a process without it being a child of the spawning process. – Coconut Jan 03 '22 at 08:00
  • @Coconut Process.start will create the process as a child of the caller -- therefore you must break the chain as Josh has described. Honestly, this should be the accepted answer/solution. – NeoH4x0r Feb 02 '22 at 21:28
  • @NeoH4x0r Yes, it's only a workaround, not the right answer to the question which reads "Start new process, without being a child of the spawning process". `System.Diagnostics.Process.Start()` method is really just `kernel32!CreateProcess()` under the hood. If you create a process with `kernel32!CreateProcess()` it allows you to specify a different parent by using a process attribute. I'm posting another answer with the source code describing that. – Coconut Feb 03 '22 at 07:15
15

I have been trying to start a updater process which deletes the files of the calling process and replaces them with new ones. By setting UseShellExecute = true, I was able to circumvent the spawned process from exiting when the calling process exited.

This is inside a .Net Core 3.0 application using WPF.

var startInfo = new ProcessStartInfo("Updater.exe");
startInfo.UseShellExecute = true;
Process.Start(startInfo);
Environment.Exit(0);
Blightbuster
  • 481
  • 7
  • 16
  • This should be the accepted answer. It works in .net 5 as well. – D. Lockett Jul 04 '21 at 11:47
  • 1
    It should be noted that the default of UseShellExecute (is true on .Net framework apps and false on .Net core apps) -- so this probably wouldn't help the OP on VB.net -- since the default would be true, and child processes were still being killed when the parent died. – NeoH4x0r Feb 04 '22 at 18:27
  • 1
    Somehow this didn't work for me (.NET 6). The process is killed when my app closes, and it prints output into my Console App. – Luke Vo Feb 22 '22 at 16:06
  • @LukeVo Which solution worked for you then? – Fabian Bigler Jul 12 '22 at 07:55
  • @FabianBigler unfortunately I didn't found any working solution. I made [another question here](https://stackoverflow.com/questions/71224456/start-a-separate-process-without-printing-its-output-to-console-and-terminating) but no solution. – Luke Vo Jul 12 '22 at 15:38
  • @LukeVo My project is on .NET 6 and it works without any issues. Im also using WPF and WinForms. [Reference](https://github.com/RatScanner/RatScanner/blob/28346e88c2aa3c67d2f94bca0eb13805b23a81ae/RatScanner/RatScannerMain.cs#L173) – Blightbuster Jul 13 '22 at 16:14
  • 1
    @LukeVo, how are you launching your initial app? For example, if you are starting the parent process from a debugger, it's possible that it is capable of tracking spawned processes and terminating them independently. – Sean Mar 10 '23 at 13:43
  • @Sean ah that's interesting. Yes that's very possible though admittedly I usually run my projects without Debugger attached unless I need to debug. – Luke Vo Mar 10 '23 at 15:59
  • @LukeVo For me Rider tracked spawned processes in Debug and non Debug builds. – Bartimaeus Aug 13 '23 at 03:38
14

If you start a process, then you'll be its parent.

Maybe you could try to start your process from cmd.exe instead, so cmd.exe will be the parent.

Process proc = Process.Start(new ProcessStartInfo { Arguments = "/C explorer", FileName = "cmd", WindowStyle = ProcessWindowStyle.Hidden });
ken2k
  • 48,145
  • 10
  • 116
  • 176
7

This runs new process without parent:

System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
psi.FileName = @"cmd";
psi.Arguments = "/C start notepad.exe";
psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
System.Diagnostics.Process.Start(psi);
Pawel P
  • 95
  • 1
  • 2
1

The documentation of Process.Start(string fileName) says

a new process that’s started alongside already running instances 
of the same process will be independent

and it says

Starting a process by specifying its file name is similar to 
typing the information in the Run dialog box of the Windows Start menu

which to me seems consistent with independent processes.

So according to the documentation, Process.Start should do what you desire.

JBSnorro
  • 6,048
  • 3
  • 41
  • 62
  • However, I ran into this problem because it want to spawn an independent child process, but it is spawned dependently through `Process.Start`. Then I tried isolating the behavior in a simple test project, where I concluded that `Process.Start` does indeed start the process independently. This makes me suspect there is something else which affects the behavior of `Process.Start` – JBSnorro Dec 20 '17 at 14:10
  • The MS docs says that is similar to typing into "run" only because the file doesn't have to be an exe and will automatically open in whater application has been configured for the file type. For example, Start("http://localhost") – NeoH4x0r Feb 04 '22 at 18:15
  • Running multiple instances of a process to make them independent ins't applicable when you only want to start a single instance. – NeoH4x0r Feb 04 '22 at 18:18
  • The OP mentions launching a [single instance] of a process -- not multiple instances of the same process.....hence the downvote. – NeoH4x0r Feb 05 '22 at 18:57
1

I am using this code. The advantage is you don't have to use PInvoke. I have found that the CreateProcess api with the DETACHED_PROCESS flag is not 100% reliable, the child process still sometimes dies when the parent dies.

https://github.com/dahall/taskscheduler

// you need two variables:
string fileName = ...
string arguments = ...

// Get the task service on the local machine
using TaskService ts = new();

// create task name
var taskName = "DetachedProcess_" + Convert.ToHexString(MD5.HashData(Encoding.UTF8.GetBytes(Path.GetFileName(fileName))));

// remove the task if it already exists
ts.RootFolder.DeleteTask(taskName, false);

// create a new task definition and assign properties
TaskDefinition td = ts.NewTask();
td.RegistrationInfo.Description = "Detached process for " + fileName;

// create a trigger that will run the process in 5 seconds
td.Triggers.Add(new TimeTrigger(IPBanService.UtcNow.AddSeconds(5.0)));

// create the action to run the process
td.Actions.Add(new ExecAction(fileName, arguments, Path.GetDirectoryName(fileName)));

// delete task upon completion
td.Actions.Add(new ExecAction("schtasks.exe", "/Delete /TN \"" + taskName + "\" /F", null));

// register the task in the root folder
var task = ts.RootFolder.RegisterTaskDefinition(taskName, td);
task.Run(); // just run it now
jjxtra
  • 20,415
  • 16
  • 100
  • 140
0

System.Diagnostics.Process.Start() method calls kernel32!CreateProcess() under the hood. When creating a process with kernel32!CreateProcess() you can specify a different parent by using a process attribute. Here is a function written in C++ that does just that - although I'm not sure how .Net supports such features.

bool CreateProcessWithParent(DWORD parentId, PWSTR commandline) {
    auto hProcess = ::OpenProcess(PROCESS_CREATE_PROCESS, FALSE, parentId);
    if (!hProcess)
        return false;
 
    SIZE_T size;
    //
    // call InitializeProcThreadAttributeList twice
    // first, get required size
    //
    ::InitializeProcThreadAttributeList(nullptr, 1, 0, &size);
 
    //
    // now allocate a buffer with the required size and call again
    //
    auto buffer = std::make_unique<BYTE[]>(size);
    auto attributes = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(buffer.get());
    ::InitializeProcThreadAttributeList(attributes, 1, 0, &size);
 
    //
    // add the parent attribute
    //
    ::UpdateProcThreadAttribute(attributes, 0, 
        PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, 
        &hProcess, sizeof(hProcess), nullptr, nullptr);
 
    STARTUPINFOEX si = { sizeof(si) };
    //
    // set the attribute list
    //
    si.lpAttributeList = attributes;
    PROCESS_INFORMATION pi;
 
    //
    // create the process
    //
    BOOL created = ::CreateProcess(nullptr, commandline, nullptr, nullptr, 
        FALSE, EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr, 
        (STARTUPINFO*)&si, &pi);
 
    //
    // cleanup
    //
    ::CloseHandle(hProcess);
    ::DeleteProcThreadAttributeList(attributes);
 
    return created;
}

Source code taken from https://scorpiosoftware.net/2021/01/10/parent-process-vs-creator-process/

Coconut
  • 2,024
  • 18
  • 25
  • This may work in the end -- but the OP didn't necessarily ask for a Windows-only solution. – NeoH4x0r Feb 04 '22 at 17:47
  • This answer has been downvoted not because it's invalid, but because it triggered other users who posted wrong answers. – Coconut Feb 08 '22 at 06:39
  • Come up with a "correct" platform-independent solution and I'll upvote it. – NeoH4x0r Feb 08 '22 at 23:56
  • The question never asked for a cross-platform solution. This is a valid answer therefore. – Coconut Feb 10 '22 at 00:41
  • The only problem is with someone who's so unintelligent that he cannot tell the sufficient condition from the necessary condition. – Coconut Feb 10 '22 at 00:43
  • OP asked for a c# / vb.net solution. Your suggesting making a new c++ solution and embed it in a project? This seems like a bloated solution at best. – Blightbuster May 10 '22 at 18:17
-1

Here is the code that I'm now using. I thought that it may be useful to someone. It accepts one argument. The argument is a base64 encoded string that decodes to the path of the file that you would like to run.

 Module Module1

    Sub Main()
        Dim CommandLineArgs As System.Collections.ObjectModel.ReadOnlyCollection(Of String) = My.Application.CommandLineArgs
        If CommandLineArgs.Count = 1 Then
            Try
                Dim path As String = FromBase64(CommandLineArgs(0))
                Diagnostics.Process.Start(path)
            Catch
            End Try
            End
        End If
    End Sub

    Function FromBase64(ByVal base64 As String) As String
        Dim b As Byte() = Convert.FromBase64String(base64)
        Return System.Text.Encoding.UTF8.GetString(b)
    End Function

End Module
Andrew Paglusch
  • 767
  • 1
  • 7
  • 17
  • 4
    Your OP says nothing about base64 encoding, WTF? – FizxMike Jul 11 '17 at 20:02
  • 2
    This answer is 6 years old, so forgive me if I don't remember all of the details here. I believe the base64 decoding of the argument is an attempt to strip unwanted characters from the input file's path. The input path would obviously need to be base64 encoded prior to use with this code. Again, this is 6 years old, so I'm not sure if that's what I was after at the time. There's probably a better way of doing this, but I was still fairly new at the time of writing. – Andrew Paglusch Jul 11 '17 at 23:50