Can I detect the missing console when running a Mono app in the background?
I have a .NET 4.5 console application that I am running on CentOS using Mono. The code ends with:
Console.ReadLine();
If I run the application interactively, then it behaves as I expected, but Console.ReadLine () expects keyboard input, however, if I start the application using nohup to start it in the background ...
nohup mono Program.exe > Program.log &
then Program.log shows Console.ReadLine () is throwing an odd exception:
System.UnauthorizedAccessException: Access to the path "/home/user/[Unknown]" is denied.
at System.IO.FileStream.ReadData (IntPtr handle, System.Byte[] buf, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0
at System.IO.FileStream.ReadInternal (System.Byte[] dest, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0
at System.IO.FileStream.Read (System.Byte[] array, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0
at System.IO.StreamReader.ReadBuffer () [0x00000] in <filename unknown>:0
at System.IO.StreamReader.ReadLine () [0x00000] in <filename unknown>:0
at System.IO.UnexceptionalStreamReader.ReadLine () [0x00000] in <filename unknown>:0
at System.IO.SynchronizedReader.ReadLine () [0x00000] in <filename unknown>:0
at System.Console.ReadLine () [0x00000] in <filename unknown>:0
at Program1.Program.Main (System.String[] args) [0x00000] in <filename unknown>:0
I can obviously catch and safely ignore the exception, but I was curious to know if it is possible to detect the fact that I do not have a Console and so change the behavior of my application?
source to share
After a bit of experimentation, this seems to do the job for me:
if (Console.In is StreamReader) {
Console.WriteLine("Interactive");
} else {
Console.WriteLine("Background");
}
Not sure if it's perfect for all possible stdin reassignments, etc., but it works well enough for my purposes.
source to share
You can take a look at Environment.UserInteractive Property (which is also supported in mono - http://buttle.shangorilla.com/1.1/handlers/monodoc.ashx?link=P%3ASystem.Environment.UserInteractive )
The UserInteractive property reports false for a Windows process or service such as IIS that runs without a user interface. If this property is false, do not display modal dialogs or message boxes because it is not a graphical user interface for user interaction.
source to share
Basically I usually want to know:
- Whether it runs as a service or not.
- Whether it works as a console or not.
I think your question is about this too ... I'll just write the recipe because it took me too long to figure out all the details ...
Project setup
The easiest way to do this is to create a windows service
C # application and start hacking into Program.cs
.
Basically I'm here:
- If it's Mono check if TTY is connected -> Console, mono
- If it is Windows check UserInteractive
- If it's interactive, highlight the console to make it work.
- If that doesn't work, we'll just accept a console app.
Here is the code:
public static ProgramType ProgramType
{
get
{
if (!programType.HasValue)
{
try
{
if (Type.GetType("Mono.Runtime") != null)
{
// It a console application if 'bool Mono.Unix.Native.Syscall.isatty(0)' in Mono.Posix.dll
var monoPosix = System.Reflection.Assembly.Load("Mono.Posix, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756");
Type syscallType = monoPosix.GetType("Mono.Unix.Native.Syscall");
var method = syscallType.GetMethod("isatty");
bool isatty = (bool)method.Invoke(null, new object[] { 0 });
if (isatty)
{
programType = ProgramType.MonoConsole;
}
else
{
programType = ProgramType.MonoService;
}
}
else
{
if (Environment.UserInteractive)
{
programType = ProgramType.WindowsConsole;
}
else
{
programType = ProgramType.WindowsService;
}
}
}
catch
{
programType = ProgramType.Unknown;
}
}
return programType.Value;
}
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AllocConsole();
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FreeConsole();
[DllImport("kernel32", SetLastError = true)]
static extern bool AttachConsole(int dwProcessId);
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
switch (ProgramType)
{
case ProgramType.WindowsConsole:
{
// WindowsConsole application
//
// Get a pointer to the forground window. The idea here is that
// IF the user is starting our application from an existing console
// shell, that shell will be the uppermost window. We'll get it
// and attach to it
IntPtr ptr = GetForegroundWindow();
int u;
GetWindowThreadProcessId(ptr, out u);
try
{
Process process = Process.GetProcessById(u);
if (process.ProcessName == "cmd")
{
// Is the uppermost window a cmd process?
AttachConsole(process.Id);
}
else
{
// No console AND we're in console mode ... create a new console.
AllocConsole();
}
// Console is now accessible.
NubiloSoft.Util.Logging.Sink.Console.Register();
// Arguments?
StartConsoleService();
}
finally
{
FreeConsole();
}
}
break;
case ProgramType.MonoConsole:
{
// Console is now accessible.
NubiloSoft.Util.Logging.Sink.Console.Register();
// Arguments?
StartConsoleService();
}
break;
case ProgramType.MonoService:
case ProgramType.WindowsService:
{
// Start service
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
}
break;
default:
Console.WriteLine("Unknown CLR detected. Running as console.");
{
// Console is now accessible.
NubiloSoft.Util.Logging.Sink.Console.Register();
// Arguments?
StartConsoleService();
}
break;
}
}
I usually use Service1 as the default to call "Start" and "Stop" in some static class Startup
. It works the same with StartConsoleService
. Since the ProgramType is displayed, you can use it whenever you need it.
source to share