You can use named pipeline interprocess communication like that:
Usings
using System;
using System.IO.Pipes;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
Static variables
static public bool AllowApplicationMultipleInstances { get; private set; } = true;
static private Mutex ApplicationMutex;
static private NamedPipeServerStream IPCServer;
Application unique identifier
static public string GetGUID()
{
  object[] attributes = Assembly.GetExecutingAssembly()
                                .GetCustomAttributes(typeof(GuidAttribute), false);
  return ( (GuidAttribute)attributes[0] ).ToString();
}
Check only one instance and init the server
static private bool CheckApplicationOnlyOneInstance(AsyncCallback duplicated)
{
  AllowApplicationMultipleInstances = false;
  string guid = GetGUID();
  ApplicationMutex = new Mutex(true, guid, out bool created);
  if ( created )
    CreateIPCServer(duplicated);
  else
  {
    var client = new NamedPipeClientStream(".", guid, PipeDirection.InOut);
    client.Connect();
    new BinaryFormatter().Serialize(client, "BringToFront");
    client.Close();
  }
  return created;
}
Create the server
static private void CreateIPCServer(AsyncCallback duplicated)
{
  IPCServer = new NamedPipeServerStream(GetGUID(),
                                        PipeDirection.InOut,
                                        1,
                                        PipeTransmissionMode.Message,
                                        PipeOptions.Asynchronous);
  IPCServer.BeginWaitForConnection(duplicated, IPCServer);
}
Anwser to a request
static private void IPCRequest(IAsyncResult ar)
{
  var server = ar.AsyncState as NamedPipeServerStream;
  server.EndWaitForConnection(ar);
  var command = new BinaryFormatter().Deserialize(server) as string;
  if ( command == "BringToFront" )
  {
    Console.WriteLine(command);
    //MainForm.Instance.SyncUI(() => MainForm.Instance.MenuShowHide_Click(null, null));
  }
  server.Close();
  CreateIPCServer(IPCRequest);
}
Test
static private void Test()
{
  if ( !SystemManager.CheckApplicationOnlyOneInstance(IPCRequest) )
    return;
  Console.ReadKey();
}
Usage
You can create as string commands as needed.
For example it allows to pass command line arguments from a process just started to the actual running process.
Also you can improve this simple behavior to have a more complex system, to use a classes framework instead of string commands.
For your application you should be able to use:
static public FormMain MainForm;
static void Main(string[] args)
{
  if ( !SystemManager.CheckApplicationOnlyOneInstance(IPCRequest) )
    return;
  Application.EnableVisualStyles();
  Application.SetCompatibleTextRenderingDefault(true);
  MainForm = new FormMain(args);
  Application.Run(MainForm);
}
If you modify a WinForm control you must to synchronize with the main UI thread:
How do I update the GUI from another thread?
How to access a WinForms control from another thread i.e. synchronize with the GUI thread?
Some links
PipeStream
Full Duplex Asynchronous Read/Write with Named Pipes (CodeProject)
Inter Process Communication (C# Vault)
WCF Comparison with Web Services and .NET Remoting (CodeProject)
Socket Programming In C# (C-SharpCorner)
Socket Programming in C# (GeeksForGeeks)
Simple Client-server Interactions using C# (CodeProject)