title: C# - Send input to child process - console app
body: <h3>Background info</h3>
  <p>
  I am writing an integration test that spawns a child process (c# console app).  The test is counting some rows in the database after the process is spun up and after the process is closed.  The process is closed via process.Kill()
  </p>
  <p>
  When the process is killed in this manner, it doesn't hit the Stop method within the process. I need to call this stop method to stop threads and remove entries from the database in order for the test to pass.
  </p>
  <h3>Original Code</h3>
  The console app process that I am spawning in my test:
  <pre><code>static void Main(string[] args)
      {
          TaskManager tm = new TaskManagerProcess();
  
          if (Environment.UserInteractive ||
              (args.EmptyForNull().Any(a =&gt; a.Equals(&quot;-RunInteractive&quot;, StringComparison.OrdinalIgnoreCase) || a.Equals(&quot;/RunInteractive&quot;))))
          {
              tm.ConsoleStart(args);
              Console.WriteLine(&quot;Press [Enter] to shut down, any other key to mark&quot;);
              while (true)
              {
                  ConsoleKeyInfo key = Console.ReadKey(true);
                  if (key.Key == ConsoleKey.Enter)
                      break;
                  Console.WriteLine(&quot;========================================================&quot;);
                  Console.Out.Flush();
              }
              Console.WriteLine(&quot;Shutting down...&quot;);
              tm.ConsoleStop();
          }
          else
          {
              ServiceBase.Run(tm);
          }
      }
  }
  </code></pre>
  <p>The test code:</p>
  <pre><code>        //count before starting child proc
          int preCount;
          //count after process is spun up
          int runningsCount;
          //count after stopped
          int postCount;
  
          //Get an initial count of the logged in modules before svc host is started
          user = ApiMethod.GetLoggedInUsers().Where(x =&gt; x.RecId == userRecID).FirstOrDefault();
          preCount = user.LoggedInModules.Count;
  
          Process proc = Helper.StartProcess(ConnectionBundle);
  
          //Give process time to spin up leaders and workers
          await Task.Delay(TimeSpan.FromSeconds(30));
  
          //Get a count of modules after process is spun up
          user = ApiMethod.GetLoggedInUsers().Where(x =&gt; x.RecId == userRecID).FirstOrDefault();
          runningCount = user.LoggedInModules.Count;
  
          //Write a line terminator to the child svc host process -
          //this allows it to shutdown normally
          Helper.ProcessInput.WriteLine();
          Helper.ProcessInput.Close();
  
          Helper.KillProcess(proc);
  
          await Task.Delay(TimeSpan.FromSeconds(5));
  
          //Get count of logged in modules after process is closed
          user = ApiMethod.GetLoggedInUsers().Where(x =&gt; x.RecId == userRecID).FirstOrDefault();
          postCount = user.LoggedInModules.Count;
  </code></pre>
  <p>
  Helper is a static class that sets up the process start info(including args) and starts the process.  In helper I've redirected the StandardInput and added a property ProcessInput which is set to the StandardInput of the created process.
  </p>
  <p>
  My goal is to send input of "Enter" from the test to the spawned process so that it will break from the loop and call tm.ConsoleStop()
  </p>
  <p>
  TaskManagerProcess is a private custom class that controls the process.  It does not inherit from System.Diagnostics.Process. As an alternate approach,  my test could interact with TaskManagerProcess directly. However, I can't make TaskManagerProcess public and I need to run TaskManagerProcess in its own AppDomain because calling ConsoleStop is disposing objects in the API that I need to finish the test.
  </p>
  <h3>Things I've Tried</h3>
  <pre><code>    [DllImport(&quot;Kernel32&quot;)]
      private static extern bool SetConsoleCtrlHandler(CloseProcDelgate handler, bool add);
  </code></pre>
  <p>
  I tried adding a call to Kernel32.SetConsoleCtrlHandler (and the necessary delegate) to call ConsoleStop when the process is exited.  This doesn't seem to work when the process is killed via process.Kill()
  </p>
  <p>
  With the original process code, I noticed an exception when I wrote to the StandardInput. The exception message told me to use Console.Read instead of Console.ReadKey(). This actually works intermittently!  I can sometimes get a breakpoint on int cKey = Console.Read()  (with debugger attached to child process) but other times it doesn't hit the breakpoint.
  </p>
  <pre><code>while (true)
              {
                  //Changing this to Console.Read instead of Console.ReadKey
                  //Allows us to send redirected input to process?
                  int cKey = Console.Read();
                  if ((ConsoleKey)cKey == ConsoleKey.Enter)
                      break;
  
                  Console.WriteLine(&quot;========================================================&quot;);
                  Console.Out.Flush();
              }
  </code></pre>
  <p>
  Finally, I tried interacting with TaskManagerProcess directly. I made the private class internal, and marked the internals visible to my test assembly.  I cannot make the class public.
  </p>
  <p>
  When I go this route,  calling tm.ConsoleStop() blows away some objects in my API so I can't check the count after this method is called. For this reason, I thought I would create a new AppDomain and call AppDomain.CreateInstanceAndUnwrap() on the TaskManagerProcess class.  However, I get an exception here, I believe its due to the the fact that the class is internal.
  </p>
  <p>
  I am really stuck at this point! Any help is appreciated and thanks for taking the time to read this!
  </p>
  <h3> Edit </h3>
  <p>
  I created a demo project <a href="https://github.com/jmaness52/Process_Communication" rel="nofollow noreferrer">here</a>
  that shows what I am trying to do and has both approaches in the Test method. </p>
  <p>
  Initially I thought I couldn't call AppDomain.CreateInstanceAndUnwrap() because the TaskManagerProcess class was internal.  However, after playing with my demo project, I think I just can't load the assembly.
  </p>