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.
Related
I want to display a tiff file using shell execute. I am assuming the default app is the photoviewer. My Problem is that when i want to kill the process with photoviewer.Kill() i get an System.InvalidOperationException. When setting a breakpoint after photoViewer.Start() i realised that photoviewer does not conatain an Id.
I there a sufficent way to kill it? As it runs via dllhost.exe i do not want to retrun all processes named dllhost and kill them all since i do not know what else is run by dllhost.
Process photoViewer = new Process();
private void StartProcessUsingShellExecute(string filePath)
{
photoViewer.StartInfo = new ProcessStartInfo(filePath);
photoViewer.StartInfo.UseShellExecute = true;
photoViewer.Start();
}
I have another approach without shell execute but this approach seems to have dpi issues.
Approach without shell execute
Found a solution, may help anyone with a similar problem.
When i looked into the task manager i found that the windows 10 photoviewer runs detached from the application via dllhost. So since i have 4 dllhost processes up and running and just want to close the window. I do:
private void StartProcessAsShellExecute(string filePath)
{
photoViewer.StartInfo = new ProcessStartInfo(filePath);
photoViewer.StartInfo.UseShellExecute = true;
photoViewer.Start();
Process[] processes = Process.GetProcessesByName("dllhost");
foreach (Process p in processes)
{
IntPtr windowHandle = p.MainWindowHandle;
CloseWindow(windowHandle);
// do something with windowHandle
}
viewerOpen = true;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
//I'd double check this constant, just in case
static uint WM_CLOSE = 0x10;
public void CloseWindow(IntPtr hWindow)
{
SendMessage(hWindow, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
that closes all dllhost windows (of which i have just 1, the photoviewer)
My Programm which uses avicap32.dll for a Webcam Capture run on my Windows 10 dev computer without problems, but when I want to use the published version on another computer (Windows 7, no updates activated, offline) the program runs, but as soon I try to capture a picture from the camera, it doesn't work. I get an exception thrown and that's it. (Also I get a black image from the webcam, I double checked that the webcam is connected and accessible).
To add avicap32.dll I used the following code:
//This call is one of the most important and is vital to the operation of the OS.
[DllImport("user32", EntryPoint = "SendMessage")]
public static extern int SendMessage(int hWnd, uint Msg, int wParam, int lParam);
//This API creates the webcam instance so we can access it.
[DllImport("avicap32.dll", EntryPoint = "capCreateCaptureWindowA")]
public static extern int capCreateCaptureWindowA(string lpszWindowName, int dwStyle, int x, int y, int nWidth, int nHeight, int hwndParent, int nID);
//This API opens the clipboard so we can fetch webcam data.
[DllImport("user32", EntryPoint = "OpenClipboard")]
public static extern int OpenClipboard(int hWnd);
//This API cleans the clipboard.
[DllImport("user32", EntryPoint = "EmptyClipboard")]
public static extern int EmptyClipboard();
//This API closes the clipboard after use.
[DllImport("user32", EntryPoint = "CloseClipboard")]
public static extern int CloseClipboard();
//This API retrieves the data from the clipboard for use.
[DllImport("user32.dll")]
extern static IntPtr GetClipboardData(uint uFormat);
//This API is needed to execute the picture indication
[DllImport("cvextern.dll")]
extern static int MyFunction()
This is the function that captures the image and saves it:
public void Capture_Image() {
try {
ImageSize();
m_CapHwnd = capCreateCaptureWindowA("WebCap", 0, 0, 0, m_Width, m_Height, Handle.ToInt32(), 0);
SendMessage(m_CapHwnd, WM_CAP_CONNECT, 0, 0);
SendMessage(m_CapHwnd, WM_CAP_GT_FRAME, 0, 0);
SendMessage(m_CapHwnd, WM_CAP_COPY, 0, 0);
OpenClipboard(m_CapHwnd);
CloseClipboard();
SendMessage(m_CapHwnd, WM_CAP_DISCONNECT, 0, 0);
Image tempImg = (Bitmap)Clipboard.GetData("Bitmap");
pictureBox2.Image = tempImg;
pictureBox2.Refresh();
GC.Collect();
GC.WaitForPendingFinalizers();
fileName = "C:/FHT59N3/Bildanalyse_Projekt/image.jpg";
Clipboard.GetImage().Save(fileName, ImageFormat.Jpeg);
}
catch (NullReferenceException) {
string message = "No Camera found";
string title = "Please connect Camera";
MessageBoxButtons buttons = MessageBoxButtons.OK;
MessageBox.Show(message, title, buttons, MessageBoxIcon.Warning);
}
}
Are there special settings I have to set to make it run on Windows 7?
This is not a solution to your problem per se, but it's too long to fit in the comment box either.
You might want to consider removing
GC.Collect();
GC.WaitForPendingFinalizers();
as it's almost always better to use SafeHandles where possible and / or the IDisposable pattern when dealing with unmanaged resources.
GC.WaitForPendingFinalizers(); blocks program execution until all finalizers are run, and it can introduce deadlocks. GC.Collect(); can also slow down your program noticably because it tries to collect all generations - contrary to what happens on a normal GC run. It also has a high overload too, because it needs to stop all threads (= block program execution) and walk all object graphs to find memory that can be collected. Every object that cannot be collected right now will be promoted to the next generation, which means that your program hangs on to memory that would not actually be required anymore for longer than necessary.
For more information, see the "Remarks" section of GC.Collect() as well as the "Remarks" section of GC.WaitForPendingFinalizers.
See also When to call GC.Collect - rule number 1 is "Don't".
Okay i got a solution. I changed reading process from using external Dlls to using the OpenCV commands. Works fine. Even a few less codelines.
This is now my code to Read the Webcam:
public void Capture_Image()
{
try
{
capture = new VideoCapture();
Bitmap tempImg = capture.QueryFrame().Bitmap;
pictureBox2.Image = null;
pictureBox2.Image = tempImg;
pictureBox2.Refresh();
tempImg.Save("C:/FHT59N3/Bildanalyse_Projekt/image.jpg");
tempImg.Dispose();
capture.Dispose();
}
catch (NullReferenceException)
{
string message = "No Camera found";
string title = "Please connect Camera";
MessageBoxButtons buttons = MessageBoxButtons.OK;
MessageBox.Show(message, title, buttons, MessageBoxIcon.Warning);
}
}
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.
Okay last attempt on this, I am trying to resolve the issue detailed below the URLs. I have looked at the articles detailed in the URLs but I am still unable to hide the relevant icon. Anyone any ideas?
http://www.codeproject.com/Articles/10807/Shell-Tray-Info-Arrange-your-system-tray-icons
http://social.msdn.microsoft.com/Forums/da/vbgeneral/thread/a11faa45-a3ea-4060-8de4-a6bc22e1516d
I want to be able to hide the system tray icon that is loaded by Windows speech recognition (comes as part of Windows 7 and Windows 8 and Windows Vista). I need to do it in C# and have been trying Google solutions for the last couple of days to no avail. It seems the best way forward would be to use this code:
//NotifyIconData structure defined above
private void button1_Click(object sender, EventArgs e)
{
NOTIFYICONDATA pnid = new NOTIFYICONDATA();
pnid.uCallbackMessage = 0x800;
pnid.uFlags = 1;
pnid.hwnd = ???;
pnid.uID = 1;
pnid.szTip = null;
pnid.uFlags |= 2;
pnid.hIcon = ???;
pnid.uFlags |= 4;
pnid.szTip = "Speech Recognition is listening";
bool b = Shell_NotifyIcon(2, ref pnid);
The first argument of Shell_NotifyIcon API function (2) is to delete. Problem is I don't know how to find the arguments with question marks including the icon handle. I have tried using ExtractIcon using the executable indicated by the window speech recognition shortcut and by the file location indicated in the task manager file location ( %windir%\Speech\Common\sapisvr.exe -SpeechUX) but it tells me that the executable has no associated icons. I also verified it with a free application that I downloaded to check the icons with that executable and it said the same thing.
I can get the window handle of the icon tray using:
IntPtr hWnd = Win32API.FindWindow("Shell_TrayWnd", null);
if(hWnd.ToInt32() > 0)
{
hWnd = Win32API.FindWindowEx(hWnd, IntPtr.Zero, "TrayNotifyWnd", null);
if (hWnd.ToInt32() > 0)
{
hWnd = Win32API.FindWindowEx(hWnd,IntPtr.Zero, "SysPager", null);
if (hWnd.ToInt32() > 0)
{
hWnd = Win32API.FindWindowEx(hWnd, IntPtr.Zero, "ToolbarWindow32", null);
}
// count = Win32API.SendMessage(hWnd, 1048 , 0, 0);
}
}
however even with the handle and the count of the icons I don't know how to ennumerate the icon handles.
If anyone can give me a working solution in C# I would be happy to pay consultancy, like I say you can easily try it by loading Windows speech recognition which comes free with Windows 7 and Windows 8 and you will see the icon I mean. I could live with a C++ solution but it would have to be completely in managed C++ (.NET)
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
static IntPtr GetSystemTrayHandle()
{
IntPtr hWndTray = FindWindow("Shell_TrayWnd", null);
if (hWndTray != IntPtr.Zero)
{
hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "TrayNotifyWnd", null);
if (hWndTray != IntPtr.Zero)
{
hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "SysPager", null);
if (hWndTray != IntPtr.Zero)
{
hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "ToolbarWindow32", null);
return hWndTray;
}
}
}
return IntPtr.Zero;
}
After you have the window handle, you can choose to iterate through the processes to find the ones that are in the system tray and do whatever work you need with them:
using System.Diagnostics;
Process [] processes = System.Diagnostics.Process.GetProcesses();
foreach (System.Diagnostics.Process process in processes)
{
if (process.MainWindowHandle == hWndTray)
{
// ...
}
}
How can I run an EXE program from a Windows Service using C#?
This is my code:
System.Diagnostics.Process.Start(#"E:\PROJECT XL\INI SQLLOADER\ConsoleApplication2\ConsoleApplication2\ConsoleApplication2\bin\Debug\ConsoleApplication2.exe");
When I run this service, the application is not starting.
What's wrong with my code?
This will never work, at least not under Windows Vista or later. The key problem is that you're trying to execute this from within a Windows Service, rather than a standard Windows application. The code you've shown will work perfectly in a Windows Forms, WPF, or Console application, but it won't work at all in a Windows Service.
Windows Services cannot start additional applications because they are not running in the context of any particular user. Unlike regular Windows applications, services are now run in an isolated session and are prohibited from interacting with a user or the desktop. This leaves no place for the application to be run.
More information is available in the answers to these related questions:
How can a Windows Service start a process when a Timer event is raised?
which process in windows is user specific?
windows service (allow service to interact with desktop)
The best solution to your problem, as you've probably figured out by now, is to create a standard Windows application instead of a service. These are designed to be run by a particular user and are associated with that user's desktop. This way, you can run additional applications whenever you want, using the code that you've already shown.
Another possible solution, assuming that your Console application does not require an interface or output of any sort, is to instruct the process not to create a window. This will prevent Windows from blocking the creation of your process, because it will no longer request that a Console window be created. You can find the relevant code in this answer to a related question.
i have tried this article Code Project, it is working fine for me.
I have used the code too. article is excellent in explanation with screenshot.
I am adding necessary explanation to this scenario
You have just booted up your computer and are about to log on. When you log on, the system assigns you a unique Session ID. In Windows Vista, the first User to log on to the computer is assigned a Session ID of 1 by the OS. The next User to log on will be assigned a Session ID of 2. And so on and so forth. You can view the Session ID assigned to each logged on User from the Users tab in Task Manager.
But your windows service is brought under session ID of 0. This session is isolated from other sessions. This ultimately prevent the windows service to invoke the application running under user session's like 1 or 2.
In order to invoke the application from windows service you need to copy the control from winlogon.exe which acts as present logged user as shown in below screenshot.
Important codes
// obtain the process id of the winlogon process that
// is running within the currently active session
Process[] processes = Process.GetProcessesByName("winlogon");
foreach (Process p in processes)
{
if ((uint)p.SessionId == dwSessionId)
{
winlogonPid = (uint)p.Id;
}
}
// obtain a handle to the winlogon process
hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);
// obtain a handle to the access token of the winlogon process
if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
{
CloseHandle(hProcess);
return false;
}
// Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser
// I would prefer to not have to use a security attribute variable and to just
// simply pass null and inherit (by default) the security attributes
// of the existing token. However, in C# structures are value types and therefore
// cannot be assigned the null value.
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
// copy the access token of the winlogon process;
// the newly created token will be a primary token
if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
{
CloseHandle(hProcess);
CloseHandle(hPToken);
return false;
}
STARTUPINFO si = new STARTUPINFO();
si.cb = (int)Marshal.SizeOf(si);
// interactive window station parameter; basically this indicates
// that the process created can display a GUI on the desktop
si.lpDesktop = #"winsta0\default";
// flags that specify the priority and creation method of the process
int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
// create a new process in the current User's logon session
bool result = CreateProcessAsUser(hUserTokenDup, // client's access token
null, // file to execute
applicationName, // command line
ref sa, // pointer to process SECURITY_ATTRIBUTES
ref sa, // pointer to thread SECURITY_ATTRIBUTES
false, // handles are not inheritable
dwCreationFlags, // creation flags
IntPtr.Zero, // pointer to new environment block
null, // name of current directory
ref si, // pointer to STARTUPINFO structure
out procInfo // receives information about new process
);
you can use from windows task scheduler for this purpose, there are many libraries like TaskScheduler that help you.
for example consider we want to scheduling a task that will executes once five seconds later:
using (var ts = new TaskService())
{
var t = ts.Execute("notepad.exe")
.Once()
.Starting(DateTime.Now.AddSeconds(5))
.AsTask("myTask");
}
notepad.exe will be executed five seconds later.
for details and more information please go to wiki
if you know which class and method in that assembly you need, you can invoke it yourself like this:
Assembly assembly = Assembly.LoadFrom("yourApp.exe");
Type[] types = assembly.GetTypes();
foreach (Type t in types)
{
if (t.Name == "YourClass")
{
MethodInfo method = t.GetMethod("YourMethod",
BindingFlags.Public | BindingFlags.Instance);
if (method != null)
{
ParameterInfo[] parameters = method.GetParameters();
object classInstance = Activator.CreateInstance(t, null);
var result = method.Invoke(classInstance, parameters.Length == 0 ? null : parameters);
break;
}
}
}
Top answer with most upvotes isn't wrong but still the opposite of what I would post. I say it will totally work to start an exe file and you can do this in the context of any user. Logically you just can't have any user interface or ask for user input...
Here is my advice:
Create a simple Console Application that does what your service should do right on start without user interaction. I really recommend not using the Windows Service project type especially because you (currently) can't using .NET Core.
Add code to start your exe you want to call from service
Example to start e.g. plink.exe. You could even listen to the output:
var psi = new ProcessStartInfo()
{
FileName = "./Client/plink.exe", //path to your *.exe
Arguments = "-telnet -P 23 127.0.0.1 -l myUsername -raw", //arguments
RedirectStandardError = true,
RedirectStandardOutput = true,
RedirectStandardInput = true,
UseShellExecute = false,
CreateNoWindow = true //no window, you can't show it anyway
};
var p = Process.Start(psi);
Use NSSM (Non-Sucking Service Manager) to register that Console Application as service. NSSM can be controlled via command line and can show an UI to configure the service or you configure it via command line. You can run the service in the context of any user if you know the login data of that user.
I took LocalSystem account which is default and more than Local Service. It worked fine without having to enter login information of a specific user. I didn't even tick the checkbox "Allow service to interact with desktop" which you could if you need higher permissions.
Lastly I just want to say how funny it is that the top answer says quite the opposite of my answer and still both of us are right it's just how you interpret the question :-D. If you now say but you can't with the windows service project type - You CAN but I had this before and installation was sketchy and it was maybe kind of an unintentional hack until I found NSSM.
You should check this article Impact of Session 0 Isolation on Services and Drivers in Windows and download the .docx file and READ IT carefully , it was very helpful for me.
However this is a class which works fine for my case :
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SECURITY_ATTRIBUTES
{
public uint nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public uint cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
internal enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
internal enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
public static class ProcessAsUser
{
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx", SetLastError = true)]
private static extern bool DuplicateTokenEx(
IntPtr hExistingToken,
uint dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
Int32 ImpersonationLevel,
Int32 dwTokenType,
ref IntPtr phNewToken);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(
IntPtr ProcessHandle,
UInt32 DesiredAccess,
ref IntPtr TokenHandle);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool CreateEnvironmentBlock(
ref IntPtr lpEnvironment,
IntPtr hToken,
bool bInherit);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool DestroyEnvironmentBlock(
IntPtr lpEnvironment);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(
IntPtr hObject);
private const short SW_SHOW = 5;
private const uint TOKEN_QUERY = 0x0008;
private const uint TOKEN_DUPLICATE = 0x0002;
private const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
private const int GENERIC_ALL_ACCESS = 0x10000000;
private const int STARTF_USESHOWWINDOW = 0x00000001;
private const int STARTF_FORCEONFEEDBACK = 0x00000040;
private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
private static bool LaunchProcessAsUser(string cmdLine, IntPtr token, IntPtr envBlock)
{
bool result = false;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
SECURITY_ATTRIBUTES saProcess = new SECURITY_ATTRIBUTES();
SECURITY_ATTRIBUTES saThread = new SECURITY_ATTRIBUTES();
saProcess.nLength = (uint)Marshal.SizeOf(saProcess);
saThread.nLength = (uint)Marshal.SizeOf(saThread);
STARTUPINFO si = new STARTUPINFO();
si.cb = (uint)Marshal.SizeOf(si);
//if this member is NULL, the new process inherits the desktop
//and window station of its parent process. If this member is
//an empty string, the process does not inherit the desktop and
//window station of its parent process; instead, the system
//determines if a new desktop and window station need to be created.
//If the impersonated user already has a desktop, the system uses the
//existing desktop.
si.lpDesktop = #"WinSta0\Default"; //Modify as needed
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK;
si.wShowWindow = SW_SHOW;
//Set other si properties as required.
result = CreateProcessAsUser(
token,
null,
cmdLine,
ref saProcess,
ref saThread,
false,
CREATE_UNICODE_ENVIRONMENT,
envBlock,
null,
ref si,
out pi);
if (result == false)
{
int error = Marshal.GetLastWin32Error();
string message = String.Format("CreateProcessAsUser Error: {0}", error);
FilesUtilities.WriteLog(message,FilesUtilities.ErrorType.Info);
}
return result;
}
private static IntPtr GetPrimaryToken(int processId)
{
IntPtr token = IntPtr.Zero;
IntPtr primaryToken = IntPtr.Zero;
bool retVal = false;
Process p = null;
try
{
p = Process.GetProcessById(processId);
}
catch (ArgumentException)
{
string details = String.Format("ProcessID {0} Not Available", processId);
FilesUtilities.WriteLog(details, FilesUtilities.ErrorType.Info);
throw;
}
//Gets impersonation token
retVal = OpenProcessToken(p.Handle, TOKEN_DUPLICATE, ref token);
if (retVal == true)
{
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.nLength = (uint)Marshal.SizeOf(sa);
//Convert the impersonation token into Primary token
retVal = DuplicateTokenEx(
token,
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY,
ref sa,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)TOKEN_TYPE.TokenPrimary,
ref primaryToken);
//Close the Token that was previously opened.
CloseHandle(token);
if (retVal == false)
{
string message = String.Format("DuplicateTokenEx Error: {0}", Marshal.GetLastWin32Error());
FilesUtilities.WriteLog(message, FilesUtilities.ErrorType.Info);
}
}
else
{
string message = String.Format("OpenProcessToken Error: {0}", Marshal.GetLastWin32Error());
FilesUtilities.WriteLog(message, FilesUtilities.ErrorType.Info);
}
//We'll Close this token after it is used.
return primaryToken;
}
private static IntPtr GetEnvironmentBlock(IntPtr token)
{
IntPtr envBlock = IntPtr.Zero;
bool retVal = CreateEnvironmentBlock(ref envBlock, token, false);
if (retVal == false)
{
//Environment Block, things like common paths to My Documents etc.
//Will not be created if "false"
//It should not adversley affect CreateProcessAsUser.
string message = String.Format("CreateEnvironmentBlock Error: {0}", Marshal.GetLastWin32Error());
FilesUtilities.WriteLog(message, FilesUtilities.ErrorType.Info);
}
return envBlock;
}
public static bool Launch(string appCmdLine /*,int processId*/)
{
bool ret = false;
//Either specify the processID explicitly
//Or try to get it from a process owned by the user.
//In this case assuming there is only one explorer.exe
Process[] ps = Process.GetProcessesByName("explorer");
int processId = -1;//=processId
if (ps.Length > 0)
{
processId = ps[0].Id;
}
if (processId > 1)
{
IntPtr token = GetPrimaryToken(processId);
if (token != IntPtr.Zero)
{
IntPtr envBlock = GetEnvironmentBlock(token);
ret = LaunchProcessAsUser(appCmdLine, token, envBlock);
if (envBlock != IntPtr.Zero)
DestroyEnvironmentBlock(envBlock);
CloseHandle(token);
}
}
return ret;
}
}
And to execute , simply call like this :
string szCmdline = "AbsolutePathToYourExe\\ExeNameWithoutExtension";
ProcessAsUser.Launch(szCmdline);
You can execute an .exe from a Windows service very well in Windows XP. I have done it myself in the past.
You need to make sure you had checked the option "Allow to interact with the Desktop" in the Windows service properties. If that is not done, it will not execute.
I need to check in Windows 7 or Vista as these versions requires additional security privileges so it may throw an error, but I am quite sure it can be achieved either directly or indirectly. For XP I am certain as I had done it myself.
First, we are going to create a Windows Service that runs under the
System account. This service will be responsible for spawning an
interactive process within the currently active User’s Session. This
newly created process will display a UI and run with full admin
rights. When the first User logs on to the computer, this service will
be started and will be running in Session0; however the process that
this service spawns will be running on the desktop of the currently
logged on User. We will refer to this service as the LoaderService.
Next, the winlogon.exe process is responsible for managing User login
and logout procedures. We know that every User who logs on to the
computer will have a unique Session ID and a corresponding
winlogon.exe process associated with their Session. Now, we mentioned
above, the LoaderService runs under the System account. We also
confirmed that each winlogon.exe process on the computer runs under
the System account. Because the System account is the owner of both
the LoaderService and the winlogon.exe processes, our LoaderService
can copy the access token (and Session ID) of the winlogon.exe process
and then call the Win32 API function CreateProcessAsUser to launch a
process into the currently active Session of the logged on User. Since
the Session ID located within the access token of the copied
winlogon.exe process is greater than 0, we can launch an interactive
process using that token.
Try this one.
Subverting Vista UAC in Both 32 and 64 bit Architectures
I think You are copying the .exe to different location. This might be the problem I guess. When you copy the exe, you are not copying its dependencies.
So, what you can do is, put all dependent dlls in GAC so that any .net exe can access it
Else, do not copy the exe to new location. Just create a environment variable and call the exe in your c#. Since the path is defined in environment variables, the exe is can be accessed by your c# program.
Update:
previously I had some kind of same issue in my c#.net 3.5 project in which I was trying to run a .exe file from c#.net code and that exe was nothing but the another project exe(where i added few supporting dlls for my functionality) and those dlls methods I was using in my exe application. At last I resolved this by creating that application as a separate project to the same solution and i added that project output to my deployment project. According to this scenario I answered, If its not what he wants then I am extremely sorry.
System.Diagnostics.Process.Start("Exe Name");