1

I'm trying to attach a console to a Service if im running it from the debugger. I've read up a couple of "working" solutions, but those seem not to really work. Here is he Code I use:

    public static void RunService(Func<ServiceBase> factory)
    {
        if (Debugger.IsAttached)
        {
            Utils.AttachConsole();
            Console.Write($"Starting service ");
            var instance = factory();
            Console.WriteLine(instance.GetType().Name);
            //Invoke start Method
            Console.WriteLine("Press [ENTER] to exit");
            Console.ReadLine();
            //Stop service
        }
        else
        {
            ServiceBase.Run(factory());
        }
    }

Alloc Console is:

    public static void AttachConsole()
    {
        var ret = NativeMethods.AllocConsole();
        IntPtr currentStdout = NativeMethods.GetStdHandle(NativeMethods.STD_OUTPUT_HANDLE);
        NativeMethods.SetStdHandle(NativeMethods.STD_OUTPUT_HANDLE, new IntPtr(7));
        TextWriter writer = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true };
        Console.SetOut(writer);
    }

and the Interop includes:

internal static class NativeMethods
{
    internal const uint STD_OUTPUT_HANDLE = 0xFFFFFFF5;

    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool AllocConsole();

    [DllImport("kernel32.dll")]
    internal static extern IntPtr GetStdHandle(uint nStdHandle);

    [DllImport("kernel32.dll")]
    internal static extern void SetStdHandle(uint nStdHandle, IntPtr handle);
}

What happens is, a console is created and attached, but there is no output. Can't be to difficult, but I'm to dumb to see it :(

EDIT: The Issue is Visual Studio, it is not the code "itself". Without VS, I can get a console and receive the expected output there. There is some kind of redirection in VS, which I am looking to overcome here.

EDIT just for Hans - here is the "full code"

static void Main(string[] args)
{
    ServiceLauncher.RunService(() => new FactoryService();
}

The project is set to application type windows.

Jaster
  • 8,255
  • 3
  • 34
  • 60
  • 3
    Doesn't the service run in session 0? – David Heffernan Nov 29 '18 at 09:21
  • new IntPtr(7) is very mysterious. Just delete the SetStdHandle() call and it will work fine. – Hans Passant Nov 29 '18 at 09:51
  • Erm... the code is intended to run it as a console from the debugger... – Jaster Nov 29 '18 at 09:53
  • @HansPassant nope, it does not. the output ends up in the visual studio output – Jaster Nov 29 '18 at 11:29
  • That is pretty normal if you use an old VS version. Disable the Visual Studio Hosting Process and try again. Project > Properties > Debug. – Hans Passant Nov 29 '18 at 11:49
  • I use 2017 pro (15.8.2 - not the most recent, but...) - so there is no option for the vshost proccess :) – Jaster Nov 29 '18 at 11:51
  • Checked on VS2017. My crystal ball says that you have a Console call in code that runs before RunService(), like Main(). – Hans Passant Dec 04 '18 at 14:13
  • Your ball is blurry today ;) I pased you the whole code. – Jaster Dec 04 '18 at 14:51
  • Have you had a look at https://stackoverflow.com/questions/15604014/no-console-output-when-using-allocconsole-and-target-architecture-x86 – Wyck Dec 05 '18 at 02:00
  • I just use AllocConsole(). It works in every config except debug mode, and this is by design : in debug mode, console output is captured by the debugger is displayed in Visual Studio's Output Window. Check this out: https://stackoverflow.com/a/47306894/403671 . Also, console keyboard input (ReadLine) still works (but w/o any output) – Simon Mourier Dec 05 '18 at 23:34

3 Answers3

2

I was able to reproduce your issue and get it working on my machine. Some of your code looks like it comes from the accepted answer at No console output when using AllocConsole and target architecture x86. If you read the comment thread under that answer, you will see that new IntPtr(7) does not work as of Windows 7/ Server 2012. The "new magic number" for Windows 7 also did not work for me. To solve this, I started down the path of porting the given c++ call from the comments into c#, which required some signature changes for the PInvokes (which were all copied and pasted from PInvoke.net, so they should be fine). The changes that I made are almost exclusively in the PInvoke code. Here is a full working code set:

Program.cs (unchanged):

static void Main()
{
    ServiceLauncher.RunService(() => new Service1());
}

ServiceLauncher.cs (unchanged):

public static void RunService(Func<ServiceBase> factory)
{
    if (Debugger.IsAttached)
    {
        Utils.AttachConsole();
        Console.Write($"Starting service ");
        var instance = factory();
        Console.WriteLine(instance.GetType().Name);
        //Invoke start Method
        Console.WriteLine("Press [ENTER] to exit");
        Console.ReadLine();
        //Stop service
    }
    else
    {
        ServiceBase.Run(factory());
    }
}

Utils.cs (1 change as documemented in comments):

public static void AttachConsole()
{
    var ret = NativeMethods.AllocConsole();
    IntPtr currentStdout = NativeMethods.GetStdHandle(NativeMethods.STD_OUTPUT_HANDLE);

    // IntPtr(7) was a dangerous assumption that doesn't work on current versions of Windows...
    //NativeMethods.SetStdHandle(NativeMethods.STD_OUTPUT_HANDLE, new IntPtr(7));

    // Instead, get the defaultStdOut using PInvoke
    SafeFileHandle defaultStdOut = NativeMethods.CreateFile("CONOUT$", EFileAccess.GenericRead | EFileAccess.GenericWrite, EFileShare.Write, IntPtr.Zero, ECreationDisposition.OpenExisting, 0, IntPtr.Zero);
    NativeMethods.SetStdHandle(NativeMethods.STD_OUTPUT_HANDLE, defaultStdOut.DangerousGetHandle());    // also seems dangerous... there may be an alternate signature for SetStdHandle that takes SafeFileHandle.

    TextWriter writer = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true };
    Console.SetOut(writer);
}

NativeMethods.cs (almost completely different - links and explanations given in comments). Enums included in this file (outside of the class scope) but can be moved to different files at your discretion:

internal static class NativeMethods
{
    // 0xFFFFFFF5 is not consistent with what I found...
    //internal const uint STD_OUTPUT_HANDLE = 0xFFFFFFF5; 

    // https://www.pinvoke.net/default.aspx/kernel32.getstdhandle
    internal const int STD_OUTPUT_HANDLE = -11;

    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool AllocConsole();

    // method signature changed per https://www.pinvoke.net/default.aspx/kernel32.getstdhandle
    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern IntPtr GetStdHandle(int nStdHandle);

    // method signature changed per https://www.pinvoke.net/default.aspx/kernel32.setstdhandle
    [DllImport("kernel32.dll")]
    internal static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    internal static extern SafeFileHandle CreateFile(
        string lpFileName,
        EFileAccess dwDesiredAccess,
        EFileShare dwShareMode,
        IntPtr lpSecurityAttributes,
        ECreationDisposition dwCreationDisposition,
        EFileAttributes dwFlagsAndAttributes,
        IntPtr hTemplateFile);
}


// ENUMS FROM http://www.pinvoke.net/default.aspx/kernel32/CreateFile.html
[Flags]
public enum EFileAccess : uint
{
    //
    // Standart Section
    //

    AccessSystemSecurity = 0x1000000,   // AccessSystemAcl access type
    MaximumAllowed = 0x2000000,     // MaximumAllowed access type

    Delete = 0x10000,
    ReadControl = 0x20000,
    WriteDAC = 0x40000,
    WriteOwner = 0x80000,
    Synchronize = 0x100000,

    StandardRightsRequired = 0xF0000,
    StandardRightsRead = ReadControl,
    StandardRightsWrite = ReadControl,
    StandardRightsExecute = ReadControl,
    StandardRightsAll = 0x1F0000,
    SpecificRightsAll = 0xFFFF,

    FILE_READ_DATA = 0x0001,        // file & pipe
    FILE_LIST_DIRECTORY = 0x0001,       // directory
    FILE_WRITE_DATA = 0x0002,       // file & pipe
    FILE_ADD_FILE = 0x0002,         // directory
    FILE_APPEND_DATA = 0x0004,      // file
    FILE_ADD_SUBDIRECTORY = 0x0004,     // directory
    FILE_CREATE_PIPE_INSTANCE = 0x0004, // named pipe
    FILE_READ_EA = 0x0008,          // file & directory
    FILE_WRITE_EA = 0x0010,         // file & directory
    FILE_EXECUTE = 0x0020,          // file
    FILE_TRAVERSE = 0x0020,         // directory
    FILE_DELETE_CHILD = 0x0040,     // directory
    FILE_READ_ATTRIBUTES = 0x0080,      // all
    FILE_WRITE_ATTRIBUTES = 0x0100,     // all

    //
    // Generic Section
    //

    GenericRead = 0x80000000,
    GenericWrite = 0x40000000,
    GenericExecute = 0x20000000,
    GenericAll = 0x10000000,

    SPECIFIC_RIGHTS_ALL = 0x00FFFF,
    FILE_ALL_ACCESS =
    StandardRightsRequired |
    Synchronize |
    0x1FF,

    FILE_GENERIC_READ =
    StandardRightsRead |
    FILE_READ_DATA |
    FILE_READ_ATTRIBUTES |
    FILE_READ_EA |
    Synchronize,

    FILE_GENERIC_WRITE =
    StandardRightsWrite |
    FILE_WRITE_DATA |
    FILE_WRITE_ATTRIBUTES |
    FILE_WRITE_EA |
    FILE_APPEND_DATA |
    Synchronize,

    FILE_GENERIC_EXECUTE =
    StandardRightsExecute |
        FILE_READ_ATTRIBUTES |
        FILE_EXECUTE |
        Synchronize
}

[Flags]
public enum EFileShare : uint
{
    /// <summary>
    /// 
    /// </summary>
    None = 0x00000000,
    /// <summary>
    /// Enables subsequent open operations on an object to request read access. 
    /// Otherwise, other processes cannot open the object if they request read access. 
    /// If this flag is not specified, but the object has been opened for read access, the function fails.
    /// </summary>
    Read = 0x00000001,
    /// <summary>
    /// Enables subsequent open operations on an object to request write access. 
    /// Otherwise, other processes cannot open the object if they request write access. 
    /// If this flag is not specified, but the object has been opened for write access, the function fails.
    /// </summary>
    Write = 0x00000002,
    /// <summary>
    /// Enables subsequent open operations on an object to request delete access. 
    /// Otherwise, other processes cannot open the object if they request delete access.
    /// If this flag is not specified, but the object has been opened for delete access, the function fails.
    /// </summary>
    Delete = 0x00000004
}

public enum ECreationDisposition : uint
{
    /// <summary>
    /// Creates a new file. The function fails if a specified file exists.
    /// </summary>
    New = 1,
    /// <summary>
    /// Creates a new file, always. 
    /// If a file exists, the function overwrites the file, clears the existing attributes, combines the specified file attributes, 
    /// and flags with FILE_ATTRIBUTE_ARCHIVE, but does not set the security descriptor that the SECURITY_ATTRIBUTES structure specifies.
    /// </summary>
    CreateAlways = 2,
    /// <summary>
    /// Opens a file. The function fails if the file does not exist. 
    /// </summary>
    OpenExisting = 3,
    /// <summary>
    /// Opens a file, always. 
    /// If a file does not exist, the function creates a file as if dwCreationDisposition is CREATE_NEW.
    /// </summary>
    OpenAlways = 4,
    /// <summary>
    /// Opens a file and truncates it so that its size is 0 (zero) bytes. The function fails if the file does not exist.
    /// The calling process must open the file with the GENERIC_WRITE access right. 
    /// </summary>
    TruncateExisting = 5
}

[Flags]
public enum EFileAttributes : uint
{
    Readonly = 0x00000001,
    Hidden = 0x00000002,
    System = 0x00000004,
    Directory = 0x00000010,
    Archive = 0x00000020,
    Device = 0x00000040,
    Normal = 0x00000080,
    Temporary = 0x00000100,
    SparseFile = 0x00000200,
    ReparsePoint = 0x00000400,
    Compressed = 0x00000800,
    Offline = 0x00001000,
    NotContentIndexed = 0x00002000,
    Encrypted = 0x00004000,
    Write_Through = 0x80000000,
    Overlapped = 0x40000000,
    NoBuffering = 0x20000000,
    RandomAccess = 0x10000000,
    SequentialScan = 0x08000000,
    DeleteOnClose = 0x04000000,
    BackupSemantics = 0x02000000,
    PosixSemantics = 0x01000000,
    OpenReparsePoint = 0x00200000,
    OpenNoRecall = 0x00100000,
    FirstPipeInstance = 0x00080000
}
Dave Smash
  • 2,941
  • 1
  • 18
  • 38
  • Wonderful! Thanks for the Job! Perfect solution. – Jaster Dec 06 '18 at 08:34
  • Thanks, I'm glad it helped! Good luck. – Dave Smash Dec 06 '18 at 14:50
  • Trying this solution, unfortunately i get File Handle errors (as soon something is accessing any File on the File-System) using this approach. Note: using it on a WinForms / Net6 App. The Conolse does appear in debug Mode. Using the old Approach AttachConsole(ATTACH_PARENT_PROCESS) doesn't work in Debug out of VS, but works without Debugging and without Errors. – Markus Doerig Dec 22 '21 at 02:04
0

I do this for every windows service that I develop. But usually I use a winform and even in some cases named pipes for being able to do more fancy stuff than watching the output in console.

That being said you don't have to do anything fancy to get console output for windows services.

1) Create a windows service project

2) change your Project output to Console application.

3) change the "Service1" class to this.

using System.ServiceProcess;

namespace WindowsService1
{
    public partial class Service1 : ServiceBase
    {
        readonly Runner _runner = new Runner();
        static void Main(string[] args)
        {
            var service = new Service1();
            if (Debugger.IsAttached)
            {
                service.OnStart(args);
                Console.WriteLine("Find the any key!");
                Console.Read();
                service.OnStop();
            }
            else
            {
                ServiceBase[] ServicesToRun;
                ServicesToRun = new ServiceBase[]
                {
                    service
                };
                ServiceBase.Run(ServicesToRun);
            }
        }

        public Service1()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            _runner.Run();
        }

        protected override void OnStop()
        {
            _runner.Stop();
        }
    }
}

Then in the Runner class you do what ever you are getting from your factory method.

Archlight
  • 2,019
  • 2
  • 21
  • 34
  • Putting the entry point into the service is really bad design. A console application is something differnt than a service, So I don't want to alloc a console when running as a service. The 'issue' with my solution is Visual Studio as it does some weird redirects which I can not overcome. Without VS attached everything runs as it should. – Jaster Dec 04 '18 at 14:07
  • I agree it is not good design, but it will work under all development environments. About the cost of allocating a console is not relevant because allocConsole is not done until your first Console.Writeline and I trust that the Microsoft Developers will make sure it works – Archlight Dec 05 '18 at 08:45
0

I've always found it easier to use Topshelf to debug service applications (http://topshelf-project.com/)

solidau
  • 4,021
  • 3
  • 24
  • 45
  • Topshelf is for people with lack of basic developing skills. – Jaster Dec 06 '18 at 08:36
  • @Jaster Not sure why you would say that. Its just a framework/wrapper that takes a lot of boilerplate and gruntwork out of scope such as installing and in your case, debugging. Care to elaborate? – solidau Dec 07 '18 at 01:12
  • 1. there is no "boilerplate" for installing services, just use installutil, which has to be part of any system running .net. Or create a "real" installer. – Jaster Dec 07 '18 at 08:22
  • 2. debugging a services does not require any additional "work", just attach to the process - even remote. – Jaster Dec 07 '18 at 08:23
  • 3. "my case" is about starting, which is not covered in a sufficent way by top shelf. – Jaster Dec 07 '18 at 08:23
  • 4. top shelf is a depdendency, which requires you to code in a different way. different means outside the .net standard by using their base clases and really stranges approaches for very simple tasks. there is not a single feature it offers, which is usefull and not aviailable out of the box. – Jaster Dec 07 '18 at 08:27