I want to look in the list of processes to determine if OpenOffice Calc is running or if OpenOffice Writer is running. With QuickStart off, I get a scalc.exe and an swriter.exe so its simple.
However when the quick start is on I just get soffice.bin and soffice.exe
Is there a way of asking those processes which application is running?
I think there is no way around checking the window title. You would have to enumerate all windows and check whether the title ends with "- OpenOffice.org Writer" or "- OpenOffice.org Calc" etc.
The following short sample would check whether Writer is running:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace Sample
{
public class Window
{
public string Title { get; set; }
public int Handle { get; set; }
public string ProcessName { get; set; }
}
public class WindowHelper
{
/// <summary>
/// Win32 API Imports
/// </summary>
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder title, int size);
[DllImport("user32.dll")]
private static extern int EnumWindows(EnumWindowsProc ewp, int lParam);
[DllImport("user32.dll")]
private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
public delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);
private List<Window> _openOfficeWindows;
public List<Window> GetOpenOfficeWindows()
{
_openOfficeWindows = new List<Window>();
EnumWindowsProc ewp = new EnumWindowsProc(EvalWindow);
EnumWindows(ewp, 0);
return _openOfficeWindows;
}
private bool EvalWindow(IntPtr hWnd, int lParam)
{
if (!IsWindowVisible(hWnd))
return (true);
uint lpdwProcessId;
StringBuilder title = new StringBuilder(256);
GetWindowThreadProcessId(hWnd, out lpdwProcessId);
GetWindowText(hWnd, title, 256);
Process p = Process.GetProcessById((int)lpdwProcessId);
if (p != null && p.ProcessName.ToLower().Contains("soffice"))
{
_openOfficeWindows.Add(new Window() { Title = title.ToString(), Handle = hWnd.ToInt32(), ProcessName = p.ProcessName });
}
return (true);
}
}
class Program
{
static void Main(string[] args)
{
WindowHelper helper = new WindowHelper();
List<Window> openOfficeWindows = helper.GetOpenOfficeWindows();
foreach (var item in openOfficeWindows)
{
if (item.Title.EndsWith("- OpenOffice.org Writer"))
{
Console.WriteLine("Writer is running");
}
}
}
}
}
Related
I've got a task where I need to send a message to a thread from another thread.
I have a class for this task, here it is:
public class MyThread
{
public Thread Thrd { get; set; }
public MyThread Next { get; set; }
public MyThread()
{
Thrd = new Thread(Work);
}
public void Start()
{
Thrd.Start();
}
private void Work()
{
while (true)
{
if (//Has message to receive)
{
//Get message and do work
//Send message to the Next.Thrd
}
}
}
}
As a message I just need to send an integer. I found functions like PostThreadMessageA and GetMessageA that can be useful for me, is this right? Also, these require a handle to a thread, how can I get it? Finally, is there a function that does check if there's a message to receive for this thread (that I could put instead of "//Has message to receive")?
these require a handle to a thread, how can I get it?
You could use GetCurrentThreadId to get the threadid after the thread starts.
is there a function that does check if there's a message to receive
for this thread?
Yes, GetMessage/PeekMessage(GetMessage will wait for the message to arrive, but PeekMessage does not wait for a message to be posted before returning.)
The following is the win32 solution. Two threads thread1, thread2 (thread1.Next = thread2) are created in the main thread. The main thread sends data to thread1, and then thread1 receives the message and sends its data to thread2:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp
{
public class MyThread
{
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
public struct MSG
{
public IntPtr hwnd;
public int message;
public UIntPtr wParam;
public IntPtr lParam;
public int time;
public POINT pt;
public int lPrivate;
}
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int GetCurrentThreadId();
[DllImport("User32.dll",SetLastError = true,CharSet =CharSet.Auto)]
public static extern bool PostThreadMessage(int idThread, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool GetMessage(out MSG Msg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool PeekMessage(out MSG Msg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax,uint wRemoveMsg);
public static uint WM_USER = 0x0400;
public Thread Thrd { get; set; }
public MyThread Next { get; set; }
public int ThreadId { get; set; }
public int data { get; set; }
public MyThread()
{
Thrd = new Thread(Work);
ThreadId = -1;
}
public void Start()
{
Thrd.Start();
}
public static bool SendTo(int data, MyThread target_thread)
{
IntPtr lparam = new IntPtr(data);
return PostThreadMessage(target_thread.ThreadId, WM_USER, IntPtr.Zero, lparam);
}
private void Work()
{
ThreadId = GetCurrentThreadId();
data = ThreadId;
while (true)
{
MSG msg = new MSG();
IntPtr hwnd = new IntPtr(-1);
if (GetMessage(out msg, IntPtr.Zero, 0, 0))
{
Console.WriteLine("received : " + msg.lParam.ToInt32());
if (Next != null)
SendTo(data, Next);
}
}
}
}
class Program
{
static void Main(string[] args)
{
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.Next = thread2;
thread1.Start();
thread2.Start();
//To Do
//To Ensure that the thread has been started, and the thread ID has been obtained through GetCurrentThreadId
Thread.Sleep(1000);
MyThread.SendTo(50, thread1);
//To Do
}
}
}
To get what I wanted I used these functions:
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool PeekMessage(
out InternalMessage lpInternalMessage,
IntPtr hWnd,
uint wMsgFilterMin,
uint wMsgFilterMax,
uint wRemoveMsg
);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool PostThreadMessage(
uint threadId,
uint msg,
UIntPtr wParam,
IntPtr lParam
);
[DllImport("kernel32.dll")]
public static extern uint GetCurrentThreadId();
and also I needed to define such a structure:
[StructLayout(LayoutKind.Sequential)]
public struct InternalMessage
{
public IntPtr hwnd;
public uint message;
public UIntPtr wParam;
public IntPtr lParam;
public int time;
public Point pt;
public int lPrivate;
}
Let's say I have multiple chrome windows open (not tabs),
how can I check the browser title?
I tried the following:
Process[] p = Process.GetProcessesByName("chrome");
foreach (Process item in p)
{
Console.WriteLine(item.MainWindowTitle);
}
but it return me only the last open window name and all other are blanks..
I had to do something like this, but it was amazingly fiddly involving calling Windows API functions. The problem was that Chrome seems to use a single process for multiple windows or some other weirdness that meant the simple approach didn't work for me.
Anyway, try this and see if it works. Basically it uses the Chrome window class name (which might be Chrome_WidgetWin_0 or Chrome_WidgetWin_1) to enumerate all windows with that class name, and returns the window titles for those which are not blank.
Note that this also always returns a windows title called "Chrome App Launcher" for some reason, so you might need to filter that out.
Note: you can also do this for Firefox by using "MozillaWindowClass" and for IE by using "IEFrame" (although any of those are likely to change with different versions).
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
namespace Demo
{
class WindowsByClassFinder
{
public delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lparam);
[SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public extern static bool EnumWindows(EnumWindowsDelegate lpEnumFunc, IntPtr lparam);
[SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
[DllImport("User32", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int GetWindowText(IntPtr windowHandle, StringBuilder stringBuilder, int nMaxCount);
[DllImport("user32.dll", EntryPoint = "GetWindowTextLength", SetLastError = true)]
internal static extern int GetWindowTextLength(IntPtr hwnd);
/// <summary>Find the windows matching the specified class name.</summary>
public static IEnumerable<IntPtr> WindowsMatching(string className)
{
return new WindowsByClassFinder(className)._result;
}
private WindowsByClassFinder(string className)
{
_className = className;
EnumWindows(callback, IntPtr.Zero);
}
private bool callback(IntPtr hWnd, IntPtr lparam)
{
if (GetClassName(hWnd, _apiResult, _apiResult.Capacity) != 0)
{
if (string.CompareOrdinal(_apiResult.ToString(), _className) == 0)
{
_result.Add(hWnd);
}
}
return true; // Keep enumerating.
}
public static IEnumerable<string> WindowTitlesForClass(string className)
{
foreach (var windowHandle in WindowsMatchingClassName(className))
{
int length = GetWindowTextLength(windowHandle);
StringBuilder sb = new StringBuilder(length + 1);
GetWindowText(windowHandle, sb, sb.Capacity);
yield return sb.ToString();
}
}
public static IEnumerable<IntPtr> WindowsMatchingClassName(string className)
{
if (string.IsNullOrWhiteSpace(className))
throw new ArgumentOutOfRangeException("className", className, "className can't be null or blank.");
return WindowsMatching(className);
}
private readonly string _className;
private readonly List<IntPtr> _result = new List<IntPtr>();
private readonly StringBuilder _apiResult = new StringBuilder(1024);
}
class Program
{
void run()
{
ChromeWindowTitles().Print();
}
public IEnumerable<string> ChromeWindowTitles()
{
foreach (var title in WindowsByClassFinder.WindowTitlesForClass("Chrome_WidgetWin_0"))
if (!string.IsNullOrWhiteSpace(title))
yield return title;
foreach (var title in WindowsByClassFinder.WindowTitlesForClass("Chrome_WidgetWin_1"))
if (!string.IsNullOrWhiteSpace(title))
yield return title;
}
static void Main()
{
new Program().run();
}
}
static class DemoUtil
{
public static void Print(this object self)
{
Console.WriteLine(self);
}
public static void Print(this string self)
{
Console.WriteLine(self);
}
public static void Print<T>(this IEnumerable<T> self)
{
foreach (var item in self)
Console.WriteLine(item);
}
}
}
I know this is already answered, but I also have made a solution, which enumerates all Windows within a thread.
It was built from Matthew Watson's solution, hence some similarities.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Chrome_Windows
{
class Program
{
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(uint dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("User32", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr windowHandle, StringBuilder stringBuilder, int nMaxCount);
[DllImport("user32.dll", EntryPoint = "GetWindowTextLength", SetLastError = true)]
internal static extern int GetWindowTextLength(IntPtr hwnd);
private static List<IntPtr> windowList;
private static string _className;
private static StringBuilder apiResult = new StringBuilder(256); //256 Is max class name length.
private delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
static void Main(string[] args)
{
List<IntPtr> ChromeWindows = WindowsFinder("Chrome_WidgetWin_1", "chrome");
foreach (IntPtr windowHandle in ChromeWindows)
{
int length = GetWindowTextLength(windowHandle);
StringBuilder sb = new StringBuilder(length + 1);
GetWindowText(windowHandle, sb, sb.Capacity);
Console.WriteLine(sb.ToString());
}
}
private static List<IntPtr> WindowsFinder(string className, string process)
{
_className = className;
windowList = new List<IntPtr>();
Process[] chromeList = Process.GetProcessesByName(process);
if (chromeList.Length > 0)
{
foreach (Process chrome in chromeList)
{
if (chrome.MainWindowHandle != IntPtr.Zero)
{
foreach (ProcessThread thread in chrome.Threads)
{
EnumThreadWindows((uint)thread.Id, new EnumThreadDelegate(EnumThreadCallback), IntPtr.Zero);
}
}
}
}
return windowList;
}
static bool EnumThreadCallback(IntPtr hWnd, IntPtr lParam)
{
if (GetClassName(hWnd, apiResult, apiResult.Capacity) != 0)
{
if (string.CompareOrdinal(apiResult.ToString(), _className) == 0)
{
windowList.Add(hWnd);
}
}
return true;
}
}
}
I know this is an old thread, but I have found the answer to this, at least for my use case anyway. I wanted to find all the open chrome windows/tabs by title as well, but in my case I wanted to close the ones I found containing x Title. After reading icbytes and dor-cohen's post above I realized I could achieve what I needed by calling Process.GetProcessesByName() more than once. When making this call you do get an array of all the running chrome processes, but only one instance will contain a value for MainWindowTitle. This is a bit annoying for several reasons. You can have multiple chrome sessions open with and "active" "displayed tab", but still the call only ever returns an array of chrome proc's with just one instance in that array having an value for MainWindowTitle. Again, my solution is not necessarily the OP's intention as he states just wanting to list the titles. My solution wants to close each found title.
What I have done is as follows:
Once I find the first chrome process with the title I am looking for I call CloseMainWindow() on that process. Do not call Kill() as it will crash the browser altogether. I am just closing the active or top level window here. I am posting my code below. I hope this will help someone else! Thanks!
bool foundAll = false;
do
{
bool foundOne = false;
procs = Process.GetProcessesByName("chrome");
foreach (Process p in procs)
{
if (p.MainWindowTitle.Length > 0)
{
string t = p.MainWindowTitle.Replace(" - Google Chrome", "");
if (t.ToLower().Contains(this.BrowserTabText.ToLower()))
{
foundOne = true;
this.WriteEventLogEntry($"Found Tab Title: {this.BrowserTabText} with PID: {p.Id}. \r\nWe will close it.", EventLogEntryType.Information);
p.CloseMainWindow();
break;
}
}
}
if (!foundOne)
{
foundAll = true;
}
} while (!foundAll);
You must get a list of processes.
Iterate through the list and only where name is "chrome".
This will allow You to get all titles.
Because if You have more then one chrome process , Your call will give You only one, because You call it only once.
Which it returns is perhaps another question. In Your case it is the last.
I want to use a system wide hotkey in my powershell GUI application. I found this C# code for registering a hotkey and integrated it in my script.
The trouble is, because I can't fully understand the C# code, I don't know how to properly add the parameters for the registerhotkey method.
The goal is: on key press SHIFT+ALT+Z execute the code in $hotkeyExecCode.
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[System.Windows.Forms.Application]::EnableVisualStyles()
$form1 = New-Object 'System.Windows.Forms.Form'
$form1.ClientSize = '200, 150'
$cCode = #"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
/// <summary> This class allows you to manage a hotkey </summary>
public class GlobalHotkeys : IDisposable
{
[DllImport( "user32", SetLastError = true )]
[return: MarshalAs( UnmanagedType.Bool )]
public static extern bool RegisterHotKey (IntPtr hwnd, int id, uint fsModifiers, uint vk);
[DllImport( "user32", SetLastError = true )]
public static extern int UnregisterHotKey (IntPtr hwnd, int id);
[DllImport( "kernel32", SetLastError = true )]
public static extern short GlobalAddAtom (string lpString);
[DllImport( "kernel32", SetLastError = true )]
public static extern short GlobalDeleteAtom (short nAtom);
public const int MOD_ALT = 1;
public const int MOD_CONTROL = 2;
public const int MOD_SHIFT = 4;
public const int MOD_WIN = 8;
public const int WM_HOTKEY = 0x312;
public GlobalHotkeys()
{
this.Handle = Process.GetCurrentProcess().Handle;
}
/// <summary>Handle of the current process</summary>
public IntPtr Handle;
/// <summary>The ID for the hotkey</summary>
public short HotkeyID { get; private set; }
/// <summary>Register the hotkey</summary>
public void RegisterGlobalHotKey(int hotkey, int modifiers, IntPtr handle)
{
UnregisterGlobalHotKey();
this.Handle = handle;
RegisterGlobalHotKey(hotkey, modifiers);
}
/// <summary>Register the hotkey</summary>
public void RegisterGlobalHotKey(int hotkey, int modifiers)
{
UnregisterGlobalHotKey();
try
{
// use the GlobalAddAtom API to get a unique ID (as suggested by MSDN)
string atomName = Thread.CurrentThread.ManagedThreadId.ToString("X8") + this.GetType().FullName;
HotkeyID = GlobalAddAtom(atomName);
if (HotkeyID == 0)
throw new Exception("Unable to generate unique hotkey ID. Error: " + Marshal.GetLastWin32Error().ToString());
// register the hotkey, throw if any error
if (!RegisterHotKey(this.Handle, HotkeyID, (uint)modifiers, (uint)hotkey))
throw new Exception("Unable to register hotkey. Error: " + Marshal.GetLastWin32Error().ToString());
}
catch (Exception ex)
{
// clean up if hotkey registration failed
Dispose();
Console.WriteLine(ex);
}
}
/// <summary>Unregister the hotkey</summary>
public void UnregisterGlobalHotKey()
{
if ( this.HotkeyID != 0 )
{
UnregisterHotKey(this.Handle, HotkeyID);
// clean up the atom list
GlobalDeleteAtom(HotkeyID);
HotkeyID = 0;
}
}
public void Dispose()
{
UnregisterGlobalHotKey();
}
}
"#
Add-Type -TypeDefinition $cCode
$hotkeyExecCode = { $form1.WindowState = "normal"}
[GlobalHotkeys]::RegisterHotKey( ???????? ) # <--------- How do i add the parameters here?
$form1.windowstate = "minimized"
$form1.ShowDialog()
I am trying to P/Invoke the NotifyServiceStatusChange event in C# to check when a service has stopped. I managed to get it to compile and run without any errors, but now, when I stop the service, it doesn't seem to want to notify that its dead. Any ideas why that could be? You can test it out by copying this code into a blank console application; just be sure to replace "My Service Name" with your service name (there are two instances of this string below).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
public delegate void StatusChanged(IntPtr parameter);
public class SERVICE_NOTIFY : MarshalByRefObject
{
public uint dwVersion;
public StatusChanged pfnNotifyCallback;
public IntPtr pContext;
public uint dwNotificationStatus;
public SERVICE_STATUS_PROCESS ServiceStatus;
public uint dwNotificationTriggered;
public IntPtr pszServiceNames;
};
public struct SERVICE_STATUS_PROCESS {
public uint dwServiceType;
public uint dwCurrentState;
public uint dwControlsAccepted;
public uint dwWin32ExitCode;
public uint dwServiceSpecificExitCode;
public uint dwCheckPoint;
public uint dwWaitHint;
public uint dwProcessId;
public uint dwServiceFlags;
};
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
[DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, ref IntPtr pNotifyBuffer);
public static SERVICE_NOTIFY notify;
public static GCHandle notifyHandle;
public static IntPtr unmanagedNotifyStructure;
static void Main(string[] args)
{
IntPtr hSCM = OpenSCManager(null, null, (uint)0xF003F);
if (hSCM != IntPtr.Zero)
{
IntPtr hService = OpenService(hSCM, "My Service Name", (uint)0xF003F);
if (hService != IntPtr.Zero)
{
StatusChanged changeDelegate = ReceivedStatusChangedEvent;
notify = new SERVICE_NOTIFY();
notify.dwVersion = 2;
notify.pfnNotifyCallback = changeDelegate;
notify.pContext = IntPtr.Zero;
notify.dwNotificationStatus = 0;
SERVICE_STATUS_PROCESS process;
process.dwServiceType = 0;
process.dwCurrentState = 0;
process.dwControlsAccepted = 0;
process.dwWin32ExitCode = 0;
process.dwServiceSpecificExitCode = 0;
process.dwCheckPoint = 0;
process.dwWaitHint = 0;
process.dwProcessId = 0;
process.dwServiceFlags = 0;
notify.ServiceStatus = process;
notify.dwNotificationTriggered = 0;
notify.pszServiceNames = Marshal.StringToHGlobalUni("My Service Name");
notifyHandle = GCHandle.Alloc(notify);
unmanagedNotifyStructure = Marshal.AllocHGlobal((IntPtr)(notifyHandle));
NotifyServiceStatusChange(hService, (uint)0x00000001, ref unmanagedNotifyStructure);
Console.WriteLine("Waiting for the service to stop. Press enter to exit.");
Console.ReadLine();
}
}
}
public static void ReceivedStatusChangedEvent(IntPtr parameter)
{
Console.WriteLine("Service stopped.");
}
}
}
If you want to keep the functionality you are attempting at present, you will need to multithread.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public class SERVICE_NOTIFY
{
public uint dwVersion;
public IntPtr pfnNotifyCallback;
public IntPtr pContext;
public uint dwNotificationStatus;
public SERVICE_STATUS_PROCESS ServiceStatus;
public uint dwNotificationTriggered;
public IntPtr pszServiceNames;
};
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct SERVICE_STATUS_PROCESS
{
public uint dwServiceType;
public uint dwCurrentState;
public uint dwControlsAccepted;
public uint dwWin32ExitCode;
public uint dwServiceSpecificExitCode;
public uint dwCheckPoint;
public uint dwWaitHint;
public uint dwProcessId;
public uint dwServiceFlags;
};
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
[DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, IntPtr pNotifyBuffer);
[DllImportAttribute("kernel32.dll", EntryPoint = "SleepEx")]
public static extern uint SleepEx(uint dwMilliseconds, [MarshalAsAttribute(UnmanagedType.Bool)] bool bAlertable);
public static SERVICE_NOTIFY notify;
public static GCHandle notifyHandle;
public static IntPtr unmanagedNotifyStructure;
static void Main(string[] args)
{
IntPtr hSCM = OpenSCManager(null, null, (uint)0xF003F);
if (hSCM != IntPtr.Zero)
{
IntPtr hService = OpenService(hSCM, "Apache2.2", (uint)0xF003F);
if (hService != IntPtr.Zero)
{
StatusChanged changeDelegate = ReceivedStatusChangedEvent;
notify = new SERVICE_NOTIFY();
notify.dwVersion = 2;
notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
notify.pContext = IntPtr.Zero;
notify.dwNotificationStatus = 0;
SERVICE_STATUS_PROCESS process;
process.dwServiceType = 0;
process.dwCurrentState = 0;
process.dwControlsAccepted = 0;
process.dwWin32ExitCode = 0;
process.dwServiceSpecificExitCode = 0;
process.dwCheckPoint = 0;
process.dwWaitHint = 0;
process.dwProcessId = 0;
process.dwServiceFlags = 0;
notify.ServiceStatus = process;
notify.dwNotificationTriggered = 0;
notify.pszServiceNames = Marshal.StringToHGlobalUni("Apache2.2");
notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned);
unmanagedNotifyStructure = notifyHandle.AddrOfPinnedObject();
NotifyServiceStatusChange(hService, (uint)0x00000001, unmanagedNotifyStructure);
GC.KeepAlive(changeDelegate);
Console.WriteLine("Waiting for the service to stop. Press enter to exit.");
while (true)
{
try
{
string keyIn = Reader.ReadLine(500);
break;
}
catch (TimeoutException)
{
SleepEx(100,true);
}
}
notifyHandle.Free();
}
}
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void StatusChanged(IntPtr parameter);
public static void ReceivedStatusChangedEvent(IntPtr parameter)
{
Console.WriteLine("Service stopped.");
}
}
}
class Reader
{
private static Thread inputThread;
private static AutoResetEvent getInput, gotInput;
private static string input;
static Reader()
{
inputThread = new Thread(reader);
inputThread.IsBackground = true;
inputThread.Start();
getInput = new AutoResetEvent(false);
gotInput = new AutoResetEvent(false);
}
private static void reader()
{
while (true)
{
getInput.WaitOne();
input = Console.ReadLine();
gotInput.Set();
}
}
public static string ReadLine(int timeOutMillisecs)
{
getInput.Set();
bool success = gotInput.WaitOne(timeOutMillisecs);
if (success)
return input;
else
throw new TimeoutException("User did not provide input within the timelimit.");
}
}
Read related question here: http://social.msdn.microsoft.com/Forums/vstudio/en-US/f68fb826-036a-4b9c-81e6-4cbd87931feb/notifyservicestatuschange-not-working-in-c-for-windows-service-notification
Important quote: "the system invokes the specified callback function as an asynchronous procedure call (APC) queued to the calling thread. The calling thread must enter an alertable wait"
I don't remember whether .NET framework 4 uses alertable waiting when you enter Thread.Sleep or some form of Wait on waithandles, even though it uses alertable waiting for asynchronous I/O, for internal timer threads etc.
However just try Thread.Sleep or some flavor of Wait on some waithandle, instead of Console.ReadLine, make sure that your thread is blocked by those APIs at the time when you kill the service. This might do the magic - but, to my knowledge, this is a dangerous way, because .NET runtime does not expect user code to be executed on an APC. At least, try not to use NET framework resources or absolutely any APIs (especially synchronization-related or memory allocation) directly from your callback - just set some primitive variable and quit.
With APCs, the safest solution for you would be to have the callback implemented in some kind of native module, and also scheduled from some non-.NET thread, interoperating with managed code through shared variables, a pipe, or COM interface.
Or, as Hans Passant suggested in another copy of your question, just do polling from managed code. Absolutely safe, easy to implement, guaranteed to work.
Excellent source of relevant information is Joe Duffy's book (he covers a lot of topics, and alertable waits and .NET in particular): http://www.amazon.com/Concurrent-Programming-Windows-Joe-Duffy/dp/032143482X
UPDATE: Just consulted with Joe Duffy's book, yes indeed, scheduling .NET code on an APC may result in deadlocks, access violations and generally unpredictable behavior. So the answer is simple: don't do APC from a managed thread.
Simplified a lot of this from #Motes' answer...(EDIT: I put it into a class that people can use to easily wait for a service to stop; it will block!
Edit Again: Made sure this worked if you force garbage collection with GC.Collect() anywhere in the function...turns out, you DO need the SERVICE_STATUS_PROCESS.
Another Edit: Made sure it works if you abort your thread (note on that: can't abort sleeping threads so if you plan to abort this thread...then make sure you give it a timeout at least so the finalizer can run after the timeout hits), also added timeouts. Also ensured mapping 1-to-1 of the OS thread to the current .NET thread.
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace ServiceAssistant
{
class ServiceHelper
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public class SERVICE_NOTIFY
{
public uint dwVersion;
public IntPtr pfnNotifyCallback;
public IntPtr pContext;
public uint dwNotificationStatus;
public SERVICE_STATUS_PROCESS ServiceStatus;
public uint dwNotificationTriggered;
public IntPtr pszServiceNames;
};
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct SERVICE_STATUS_PROCESS
{
public uint dwServiceType;
public uint dwCurrentState;
public uint dwControlsAccepted;
public uint dwWin32ExitCode;
public uint dwServiceSpecificExitCode;
public uint dwCheckPoint;
public uint dwWaitHint;
public uint dwProcessId;
public uint dwServiceFlags;
};
[DllImport("advapi32.dll")]
static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
[DllImport("advapi32.dll")]
static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
[DllImport("advapi32.dll")]
static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, IntPtr pNotifyBuffer);
[DllImportAttribute("kernel32.dll")]
static extern uint SleepEx(uint dwMilliseconds, bool bAlertable);
[DllImport("advapi32.dll")]
static extern bool CloseServiceHandle(IntPtr hSCObject);
delegate void StatusChangedCallbackDelegate(IntPtr parameter);
/// <summary>
/// Block until a service stops, is killed, or is found to be already dead.
/// </summary>
/// <param name="serviceName">The name of the service you would like to wait for.</param>
/// <param name="timeout">An amount of time you would like to wait for. uint.MaxValue is the default, and it will force this thread to wait indefinitely.</param>
public static void WaitForServiceToStop(string serviceName, uint timeout = uint.MaxValue)
{
// Ensure that this thread's identity is mapped, 1-to-1, with a native OS thread.
Thread.BeginThreadAffinity();
GCHandle notifyHandle = default(GCHandle);
StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent;
IntPtr hSCM = IntPtr.Zero;
IntPtr hService = IntPtr.Zero;
try
{
hSCM = OpenSCManager(null, null, (uint)0xF003F);
if (hSCM != IntPtr.Zero)
{
hService = OpenService(hSCM, serviceName, (uint)0xF003F);
if (hService != IntPtr.Zero)
{
SERVICE_NOTIFY notify = new SERVICE_NOTIFY();
notify.dwVersion = 2;
notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
notify.ServiceStatus = new SERVICE_STATUS_PROCESS();
notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned);
IntPtr pinnedNotifyStructure = notifyHandle.AddrOfPinnedObject();
NotifyServiceStatusChange(hService, (uint)0x00000001, pinnedNotifyStructure);
SleepEx(timeout, true);
}
}
}
finally
{
// Clean up at the end of our operation, or if this thread is aborted.
if (hService != IntPtr.Zero)
{
CloseServiceHandle(hService);
}
if (hSCM != IntPtr.Zero)
{
CloseServiceHandle(hSCM);
}
GC.KeepAlive(changeDelegate);
if (notifyHandle != default(GCHandle))
{
notifyHandle.Free();
}
Thread.EndThreadAffinity();
}
}
public static void ReceivedStatusChangedEvent(IntPtr parameter)
{
}
}
}
Yes! We did it. What a journey it has been.
I have a problem I'm trying to get a window's title by it's process name and I can't do it here s what I tried :
Process[] p = Process.GetProcessesByName("Acrobat");
Console.WriteLine(p[0].MainWindowTitle);
Console.ReadLine();
But the problem is that I can only get it only if the associated process does have a main window. How can I make it working ?
The main goal is that I've a method named BringToFront()
But this method ask for a caption name which is "thenameofthePDF.pdf - Adobe Acrobat Pro (Yes, acrobat is running with an opened pdf)
I would like to bring to front my Acrobat window..
but for this I need the name of the windows as my method is asking for the caption.
Here is the entire code at the moment:
class Program
{
[DllImport("User32.dll")]
public static extern Int32 SetForegroundWindow(int hWnd);
[DllImport("user32.dll")]
public static extern int FindWindow(string lpClassName, string lpWindowName);
private static void BringToFront(string className, string CaptionName)
{
SetForegroundWindow(FindWindow(className, CaptionName));
}
static void Main(string[] args)
{
// BringToFront("Acrobat", "mypdf.pdf - Adobe Acrobate Pro");
Process[] p = Process.GetProcesses();
foreach (var process in p)
{
Console.WriteLine(process.MainWindowTitle);
}
Console.ReadLine();
}
}
Did you read the manual, does the following apply?
A process has a main window associated with it only if the process has a graphical interface. If the associated process does not have a main window (so that MainWindowHandle is zero), MainWindowTitle is an empty string (""). If you have just started a process and want to use its main window title, consider using the WaitForInputIdle method to allow the process to finish starting, ensuring that the main window handle has been created. Otherwise, the system throws an exception.
One possible solution is to enumerate all top-level windows and pick the one you are interested in. In your case this would be all windows with a class of AcrobatSDIWindow and a window title starting with your document name.
class Program
{
public class SearchData
{
public string ClassName { get; set; }
public string Title { get; set; }
private readonly List<IntPtr> _result = new List<IntPtr>();
public List<IntPtr> Result
{
get { return _result; }
}
}
[DllImport("User32.dll")]
public static extern Int32 SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, ref SearchData data);
private delegate bool EnumWindowsProc(IntPtr hWnd, ref SearchData data);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
public static bool EnumProc(IntPtr hWnd, ref SearchData searchData)
{
var sbClassName = new StringBuilder(1024);
GetClassName(hWnd, sbClassName, sbClassName.Capacity);
if (searchData.ClassName == null || Regex.IsMatch(sbClassName.ToString(), searchData.ClassName))
{
var sbWindowText = new StringBuilder(1024);
GetWindowText(hWnd, sbWindowText, sbWindowText.Capacity);
if (searchData.Title == null || Regex.IsMatch(sbWindowText.ToString(), searchData.Title))
{
searchData.Result.Add(hWnd);
}
}
return true;
}
static void Main(string[] args)
{
var searchData = new SearchData
{
ClassName = "AcrobatSDIWindow",
Title = "^My Document\\.pdf.*"
};
EnumWindows(EnumProc, ref searchData);
var firstResult = searchData.Result.FirstOrDefault();
if (firstResult != IntPtr.Zero)
{
SetForegroundWindow(firstResult);
}
}
}
If you dump them all out using GetProcesses() it appears that the window title will have 'Adobe Reader', prefixed by the name of the PDF if one is open. So you might need to do that and walk the array instead.
For example if I have UserManual.pdf open and I run the code below it displays UserManual.pdf - Adobe Reader on the console:
Process[] p = Process.GetProcesses();
String Title = String.Empty;
for (var i = 0; i < p.Length; i++)
{
Title = p[i].MainWindowTitle;
if (Title.Contains(#"Adobe"))
Console.WriteLine(Title);
}