An Example:
If you have Visual Studio ( 2010 ) open and running, and then double click a misc *.cs file on your PC desktop, the file will open in the current running instance of Visual Studio, instead of opening another instance of VS.
How can I get my own C# program to mimic this behavior ?
In other words, if I have a file type such as *.myfile associated with my program, and the user double-clicks the *.myfile in Windows Explorer, and.... my program is already running..... it should open the file without Windows starting another instance of my program. If the program was not running, then Windows can start an instance normally.
Note that multiple instances of my program are allowed - same as Visual Studio.
Any suggestions would be appreciated !!
If you look at what is registered for for .cs file in registry you will see that it is not the Visual Studio. For Express edition e.g. the registered application is 'VCSExpress.exe' and the studio is running in in 'WDExpress.exe'. In advanced versions I think the studio runs as 'devenv.exe'. The interesting point is that there are two applications: your UI application and a kind of launcher application. I don't know how VS does it, but I could imagine this way: launcher communicates with UI by any kind of interprocess communication e.g. named pipes. (See here) Maybe try this:
Launcher application (your file extension is registered with it) tries to open a pipe to UI application as client.
If it fails, it starts a new instance of UI application and passes file name as parameter. UI application start server side of named pipe
If pipe is opened successful i.e. there is already running a UI instance, launcher sends file name to existing UI process via pipe.
Launcher exits after passing the job to UI
I made some project-template which implements this stuff, using windows-messaging.
The template is huge and contains some other stuff (like localization, updates, formclosing, clipboard, and an interface for documents, this way the actions in the MDI can be easily forwarded to the MDI-children).
If you want to view the template, try this link (or this link)
Some of the code:
Win32.cs:
public partial class Win32
{
//public const int WM_CLOSE = 16;
//public const int BN_CLICKED = 245;
public const int WM_COPYDATA = 0x004A;
public struct CopyDataStruct : IDisposable
{
public IntPtr dwData;
public int cbData;
public IntPtr lpData;
public void Dispose()
{
if (this.lpData != IntPtr.Zero)
{
LocalFree(this.lpData);
this.lpData = IntPtr.Zero;
}
}
}
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, ref CopyDataStruct lParam);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LocalAlloc(int flag, int size);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LocalFree(IntPtr p);
}
Program.cs:
static class Program
{
static Mutex mutex = new Mutex(true, guid());
static string guid()
{
// http://stackoverflow.com/questions/502303/how-do-i-programmatically-get-the-guid-of-an-application-in-net2-0
Assembly assembly = Assembly.GetExecutingAssembly();
var attribute = (GuidAttribute)assembly.GetCustomAttributes(typeof(GuidAttribute), true)[0];
return attribute.Value;
}
static int MainWindowHandle
{
get
{
return Settings.Default.hwnd;
}
set
{
Settings sett = Settings.Default;
sett.hwnd = value;
sett.Save();
}
}
public static string GetFileName()
{
ActivationArguments a = AppDomain.CurrentDomain.SetupInformation.ActivationArguments;
// aangeklikt bestand achterhalen
string[] args = a == null ? null : a.ActivationData;
return args == null ? "" : args[0];
}
[STAThread]
static void Main()
{
if (mutex.WaitOne(TimeSpan.Zero, true))
{
#region standaard
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
#endregion
#region Culture instellen
string cult = CultureInfo.CurrentCulture.Name;
Thread.CurrentThread.CurrentUICulture = new CultureInfo(cult);
Thread.CurrentThread.CurrentCulture = new CultureInfo(cult);
#endregion
MainForm frm = new MainForm();
MainWindowHandle = (int)frm.Handle;
Application.Run(frm);
MainWindowHandle = 0;
mutex.ReleaseMutex();
}
else
{
int hwnd = 0;
while (hwnd == 0)
{
Thread.Sleep(600);
hwnd = MainWindowHandle;
}
if (hwnd != 0)
{
Win32.CopyDataStruct cds = new Win32.CopyDataStruct();
try
{
string data = GetFileName();
cds.cbData = (data.Length + 1) * 2; // number of bytes
cds.lpData = Win32.LocalAlloc(0x40, cds.cbData); // known local-pointer in RAM
Marshal.Copy(data.ToCharArray(), 0, cds.lpData, data.Length); // Copy data to preserved local-pointer
cds.dwData = (IntPtr)1;
Win32.SendMessage((IntPtr)hwnd, Win32.WM_COPYDATA, IntPtr.Zero, ref cds);
}
finally
{
cds.Dispose();
}
}
}
}
}
MainFrom.cs:
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case Win32.WM_COPYDATA:
Win32.CopyDataStruct st = (Win32.CopyDataStruct)Marshal.PtrToStructure(m.LParam, typeof(Win32.CopyDataStruct));
string strData = Marshal.PtrToStringUni(st.lpData);
OpenFile(strData);
Activate();
break;
default:
// let the base class deal with it
base.WndProc(ref m);
break;
}
}
It even works when launching up to 15 files at once.
It's been almost 20 years since I had to do something like this, but IIRC, you do something like this:
Before anything else, create a Mailslot (or any other convenient IPC tool)
If you are asked to open a document of the type that should go to an existing instance and if there are no other Mailslots, you continue on
If there IS a Mailslot, you send the Mailslot an open message with the file info and then you exit.
Write code to respond to Mailslot open messages.
If you do the steps before you create windows, it should act like what you want.
Related
Is there a way to launch a C# application with the following features?
It determines by command-line parameters whether it is a windowed or console app
It doesn't show a console when it is asked to be windowed and doesn't show a GUI window when it is running from the console.
For example, myapp.exe /help would output to stdout on the console you used, but myapp.exe by itself would launch my Winforms or WPF user interface.
The best answers I know of so far involve having two separate exe and use IPC, but that feels really hacky.
What options do I have and trade-offs can I make to get the behavior described in the example above? I'm open to ideas that are Winform-specific or WPF-specific, too.
Make the app a regular windows app, and create a console on the fly if needed.
More details at this link (code below from there)
using System;
using System.Windows.Forms;
namespace WindowsApplication1 {
static class Program {
[STAThread]
static void Main(string[] args) {
if (args.Length > 0) {
// Command line given, display console
if ( !AttachConsole(-1) ) { // Attach to an parent process console
AllocConsole(); // Alloc a new console
}
ConsoleMain(args);
}
else {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
private static void ConsoleMain(string[] args) {
Console.WriteLine("Command line = {0}", Environment.CommandLine);
for (int ix = 0; ix < args.Length; ++ix)
Console.WriteLine("Argument{0} = {1}", ix + 1, args[ix]);
Console.ReadLine();
}
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool AllocConsole();
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool AttachConsole(int pid);
}
}
I basically do that the way depicted in Eric's answer, additionally I detach the console with FreeConsole and use the SendKeys command to get the command prompt back.
[DllImport("kernel32.dll")]
private static extern bool AllocConsole();
[DllImport("kernel32.dll")]
private static extern bool AttachConsole(int pid);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FreeConsole();
[STAThread]
static void Main(string[] args)
{
if (args.Length > 0 && (args[0].Equals("/?") || args[0].Equals("/help", StringComparison.OrdinalIgnoreCase)))
{
// get console output
if (!AttachConsole(-1))
AllocConsole();
ShowHelp(); // show help output with Console.WriteLine
FreeConsole(); // detach console
// get command prompt back
System.Windows.Forms.SendKeys.SendWait("{ENTER}");
return;
}
// normal winforms code
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
Write two apps (one console, one windows) and then write another smaller app which based on the parameters given opens up one of the other apps (and then would presumably close itself since it would no longer be needed)?
I've done this by creating two separate apps.
Create the WPF app with this name: MyApp.exe. And create the console app with this name: MyApp.com. When you type your app name in the command line like this MyApp or MyApp /help (without .exe extension) the console app with the .com extension will take precedence. You can have your console application invoke the MyApp.exe according to the parameters.
This is exactly how devenv behaves. Typing devenv at the command line will launch Visual Studio's IDE. If you pass parameters like /build, it will remain in the command line.
NOTE: I haven't tested this, but I believe it would work...
You could do this:
Make your app a windows forms application. If you get a request for console, don't show your main form. Instead, use platform invoke to call into the Console Functions in the Windows API and allocate a console on the fly.
(Alternatively, use the API to hide the console in a console app, but you'd probably see the console "flicker" as it was created in this case...)
As far as I am aware there is a flag in the exe that tells it whether to run as console or windowed app. You can flick the flag with tools that come with Visual Studio, but you cann't do this at runtime.
If the exe is compiled as a console, then it will always open a new console if its not started from one.
If the the exe is an application then it can't output to the console. You can spawn a separate console - but it won't behave like a console app.
I the past we have used 2 separate exe's. The console one being a thin wrapper over the forms one (you can reference an exe as you would reference a dll, and you can use the [assembly:InternalsVisibleTo("cs_friend_assemblies_2")] attribute to trust the console one, so you don't have to expose more than you need to).
I would create a solution that is a Windows Form App since there are two functions you can call that will hook into the current console. So you can treat the program like a console program. or by default you can launch the GUI.
The AttachConsole function will not create a new console. For more information about AttachConsole, check out PInvoke: AttachConsole
Below a sample program of how to use it.
using System.Runtime.InteropServices;
namespace Test
{
/// <summary>
/// This function will attach to the console given a specific ProcessID for that Console, or
/// the program will attach to the console it was launched if -1 is passed in.
/// </summary>
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FreeConsole();
[STAThread]
public static void Main()
{
Application.ApplicationExit +=new EventHandler(Application_ApplicationExit);
string[] commandLineArgs = System.Environment.GetCommandLineArgs();
if(commandLineArgs[0] == "-cmd")
{
//attaches the program to the running console to map the output
AttachConsole(-1);
}
else
{
//Open new form and do UI stuff
Form f = new Form();
f.ShowDialog();
}
}
/// <summary>
/// Handles the cleaning up of resources after the application has been closed
/// </summary>
/// <param name="sender"></param>
public static void Application_ApplicationExit(object sender, System.EventArgs e)
{
FreeConsole();
}
}
One way to do this is to write a Window app that doesn't show a window if the command line arguments indicate it shouldn't.
You can always get the command line arguments and check them before showing the first window.
The important thing to remember to do after AttachConsole() or AllocConsole() calls to get it to work in all cases is:
if (AttachConsole(ATTACH_PARENT_PROCESS))
{
System.IO.StreamWriter sw =
new System.IO.StreamWriter(System.Console.OpenStandardOutput());
sw.AutoFlush = true;
System.Console.SetOut(sw);
System.Console.SetError(sw);
}
I have found that works with or without VS hosting process. With output being sent with System.Console.WriteLine or System.Console.out.WriteLine before call To AttachConsole or AllocConsole. I have included my method below:
public static bool DoConsoleSetep(bool ClearLineIfParentConsole)
{
if (GetConsoleWindow() != System.IntPtr.Zero)
{
return true;
}
if (AttachConsole(ATTACH_PARENT_PROCESS))
{
System.IO.StreamWriter sw = new System.IO.StreamWriter(System.Console.OpenStandardOutput());
sw.AutoFlush = true;
System.Console.SetOut(sw);
System.Console.SetError(sw);
ConsoleSetupWasParentConsole = true;
if (ClearLineIfParentConsole)
{
// Clear command prompt since windows thinks we are a windowing app
System.Console.CursorLeft = 0;
char[] bl = System.Linq.Enumerable.ToArray<char>(System.Linq.Enumerable.Repeat<char>(' ', System.Console.WindowWidth - 1));
System.Console.Write(bl);
System.Console.CursorLeft = 0;
}
return true;
}
int Error = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
if (Error == ERROR_ACCESS_DENIED)
{
if (log.IsDebugEnabled) log.Debug("AttachConsole(ATTACH_PARENT_PROCESS) returned ERROR_ACCESS_DENIED");
return true;
}
if (Error == ERROR_INVALID_HANDLE)
{
if (AllocConsole())
{
System.IO.StreamWriter sw = new System.IO.StreamWriter(System.Console.OpenStandardOutput());
sw.AutoFlush = true;
System.Console.SetOut(sw);
System.Console.SetError(sw);
return true;
}
}
return false;
}
I also called this when I was done in case I needed command prompt to redisplay when I was done doing output.
public static void SendConsoleInputCR(bool UseConsoleSetupWasParentConsole)
{
if (UseConsoleSetupWasParentConsole && !ConsoleSetupWasParentConsole)
{
return;
}
long LongNegOne = -1;
System.IntPtr NegOne = new System.IntPtr(LongNegOne);
System.IntPtr StdIn = GetStdHandle(STD_INPUT_HANDLE);
if (StdIn == NegOne)
{
return;
}
INPUT_RECORD[] ira = new INPUT_RECORD[2];
ira[0].EventType = KEY_EVENT;
ira[0].KeyEvent.bKeyDown = true;
ira[0].KeyEvent.wRepeatCount = 1;
ira[0].KeyEvent.wVirtualKeyCode = 0;
ira[0].KeyEvent.wVirtualScanCode = 0;
ira[0].KeyEvent.UnicodeChar = '\r';
ira[0].KeyEvent.dwControlKeyState = 0;
ira[1].EventType = KEY_EVENT;
ira[1].KeyEvent.bKeyDown = false;
ira[1].KeyEvent.wRepeatCount = 1;
ira[1].KeyEvent.wVirtualKeyCode = 0;
ira[1].KeyEvent.wVirtualScanCode = 0;
ira[1].KeyEvent.UnicodeChar = '\r';
ira[1].KeyEvent.dwControlKeyState = 0;
uint recs = 2;
uint zero = 0;
WriteConsoleInput(StdIn, ira, recs, out zero);
}
Hope this helps...
No 1 is easy.
No 2 can't be done, I don't think.
The docs say:
Calls to methods such as Write and WriteLine have no effect in Windows applications.
The System.Console class is initialized differently in console and GUI applications. You can verify this by looking at the Console class in the debugger in each application type. Not sure if there's any way to re-initialize it.
Demo:
Create a new Windows Forms app, then replace the Main method with this:
static void Main(string[] args)
{
if (args.Length == 0)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
else
{
Console.WriteLine("Console!\r\n");
}
}
The idea is that any command line parameters will print to the console and exit. When you run it with no arguments, you get the window. But when you run it with a command line argument, nothing happens.
Then select the project properties, change the project type to "Console Application", and recompile. Now when you run it with an argument, you get "Console!" like you want. And when you run it (from the command line) with no arguments, you get the window. But the command prompt won't return until you exit the program. And if you run the program from Explorer, a command window will open and then you get a window.
I have worked out a way to do this including using stdin, but I must warn you that it is not pretty.
The problem with using stdin from an attached console is that the shell will also read from it. This causes the input to sometimes go to your app but sometimes to the shell.
The solution is to block the shell for the duration of the apps lifetime (although technically you could try to block it only when you need input). The way I choose to do this is by sending keystrokes to the shell to run a powershell command that waits for the app to terminate.
Incidentally this also fixes the problem of the prompt not getting back after the app terminates.
I have briefly attempted to get it to work from the powershell console as well. The same principles apply, but I didn't get it to execute my command. It might be that powershell has some security checks to prevent running commands from other applications. Because I don't use powershell much I didn't look into it.
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AllocConsole();
[DllImport("kernel32", SetLastError = true)]
private static extern bool AttachConsole(int dwProcessId);
private const uint STD_INPUT_HANDLE = 0xfffffff6;
private const uint STD_OUTPUT_HANDLE = 0xfffffff5;
private const uint STD_ERROR_HANDLE = 0xfffffff4;
[DllImport("kernel32.dll")]
private static extern IntPtr GetStdHandle(uint nStdHandle);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern int SetStdHandle(uint nStdHandle, IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern int GetConsoleProcessList(int[] ProcessList, int ProcessCount);
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
/// <summary>
/// Attach to existing console or create new. Must be called before using System.Console.
/// </summary>
/// <returns>Return true if console exists or is created.</returns>
public static bool InitConsole(bool createConsole = false, bool suspendHost = true) {
// first try to attach to an existing console
if (AttachConsole(-1)) {
if (suspendHost) {
// to suspend the host first try to find the parent
var processes = GetConsoleProcessList();
Process host = null;
string blockingCommand = null;
foreach (var proc in processes) {
var netproc = Process.GetProcessById(proc);
var processName = netproc.ProcessName;
Console.WriteLine(processName);
if (processName.Equals("cmd", StringComparison.OrdinalIgnoreCase)) {
host = netproc;
blockingCommand = $"powershell \"& wait-process -id {Process.GetCurrentProcess().Id}\"";
} else if (processName.Equals("powershell", StringComparison.OrdinalIgnoreCase)) {
host = netproc;
blockingCommand = $"wait-process -id {Process.GetCurrentProcess().Id}";
}
}
if (host != null) {
// if a parent is found send keystrokes to simulate a command
var cmdWindow = host.MainWindowHandle;
if (cmdWindow == IntPtr.Zero) Console.WriteLine("Main Window null");
foreach (char key in blockingCommand) {
SendChar(cmdWindow, key);
System.Threading.Thread.Sleep(1); // required for powershell
}
SendKeyDown(cmdWindow, Keys.Enter);
// i haven't worked out how to get powershell to accept a command, it might be that this is a security feature of powershell
if (host.ProcessName == "powershell") Console.WriteLine("\r\n *** PRESS ENTER ***");
}
}
return true;
} else if (createConsole) {
return AllocConsole();
} else {
return false;
}
}
private static void SendChar(IntPtr cmdWindow, char k) {
const uint WM_CHAR = 0x0102;
IntPtr result = PostMessage(cmdWindow, WM_CHAR, ((IntPtr)k), IntPtr.Zero);
}
private static void SendKeyDown(IntPtr cmdWindow, Keys k) {
const uint WM_KEYDOWN = 0x100;
const uint WM_KEYUP = 0x101;
IntPtr result = SendMessage(cmdWindow, WM_KEYDOWN, ((IntPtr)k), IntPtr.Zero);
System.Threading.Thread.Sleep(1);
IntPtr result2 = SendMessage(cmdWindow, WM_KEYUP, ((IntPtr)k), IntPtr.Zero);
}
public static int[] GetConsoleProcessList() {
int processCount = 16;
int[] processList = new int[processCount];
// supposedly calling it with null/zero should return the count but it didn't work for me at the time
// limiting it to a fixed number if fine for now
processCount = GetConsoleProcessList(processList, processCount);
if (processCount <= 0 || processCount >= processList.Length) return null; // some sanity checks
return processList.Take(processCount).ToArray();
}
I have a console application that i would be scheduling to run via Windows Task Scheduler. Now one of my requirements is to have it run either as foreground or background process based on app.config setting. Is it possible to achieve?
You can run a console application in the background through code by using this code
string path = "C:\\myfile.bat";
string args = "";
ProcessStartInfo procInfo = new ProcessStartInfo(path, args);
procInfo.CreateNoWindow = false;
procInfo.UseShellExecute = true;
procInfo.WindowStyle = ProcessWindowStyle.Hidden;
Process procRun = Process.Start(procInfo);
procRun.WaitForExit();
To run this in the foreground change the WindowStyle line to
procInfo.WindowStyle = ProcessWindowStyle.Normal;
--- EDIT ---
I just remembered, you could just use the Windows API to hide the window.
You will require these namespaces:
using System.Linq;
using System.Runtime.InteropServices;
Then you can import these two functions:
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ShowWindow(IntPtr hWnd, uint nCmdShow);
[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();
GetConsoleWindow() will get the window handle to the current console window.
ShowWindow() is a WinAPI function that lets you show/hide/minimize/maximize/etc. windows by sending their window handles to it.
In your Main() method you can now just do like this whenever the -silent argument is passed:
static void Main(string[] args)
{
if (args.Length >= 1 && args.Any(s => s.Equals("-silent", StringComparison.OrdinalIgnoreCase))) //Case-insensitively iterate through all arguments and look for the arg "-silent".
{
ShowWindow(GetConsoleWindow(), 0); //0 is equal to SW_HIDE, which means hide the window.
}
}
So in the Task Scheduler, just make it start your application with the argument -silent, and it shouldn't display the window.
Hope this helps!
--- Old answer ---
In order to be able to start your application as a foreground/background application without the aid of a second application you must have some way of indicating to your app that you want it to start without a window.
Once your application starts, you could make it check for a certain argument that tells wether you want it to be in the background or not. Then you could make your application start a new instance of itself without no window, automatically closing the current instance afterwards.
Using System.Reflection.Assembly.GetExecutingAssembly() you are able to obtain the name and full path of the current executable via the CodeBase property. You can then just pass that along to an instance of the ProcessStartInfo class with the proper settings to not create a window.
This is an example of a Main() method doing so:
static void Main(string[] args)
{
if (args.Length >= 1 && args.Any(s => s.Equals("-silent", StringComparison.OrdinalIgnoreCase))) //Case-insensitively iterate through all arguments and look for the arg "-silent".
{
ProcessStartInfo psi = new ProcessStartInfo(System.Reflection.Assembly.GetExecutingAssembly().CodeBase); //Start this application again.
psi.CreateNoWindow = true; //Create no window (make it run in the background).
psi.WindowStyle = ProcessWindowStyle.Hidden; //Create no window (make it run in the background).
psi.WorkingDirectory = Process.GetCurrentProcess().StartInfo.WorkingDirectory; //Use the same working directory as this process.
Process.Start(psi); //Start the process.
return; //Stop execution, thus stopping the current process.
}
//...your normal code here...
}
Note that this code must be on the very top of your Main() method.
The only problem with this code is that in order to close the application (if it doesn't close itself) you must forcibly kill the process either via the task manager or a second application (yes, sadly).
foreach (Process myApp in Process.GetProcessesByName("myApplication")) //Your executable's name, without the ".exe" part.
{
myApp.Kill();
}
The new instance is also not in control by the Task Scheduler, meaning you cannot stop it from there either.
This isn't exactly a "task" but could be references as one. Right click on your project and go to properties. Inside of properties there should be an option inside of Application Tab -> Output type -> [switch it to "Windows Application"]:
using System;
using System.Windows.Forms;
namespace TestConsoleApp
{
public class Program
{
private const string GuiWinForm = "gui";
private const string GuiConsole = "console";
private const string BackgroundProcess = "bgp";
private const string Cmd = "cmd";
[DllImport("kernel32", SetLastError = true)]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
private static void LoadForm()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AllocConsole();
private static void CreateNewConsole()
{
AllocConsole();
Console.WriteLine("New Console App");
Console.ReadLine();
}
private static void LoadConsole()
{
// *** Gets Command Window if it is already open ***
var ptr = GetForegroundWindow();
int u;
GetWindowThreadProcessId(ptr, out u);
var process = Process.GetProcessById(u);
if (process.ProcessName == Cmd)
AttachConsole(process.Id);
// *** Creates a new Command Prompt if the foreground is not a console ***
else
CreateNewConsole();
}
// Get the args from either command line or from config file
[STAThread]
private static void Main(string[] args)
{
var mode = args.Length > 0 ? args[0] : Gui;
switch (mode)
{
case Gui:
LoadForm();
break;
/* If for some reason you just cannot stand the
idea of using a winform for this task/process */
case GuiConsole:
LoadConsole();
break;
case BackgroundProcess:
// Code that will perform task
break;
}
}
}
}
To get stuck in straight away, a very basic example:
using System;
using System.Windows.Forms;
class test
{
static void Main()
{
Console.WriteLine("test");
MessageBox.Show("test");
}
}
If I compile this with default options (using csc at command line), as expected, it will compile to a console application. Also, because I imported System.Windows.Forms, it will also show a message box.
Now, if I use the option /target:winexe, which I think is the same as choosing Windows Application from within project options, as expected I will only see the Message Box and no console output.
(In fact, the moment it is launched from command line, I can issue the next command before the application has even completed).
So, my question is - I know that you can have "windows"/forms output from a console application, but is there anyway to show the console from a Windows application?
this one should work.
using System.Runtime.InteropServices;
private void Form1_Load(object sender, EventArgs e)
{
AllocConsole();
}
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool AllocConsole();
Perhaps this is over-simplistic...
Create a Windows Form project...
Then: Project Properties -> Application -> Output Type -> Console Application
Then can have Console and Forms running together, works for me
If you are not worrying about opening a console on-command, you can go into the properties for your project and change it to Console Application
.
This will still show your form as well as popping up a console window. You can't close the console window, but it works as an excellent temporary logger for debugging.
Just remember to turn it back off before you deploy the program.
You can call AttachConsole using pinvoke to get a console window attached to a WinForms project: http://www.csharp411.com/console-output-from-winforms-application/
You may also want to consider Log4net ( http://logging.apache.org/log4net/index.html ) for configuring log output in different configurations.
Create a Windows Forms Application, and change the output type to Console.
It will result in both a console and the form to open.
This worked for me, to pipe the output to a file.
Call the console with
cmd /c "C:\path\to\your\application.exe" > myfile.txt
Add this code to your application.
[DllImport("kernel32.dll")]
static extern bool AttachConsole(UInt32 dwProcessId);
[DllImport("kernel32.dll")]
private static extern bool GetFileInformationByHandle(
SafeFileHandle hFile,
out BY_HANDLE_FILE_INFORMATION lpFileInformation
);
[DllImport("kernel32.dll")]
private static extern SafeFileHandle GetStdHandle(UInt32 nStdHandle);
[DllImport("kernel32.dll")]
private static extern bool SetStdHandle(UInt32 nStdHandle, SafeFileHandle hHandle);
[DllImport("kernel32.dll")]
private static extern bool DuplicateHandle(
IntPtr hSourceProcessHandle,
SafeFileHandle hSourceHandle,
IntPtr hTargetProcessHandle,
out SafeFileHandle lpTargetHandle,
UInt32 dwDesiredAccess,
Boolean bInheritHandle,
UInt32 dwOptions
);
private const UInt32 ATTACH_PARENT_PROCESS = 0xFFFFFFFF;
private const UInt32 STD_OUTPUT_HANDLE = 0xFFFFFFF5;
private const UInt32 STD_ERROR_HANDLE = 0xFFFFFFF4;
private const UInt32 DUPLICATE_SAME_ACCESS = 2;
struct BY_HANDLE_FILE_INFORMATION
{
public UInt32 FileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime;
public UInt32 VolumeSerialNumber;
public UInt32 FileSizeHigh;
public UInt32 FileSizeLow;
public UInt32 NumberOfLinks;
public UInt32 FileIndexHigh;
public UInt32 FileIndexLow;
}
static void InitConsoleHandles()
{
SafeFileHandle hStdOut, hStdErr, hStdOutDup, hStdErrDup;
BY_HANDLE_FILE_INFORMATION bhfi;
hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
hStdErr = GetStdHandle(STD_ERROR_HANDLE);
// Get current process handle
IntPtr hProcess = Process.GetCurrentProcess().Handle;
// Duplicate Stdout handle to save initial value
DuplicateHandle(hProcess, hStdOut, hProcess, out hStdOutDup,
0, true, DUPLICATE_SAME_ACCESS);
// Duplicate Stderr handle to save initial value
DuplicateHandle(hProcess, hStdErr, hProcess, out hStdErrDup,
0, true, DUPLICATE_SAME_ACCESS);
// Attach to console window – this may modify the standard handles
AttachConsole(ATTACH_PARENT_PROCESS);
// Adjust the standard handles
if (GetFileInformationByHandle(GetStdHandle(STD_OUTPUT_HANDLE), out bhfi))
{
SetStdHandle(STD_OUTPUT_HANDLE, hStdOutDup);
}
else
{
SetStdHandle(STD_OUTPUT_HANDLE, hStdOut);
}
if (GetFileInformationByHandle(GetStdHandle(STD_ERROR_HANDLE), out bhfi))
{
SetStdHandle(STD_ERROR_HANDLE, hStdErrDup);
}
else
{
SetStdHandle(STD_ERROR_HANDLE, hStdErr);
}
}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
// initialize console handles
InitConsoleHandles();
if (args.Length != 0)
{
if (args[0].Equals("waitfordebugger"))
{
MessageBox.Show("Attach the debugger now");
}
if (args[0].Equals("version"))
{
#if DEBUG
String typeOfBuild = "d";
#else
String typeOfBuild = "r";
#endif
String output = typeOfBuild + Assembly.GetExecutingAssembly()
.GetName().Version.ToString();
//Just for the fun of it
Console.Write(output);
Console.Beep(4000, 100);
Console.Beep(2000, 100);
Console.Beep(1000, 100);
Console.Beep(8000, 100);
return;
}
}
}
I found this code here: http://www.csharp411.com/console-output-from-winforms-application/
I thought is was worthy to post it here as well.
There are basically two things that can happen here.
Console output
It is possible for a winforms program to attach itself to the console window that created it (or to a different console window, or indeed to a new console window if desired). Once attached to the console window Console.WriteLine() etc works as expected. One gotcha to this approach is that the program returns control to the console window immediately, and then carries on writing to it, so the user can also type away in the console window. You can use start with the /wait parameter to handle this I think.
Link to start Command syntax
Redirected console output
This is when someone pipes the output from your program somewhere else, eg.
yourapp > file.txt
Attaching to a console window in this case effectively ignores the piping. To make this work you can call Console.OpenStandardOutput() to get a handle to the stream that the output should be piped to. This only works if the output is piped, so if you want to handle both of the scenarios you need to open the standard output and write to it and attach to the console window. This does mean that the output is sent to the console window and to the pipe but its the best solution I could find. Below the code I use to do this.
// This always writes to the parent console window and also to a redirected stdout if there is one.
// It would be better to do the relevant thing (eg write to the redirected file if there is one, otherwise
// write to the console) but it doesn't seem possible.
public class GUIConsoleWriter : IConsoleWriter
{
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool AttachConsole(int dwProcessId);
private const int ATTACH_PARENT_PROCESS = -1;
StreamWriter _stdOutWriter;
// this must be called early in the program
public GUIConsoleWriter()
{
// this needs to happen before attachconsole.
// If the output is not redirected we still get a valid stream but it doesn't appear to write anywhere
// I guess it probably does write somewhere, but nowhere I can find out about
var stdout = Console.OpenStandardOutput();
_stdOutWriter = new StreamWriter(stdout);
_stdOutWriter.AutoFlush = true;
AttachConsole(ATTACH_PARENT_PROCESS);
}
public void WriteLine(string line)
{
_stdOutWriter.WriteLine(line);
Console.WriteLine(line);
}
}
//From your application set the Console to write to your RichTextkBox
//object:
Console.SetOut(new RichTextBoxWriter(yourRichTextBox));
//To ensure that your RichTextBox object is scrolled down when its text is
//changed add this event:
private void yourRichTextBox_TextChanged(object sender, EventArgs e)
{
yourRichTextBox.SelectionStart = yourRichTextBox.Text.Length;
yourRichTextBox.ScrollToCaret();
}
public delegate void StringArgReturningVoidDelegate(string text);
public class RichTextBoxWriter : TextWriter
{
private readonly RichTextBox _richTextBox;
public RichTextBoxWriter(RichTextBox richTexttbox)
{
_richTextBox = richTexttbox;
}
public override void Write(char value)
{
SetText(value.ToString());
}
public override void Write(string value)
{
SetText(value);
}
public override void WriteLine(char value)
{
SetText(value + Environment.NewLine);
}
public override void WriteLine(string value)
{
SetText(value + Environment.NewLine);
}
public override Encoding Encoding => Encoding.ASCII;
//Write to your UI object in thread safe way:
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (_richTextBox.InvokeRequired)
{
var d = new StringArgReturningVoidDelegate(SetText);
_richTextBox.Invoke(d, text);
}
else
{
_richTextBox.Text += text;
}
}
}
Building on Chaz's answer, in .NET 5 there is a breaking change, so two modifications are required in the project file, i.e. changing OutputType and adding DisableWinExeOutputInference. Example:
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0-windows10.0.17763.0</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<DisableWinExeOutputInference>true</DisableWinExeOutputInference>
<Platforms>AnyCPU;x64;x86</Platforms>
</PropertyGroup>
using System;
using System.Runtime.InteropServices;
namespace SomeProject
{
class GuiRedirect
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetStdHandle(StandardHandle nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetStdHandle(StandardHandle nStdHandle, IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern FileType GetFileType(IntPtr handle);
private enum StandardHandle : uint
{
Input = unchecked((uint)-10),
Output = unchecked((uint)-11),
Error = unchecked((uint)-12)
}
private enum FileType : uint
{
Unknown = 0x0000,
Disk = 0x0001,
Char = 0x0002,
Pipe = 0x0003
}
private static bool IsRedirected(IntPtr handle)
{
FileType fileType = GetFileType(handle);
return (fileType == FileType.Disk) || (fileType == FileType.Pipe);
}
public static void Redirect()
{
if (IsRedirected(GetStdHandle(StandardHandle.Output)))
{
var initialiseOut = Console.Out;
}
bool errorRedirected = IsRedirected(GetStdHandle(StandardHandle.Error));
if (errorRedirected)
{
var initialiseError = Console.Error;
}
AttachConsole(-1);
if (!errorRedirected)
SetStdHandle(StandardHandle.Error, GetStdHandle(StandardHandle.Output));
}
}
Setting the output type as Console in the project properties will give you a Console application along with the form you created.
if what you want is simple debug output the following works for me. I am using VS 2022 programming in C#
add "using System.Diagnostics"
then
Debug.WriteLine("*****");
Debug.WriteLine(...);
Debug.WriteLine("");
THe output appears in the debug console of VS2022. There is a lot of stuff there so I use the Debug.WriteLine("*****") and Debug.WriteLine("") to help me find my output. You can also clear the debug output after start up.
I am still working but running under VS there is no output when running wo debugging
Why not just leave it as a Window Forms app, and create a simple form to mimic the Console. The form can be made to look just like the black-screened Console, and have it respond directly to key press.
Then, in the program.cs file, you decide whether you need to Run the main form or the ConsoleForm. For example, I use this approach to capture the command line arguments in the program.cs file. I create the ConsoleForm, initially hide it, then pass the command line strings to an AddCommand function in it, which displays the allowed commands. Finally, if the user gave the -h or -? command, I call the .Show on the ConsoleForm and when the user hits any key on it, I shut down the program. If the user doesn't give the -? command, I close the hidden ConsoleForm and Run the main form.
You can any time switch between type of applications, to console or windows. So, you will not write special logic to see the stdout. Also, when running application in debugger, you will see all the stdout in output window. You might also just add a breakpoint, and in breakpoint properties change "When Hit...", you can output any messages, and variables. Also you can check/uncheck "Continue execution", and your breakpoint will become square shaped. So, the breakpoint messages without changhing anything in the application in the debug output window.
When a button is clicked in our application, it downloads a file from the server to the client and opens it for the user to view. We allow the user to select the application to open that file with, but the standard box offers an "Always use the selected program to open this kind of file" option. Unofrtunately ticking this makes no difference and the .rtf file defaults to opening in Word again next time.
How do we get this setting to store and retreive correctly?
The code that we're using to display this window is as follows:
//Ask the user what application they want to open the file in.
if (strFileName != "" && File.Exists(strFileName))
{
// Call Windows "Open With" dialog
CoreUtilities.ShowOpenFileDialog(strFileName);
}
Many thanks
Colin
Here's a code snippet that calls Windows's Open with... dialog,
Unless CoreUtilities.ShowOpenFileDialog is already implemented using this approach, maybe you should give it a hit:
[Serializable]
public struct ShellExecuteInfo
{
public int Size;
public uint Mask;
public IntPtr hwnd;
public string Verb;
public string File;
public string Parameters;
public string Directory;
public uint Show;
public IntPtr InstApp;
public IntPtr IDList;
public string Class;
public IntPtr hkeyClass;
public uint HotKey;
public IntPtr Icon;
public IntPtr Monitor;
}
// Code For OpenWithDialog Box
[DllImport("shell32.dll", SetLastError = true)]
extern public static bool
ShellExecuteEx(ref ShellExecuteInfo lpExecInfo);
public const uint SW_NORMAL = 1;
static void OpenAs(string file)
{
ShellExecuteInfo sei = new ShellExecuteInfo();
sei.Size = Marshal.SizeOf(sei);
sei.Verb = "openas";
sei.File = file;
sei.Show = SW_NORMAL;
if (!ShellExecuteEx(ref sei))
throw new System.ComponentModel.Win32Exception();
}
There is a dirty hack, but I don't know if you'll like it :)
You could delete stored assoc in registry before calling CoreUtilities.ShowOpenFileDialog() every time.
Here's the path in registry
HKEY_CURRENT_USER/Software/Microsoft/Windows/CurrentVersion/Explorer/FileExts
Or you can try running
System.Diagnostics.Process.Start(path);
This will always use the default program. Or show the open with dialog when needed (when there's no default program associated)
Have you tried opening the file using the "open" verb?
public static void displayLabel(string labelFileName)
{
System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo(labelFileName);
info.UseShellExecute = true;
info.Verb = "open";
System.Diagnostics.Process.Start(info);
}
I use the code above to open up files. It will open the file with the default application you have assigned for the given extension. For example if called with a filename of .pdf it would open in Acrobat, where as if you passed in a .txt it would open in Notepad.
I have developed a GUI test framework that does integrationtesting of our company website on a scheduled basis. When something fails, it'll take a screenshot of the desktop, among other things. This runs unattended on a logged in user on a dedicated Windows Server 2008.
The problem is that taking a screenshot on a desktop that I have disconnected my remote desktop session from. I get the following exception:
System.ComponentModel.Win32Exception (0x80004005): The handle is invalid
at System.Drawing.Graphics.CopyFromScreen(Int32 sourceX, Int32 sourceY, Int32 destinationX, Int32 destinationY, Size blockRegionSize, CopyPixelOperation copyPixelOperation)
at System.Drawing.Graphics.CopyFromScreen(Point upperLeftSource, Point upperLeftDestination, Size blockRegionSize)
at IntegrationTester.TestCaseRunner.TakeScreenshot(String name) in C:\VS2010\IntegrationTester\IntegrationTester\Config\TestCaseRunner.cs:line 144
at IntegrationTester.TestCaseRunner.StartTest() in C:\VS2010\IntegrationTester\IntegrationTester\Config\TestCaseRunner.cs:line 96
The TakeScreenshot() method looks like this:
public static void TakeScreenshot(string name)
{
var bounds = Screen.GetBounds(Point.Empty);
using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
}
bitmap.Save("someFileName", ImageFormat.Jpeg);
}
}
I have made sure that screensaver is set to "None" with no timeout.
I have also implemented a piece of code that does a couple of pinvokes to send a mouse move, hoping it would generate a desktop graphics handle.. but no.
IntPtr hWnd = GetForegroundWindow();
if (hWnd != IntPtr.Zero)
SendMessage(hWnd, 0x200, IntPtr.Zero, IntPtr.Zero);
Any advice is appreciated.
In order to capture the screen you need to run a program in the session of an user. That is because without the user there is no way to have a desktop associated.
To solve this you can run a desktop application to take the image, this application can be invoked in the session of the active user, this can be done from a service.
The code below allows you to invoke an desktop application in such way that it run on the local user's desktop.
If you need to execute as a particular user, check the code in article Allow service to interact with desktop? Ouch.. You can also consider using the function LogonUser.
The code:
public void Execute()
{
IntPtr sessionTokenHandle = IntPtr.Zero;
try
{
sessionTokenHandle = SessionFinder.GetLocalInteractiveSession();
if (sessionTokenHandle != IntPtr.Zero)
{
ProcessLauncher.StartProcessAsUser("Executable Path", "Command Line", "Working Directory", sessionTokenHandle);
}
}
catch
{
//What are we gonna do?
}
finally
{
if (sessionTokenHandle != IntPtr.Zero)
{
NativeMethods.CloseHandle(sessionTokenHandle);
}
}
}
internal static class SessionFinder
{
private const int INT_ConsoleSession = -1;
internal static IntPtr GetLocalInteractiveSession()
{
IntPtr tokenHandle = IntPtr.Zero;
int sessionID = NativeMethods.WTSGetActiveConsoleSessionId();
if (sessionID != INT_ConsoleSession)
{
if (!NativeMethods.WTSQueryUserToken(sessionID, out tokenHandle))
{
throw new System.ComponentModel.Win32Exception();
}
}
return tokenHandle;
}
}
internal static class ProcessLauncher
{
internal static void StartProcessAsUser(string executablePath, string commandline, string workingDirectory, IntPtr sessionTokenHandle)
{
var processInformation = new NativeMethods.PROCESS_INFORMATION();
try
{
var startupInformation = new NativeMethods.STARTUPINFO();
startupInformation.length = Marshal.SizeOf(startupInformation);
startupInformation.desktop = string.Empty;
bool result = NativeMethods.CreateProcessAsUser
(
sessionTokenHandle,
executablePath,
commandline,
IntPtr.Zero,
IntPtr.Zero,
false,
0,
IntPtr.Zero,
workingDirectory,
ref startupInformation,
ref processInformation
);
if (!result)
{
int error = Marshal.GetLastWin32Error();
string message = string.Format("CreateProcessAsUser Error: {0}", error);
throw new ApplicationException(message);
}
}
finally
{
if (processInformation.processHandle != IntPtr.Zero)
{
NativeMethods.CloseHandle(processInformation.processHandle);
}
if (processInformation.threadHandle != IntPtr.Zero)
{
NativeMethods.CloseHandle(processInformation.threadHandle);
}
if (sessionTokenHandle != IntPtr.Zero)
{
NativeMethods.CloseHandle(sessionTokenHandle);
}
}
}
}
internal static class NativeMethods
{
[DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
internal static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool CreateProcessAsUser(IntPtr tokenHandle, string applicationName, string commandLine, IntPtr processAttributes, IntPtr threadAttributes, bool inheritHandle, int creationFlags, IntPtr envrionment, string currentDirectory, ref STARTUPINFO startupInfo, ref PROCESS_INFORMATION processInformation);
[DllImport("Kernel32.dll", EntryPoint = "WTSGetActiveConsoleSessionId")]
internal static extern int WTSGetActiveConsoleSessionId();
[DllImport("WtsApi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool WTSQueryUserToken(int SessionId, out IntPtr phToken);
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr processHandle;
public IntPtr threadHandle;
public int processID;
public int threadID;
}
[StructLayout(LayoutKind.Sequential)]
internal struct STARTUPINFO
{
public int length;
public string reserved;
public string desktop;
public string title;
public int x;
public int y;
public int width;
public int height;
public int consoleColumns;
public int consoleRows;
public int consoleFillAttribute;
public int flags;
public short showWindow;
public short reserverd2;
public IntPtr reserved3;
public IntPtr stdInputHandle;
public IntPtr stdOutputHandle;
public IntPtr stdErrorHandle;
}
}
This code is a modification of the one found at the article Allow service to interact with desktop? Ouch. (MUST READ)
Addendum:
The code above allows to execute a program in the desktop of the user logged locally on the machine. This method specific for the current local user, but it is possible to do it for any user. Check the code at the article Allow service to interact with desktop? Ouch. for an example.
The core of this method is the function CreateProcessAsUser, you can find more about at MSDN.
Replace "Executable Path" with the path of the executable to run. Replace "Command Line" with the string passed as execution arguments, and replace "Working Directory" with the working directory you want. For example you can extract the folder of the executable path:
internal static string GetFolder(string path)
{
var folder = System.IO.Directory.GetParent(path).FullName;
if (!folder.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString()))
{
folder += System.IO.Path.DirectorySeparatorChar;
}
return folder;
}
If you have a service, you can use this code in the service to invoke a desktop application. That desktop application may also be the the service executable... for that you can use Assembly.GetExecutingAssembly().Location as the executable path. Then you can use System.Environment.UserInteractive to detect if the executable is not running as a service and pass as execution arguments information about the task needed to do. In the context of this answer that is to capture the screen (for example with CopyFromScreen), it could be something else.
What I did to solve this is call tscon.exe and tell it to redirect the session back to the console just before the screenshot is taken. It goes like this (note, this exact code is untested):
public static void TakeScreenshot(string path) {
try {
InternalTakeScreenshot(path);
} catch(Win32Exception) {
var winDir = System.Environment.GetEnvironmentVariable("WINDIR");
Process.Start(
Path.Combine(winDir, "system32", "tscon.exe"),
String.Format("{0} /dest:console", GetTerminalServicesSessionId()))
.WaitForExit();
InternalTakeScreenshot(path);
}
}
static void InternalTakeScreenshot(string path) {
var point = new System.Drawing.Point(0,0);
var bounds = System.Windows.Forms.Screen.GetBounds(point);
var size = new System.Drawing.Size(bounds.Width, bounds.Height);
var screenshot = new System.Drawing.Bitmap(bounds.Width, bounds.Height);
var g = System.Drawing.Graphics.FromImage(screenshot)
g.CopyFromScreen(0,0,0,0,size);
screenshot.Save(path, System.Drawing.Imaging.ImageFormat.Jpeg);
}
[DllImport("kernel32.dll")]
static extern bool ProcessIdToSessionId(uint dwProcessId, out uint pSessionId);
static uint GetTerminalServicesSessionId()
{
var proc = Process.GetCurrentProcess();
var pid = proc.Id;
var sessionId = 0U;
if(ProcessIdToSessionId((uint)pid, out sessionId))
return sessionId;
return 1U; // fallback, the console session is session 1
}
This is not a supported feature it is true that it works in XP and windows server 2003 however this is seen as security flaw.
To prevent this, don't use the 'x' to close the remote connection, but use %windir%\system32\tscon.exe 0 /dest:console instead. (That will insure that the screen isn't locked). - Nicolas Voron
It true that if you disconnect from the server in this way the "screen" wont be locked to ensure it stay unlocked you need to make sure you turn off the screen saver since as soon as that start up it will auto lock you screen.
There is quite a few examples only of people doing the same thing even here at stack overflow the post below suggest that you create a windows application that run under an actual user account that sends screen shots over IPC to the running service.
The correct way to get a custom GUI that works with a service is to
separate them into two processes and do some kind of IPC (inter
process communication). So the service will start-up when the machine
comes up and a GUI application will be started in the user session. In
that case the GUI can create a screenshot, send it to the service and
the service can do with it, whatever you like. - Screenshot of process under Windows Service
I have collated a few strategies I have found online that may give you some ideas.
Third party software
There is a lot of programs out there that make screen shots of web sites like http://www.websitescreenshots.com/ they have a user interface and command line tool. But if you are using some testing framework this might not work since it will make a new request to fetch all the assets and draw the page.
WebBrowser control
I am not sure what browser you are using to test you company web site however if you are not bothered about which browser It is you could use a WebBrowser control and use the DrawToBitmap method.
Virtualisation
I have seen a system where the developers were using virtual environments with the browser of their choice with all the settings done to make sure the machine didn't lock and if it did it would restart.
Selenium
It is also possible using selenium with the selenium-webdriver and headless a ruby gem developed by leonid-shevtsov if your test are in selenium this approach might be the best. Selenium itself support screen capture on the webdrivers they have available.
Of course all of this depends on what you are using for your testing framework if you can share some details on your setup we will be able to give you a better answer.
The problem seems to be that when you close the remote connection, the screen goes in a locked state which prevent the system to perform graphics operation like your g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
To prevent this, don't use the 'x' to close the remote connection, but use %windir%\system32\tscon.exe 0 /dest:console instead. (That will insure that the screen isn't locked).
Read this post for further informations (in VBA, but c#-understandable ;-) )
EDIT
If you want to do it directly in c#, try something like this :
Process p = new Process();
p.StartInfo.FileName = "tscon";
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
p.StartInfo.Arguments = "0 /dest:console";
p.Start();
I found a similar question Screen capture with C# and Remote Desktop problems. Hope it helps you solve the problem.
Here is the code from that answer:
public Image CaptureWindow(IntPtr handle)
{
// get te hDC of the target window
IntPtr hdcSrc = User32.GetWindowDC(handle);
// get the size
User32.RECT windowRect = new User32.RECT();
User32.GetWindowRect(handle, ref windowRect);
int width = windowRect.right - windowRect.left;
int height = windowRect.bottom - windowRect.top;
// create a device context we can copy to
IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
// create a bitmap we can copy it to,
// using GetDeviceCaps to get the width/height
IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc, width, height);
// select the bitmap object
IntPtr hOld = GDI32.SelectObject(hdcDest, hBitmap);
// bitblt over
GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, GDI32.SRCCOPY);
// restore selection
GDI32.SelectObject(hdcDest, hOld);
// clean up
GDI32.DeleteDC(hdcDest);
User32.ReleaseDC(handle, hdcSrc);
// get a .NET image object for it
Image img = Image.FromHbitmap(hBitmap);
// free up the Bitmap object
GDI32.DeleteObject(hBitmap);
return img;
}
I think the problem may be that you're on the wrong WindowStation. Have a look at these articles;
Why does print screen in a Windows Service return a black image?
Screen capture from windows service
It could be that your win-station is dissappearing when you disconnect. Are you running the application when you log in and then trying to leave it running when you disconnect?
If so, does it still do it if you connect with "mstsc /admin"? In other words, connecting to and running on the console session? If not, this might be a workaround.