I'm working on an application that scans my active processes, and after selection, passes key commands to that process. The problem I'm having is that not all my processes seem to have a MainWindowTitle. Below is the code snippit where I immediately recognized this problem:
Dictionary<int, string> procInfo = new Dictionary<int, string>();
Process[] processes = Process.GetProcesses();
foreach (Process procData in processes)
{
if (procData.MainWindowTitle.Length > 0)// || procData.ProcessName == "notepad++")
{
procInfo.Add(procData.Id, procData.ProcessName);
}
}
foreach (KeyValuePair<int, string> proc in procInfo)
{
lstProcesses.Items.Add(proc.Value + " (" + proc.Key.ToString() + ")");
}
If you look at line 5, you'll see where I was forcing Notepad++ into the list to test against my results from WinSpy++ (alternative to Spy++) and without the force it refuses to display as it's MainWindowTitle property is blank.
Without the MainWindowTitle I can't get the class name for the application:
//procID set earlier and shown to be working
Process proc = Process.GetProcessById(procID);
string winTitle = proc.MainWindowTitle;
IntPtr hWnd = proc.MainWindowHandle;
StringBuilder buffer = new StringBuilder(1024);
GetClassName(hWnd, buffer, buffer.Capacity);
string winCaption = buffer.ToString();
And thus I can't target the application to pass in key commands:
//winCaption currently blank, winTitle tested and working
IntPtr appHandler = FindWindow(winCaption, winTitle);
SetForegroundWindow(appHandler);
SendKeys.SendWait("things and junk");
I have the proper DllImports setup for my project so the problem isn't there and I haven't found an answer here or anything reliable cruising the rest of the interwebs. Am I missing something in my code, or is this just bad and should I feel bad?
I had to completely scrap the options above in favor of a P/Invoke solution: http://pinvoke.net/default.aspx/user32.EnumDesktopWindows
Taking advantage of GetWindowText, GetWindowThreadProcessID and GetClassName in a new function:
//the following was jacked from: http://pinvoke.net/default.aspx/user32.EnumDesktopWindows
var procCollection = new List<string>();
//Dictionary<string, int> procCollection = new Dictionary<string, int>();
EnumDelegate filter = delegate(IntPtr hWnd, int lParam)
{
//return window titles
StringBuilder strbTitle = new StringBuilder(255);
int nLength = GetWindowText(hWnd, strbTitle, strbTitle.Capacity + 1);
string winTitle = strbTitle.ToString();
//return thread process id
uint getID = 0;
GetWindowThreadProcessId(hWnd, ref getID);
int winID = Convert.ToInt32(getID);
//return class names
StringBuilder strbClass = new StringBuilder(255);
GetClassName(hWnd, strbClass, strbClass.Capacity+1);
string winClass = strbClass.ToString();
if (IsWindowVisible(hWnd) && string.IsNullOrEmpty(winTitle) == false)
{
procCollection.Add(winTitle+" -- "+winID+" -- "+winClass);
}
return true;
};
if (EnumDesktopWindows(IntPtr.Zero, filter, IntPtr.Zero))
{
//foreach (KeyValuePair<string, int> procInfo in procCollection)
foreach(string procData in procCollection)
{
//if (procInfo.Key != "Start" && procInfo.Key != "Program Manager")
if (procData.Contains("Start") == false && procData.Contains("Program Manager") == false)
{
lstProcesses.Items.Add(procData);
}
}
}
Allows me to get everything I need to build my list of open Windows. This doesn't give me the list of processes like WinSpy++ (Spy++), but it's exactly what I need.
Related
I am looking for a way to get all the excel processes initiated by my winform application. Is it even possible?
If I can get list of all running processes using
Process[] processes = Process.GetProcesses();
Shouldn't there be a way to get processes started by a certain applciation?
First add this namespaces to your application
using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
using System.Diagnostics;
then in your application reference user32 dll
class ExcelClass
{
[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
Process GetId(Excel.Application excelApp)
{
int id;
GetWindowThreadProcessId(excelApp.Hwnd, out id);
return Process.GetProcessById(id);
}
}
Okay I have found the answer myself. I am sharing it just in case someone needs it.
int myappid = Process.GetCurrentProcess().Id;
Process[] processes = Process.GetProcessesByName("EXCEL");
foreach (Process prs in processes)
{
var query = string.Format("SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = {0}", prs.Id);
var search = new ManagementObjectSearcher("root\\CIMV2", query);
var results = search.Get().GetEnumerator();
results.MoveNext();
var queryObj = results.Current;
var parentId = (uint)queryObj["ParentProcessId"];
if (ProcessExists((int)parentId))
{
var parent = Process.GetProcessById((int)parentId);
if (parent.Id == myappid)
{
prs.Kill();
}
}
}
private bool ProcessExists(int id)
{
return Process.GetProcesses().Any(x => x.Id == id);
}
In the above code Process[] processes = Process.GetProcesses(); would give me all the processes. I am looping though the collection and finding parent process id of each. If parent id matches my application process id and the name of the child process is excel, I kill it.
Just an FYI, this bit of code I used to get existing excel processes and then kill excel processes created by my app.
Hashtable myHashtable;
private void Get_Already_Running_Excel()
{
Process[] AllProcesses = Process.GetProcessesByName("excel");
myHashtable = new Hashtable();
int iCount = 0;
foreach (Process ExcelProcess in AllProcesses)
{
myHashtable.Add(ExcelProcess.Id, iCount);
iCount = iCount + 1;
}
}
public void Close_User_Excel()
{
Process[] AllProcesses = Process.GetProcessesByName("excel");
// check to kill the right process
foreach (Process ExcelProcess in AllProcesses)
{
if (myHashtable.ContainsKey(ExcelProcess.Id) == false)
{ ExcelProcess.Kill(); }
}
AllProcesses = null;
}
I am looking for a process by the name of "MyApp.exe" and I want to make sure I get the process that is owned by a particular user.
I use the following code to get a list of the processes:
Process[] processes = Process.GetProcessesByName("MyApp");
This gives me a list of processes, but there does not appear to be a way in the Process class to determine who owns that process? Any thoughts on how I can do this?
You can use WMI to get the user owning a certain process. To use WMI you need to add a reference to the System.Management.dll to your project.
By process id:
public string GetProcessOwner(int processId)
{
string query = "Select * From Win32_Process Where ProcessID = " + processId;
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
ManagementObjectCollection processList = searcher.Get();
foreach (ManagementObject obj in processList)
{
string[] argList = new string[] { string.Empty, string.Empty };
int returnVal = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList));
if (returnVal == 0)
{
// return DOMAIN\user
return argList[1] + "\\" + argList[0];
}
}
return "NO OWNER";
}
By process name (finds the first process only, adjust accordingly):
public string GetProcessOwner(string processName)
{
string query = "Select * from Win32_Process Where Name = \"" + processName + "\"";
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
ManagementObjectCollection processList = searcher.Get();
foreach (ManagementObject obj in processList)
{
string[] argList = new string[] { string.Empty, string.Empty };
int returnVal = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList));
if (returnVal == 0)
{
// return DOMAIN\user
string owner = argList[1] + "\\" + argList[0];
return owner;
}
}
return "NO OWNER";
}
Since WMI is not always a fast way of retrieving information, here is the native P/Invoke way of doing it:
The return value is null when unsuccessful. In order to get the names of processes running under the SYSTEM user, you need to execute this code as administrator.
private static string GetProcessUser(Process process)
{
IntPtr processHandle = IntPtr.Zero;
try
{
OpenProcessToken(process.Handle, 8, out processHandle);
WindowsIdentity wi = new WindowsIdentity(processHandle);
string user = wi.Name;
return user.Contains(#"\") ? user.Substring(user.IndexOf(#"\") + 1) : user;
}
catch
{
return null;
}
finally
{
if (processHandle != IntPtr.Zero)
{
CloseHandle(processHandle);
}
}
}
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);
Here is the VB version for the non C# speakers:
Function GetProcessOwner(ProcessName As String) As String
Dim query = "Select * from Win32_Process Where Name = """ + ProcessName + """"
Dim searcher = New ManagementObjectSearcher(query)
Dim processList = searcher.Get()
For Each obj As ManagementObject In processList
Dim argList As String() = {String.Empty, String.Empty}
Dim returnVal = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList))
If returnVal = 0 Then
' return DOMAIN\user
Dim owner = argList(1) + "\\" + argList(0)
Return owner
End If
Next
Return "NO OWNER"
End Function
Function GetProcessOwner(processId As Integer) As String
Dim query = "Select * From Win32_Process Where ProcessID = " & processId
Dim searcher = New ManagementObjectSearcher(query)
Dim processList = searcher.Get()
For Each obj As ManagementObject In processList
Dim argList As String() = {String.Empty, String.Empty}
Dim returnVal = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList))
If returnVal = 0 Then
' return DOMAIN\user
Return argList(1) + "\\" + argList(0)
End If
Next
Return "NO OWNER"
End Function
Unfortunately there's no native .Net way of getting the process owner.
Have a look at these for a potential solution:
http://msmvps.com/blogs/siva/archive/2006/10/02/Getting-Windows-Process-Owner-Name.aspx
http://www.codeproject.com/KB/cs/processownersid.aspx
var myApp = Process.GetProcessesByName("MyApp").FirstOrDefault();
if (myApp != null)
{
string username = GetUsername(myApp.SessionId);
}
Implementation of method GetUsername here: https://stackoverflow.com/a/35810391/10412686
With the help from detecting-user-name-from-process-id I have written the better - quicker - version of this function:
public static string GetProcessOwnerByID(int processId)
{
IntPtr processHandle = IntPtr.Zero;
IntPtr tokenHandle = IntPtr.Zero;
try
{
processHandle = OpenProcess(PROCESS_QUERY_INFORMATION, false, processId);
if (processHandle == IntPtr.Zero)
return "NO ACCESS";
OpenProcessToken(processHandle, TOKEN_QUERY, out tokenHandle);
using (WindowsIdentity wi = new WindowsIdentity(tokenHandle))
{
string user = wi.Name;
return user.Contains(#"\") ? user.Substring(user.IndexOf(#"\") + 1) : user;
}
}
finally
{
if (tokenHandle != IntPtr.Zero) CloseHandle(tokenHandle);
if (processHandle != IntPtr.Zero) CloseHandle(processHandle);
}
}
Whole file can be found on GitHub gist
Add a reference to your project:
System.Management
Then add the following method to your project:
public string GetProcessOwner(int processId)
{
string MethodResult = null;
try
{
StringBuilder sb = new StringBuilder();
sb.Append(" SELECT ");
sb.Append(" * ");
sb.Append(" FROM ");
sb.Append(" WIN32_PROCESS");
sb.Append(" WHERE ");
sb.Append(" ProcessId = " + processId);
string Query = sb.ToString();
ManagementObjectCollection Processes = new ManagementObjectSearcher(Query).Get();
foreach (ManagementObject Process in Processes)
{
string[] Args = new string[] { "", "" };
int ReturnCode = Convert.ToInt32(Process.InvokeMethod("GetOwner", Args));
switch(ReturnCode)
{
case 0:
MethodResult = Args[1] + "\\" + Args[0];
break;
default:
MethodResult = "None";
break;
}
}
}
catch //(Exception ex)
{
//ex.HandleException();
}
return MethodResult;
}
Then add this method:
public DataTable GetProcessTable()
{
DataTable MethodResult = null;
try
{
List<Process> Processes = Process.GetProcesses().ToList<Process>();
DataTable dt = new DataTable();
dt.Columns.Add("Name", typeof(string));
dt.Columns["Name"].ReadOnly = true;
dt.Columns.Add("Id", typeof(string));
dt.Columns["Id"].ReadOnly = true;
dt.Columns.Add("Owner", typeof(string));
dt.Columns["Owner"].ReadOnly = true;
foreach (Process p in Processes)
{
DataRow r = dt.NewRow();
bool Match = false;
r["Id"] = p.Id.ToString();
r["Name"] = p.ProcessName;
r["Owner"] = GetProcessOwner(p.Id);
dt.Rows.Add(r);
}
MethodResult = dt;
}
catch //(Exception ex)
{
//ex.HandleException();
}
return MethodResult;
}
Calling GetProcessTable() gives you a DataTable of all running processes along with their Id and Name, which is handy because it can be used as a DataGridView's Datasource parameter.
Let me know if you need any more fields adding to the table.
WMI is really the worst possible way how to get this information from Process. But... sometimes you need to get that info from remote process, and in that case you sadly need WMI. So if you have to, or want to use WMI, I suggest to do it like this (it's more than 60% quicker then classic WMI methods above):
Method:
public struct WMIProcessProperties
{
public string Owner;
public int ID;
}
public static async Task<Dictionary<Process, WMIProcessProperties>> GetWMIProperties(this IEnumerable<Process> processes)
{
Dictionary<Process, WMIProcessProperties> result = new Dictionary<Process, WMIProcessProperties>();
if (processes == null || processes.Count() == 0) { return result; }
string selectQuery = "SELECT Handle, ProcessID FROM Win32_Process";
selectQuery += processes.Count() <= 10 ? string.Format(" WHERE ProcessID = {0}", string.Join(" OR ProcessID = ", processes.Select(p => p.Id))) : string.Empty;
using (CimSession session = await Task.Run(() => CimSession.Create(processes.ElementAt(0).MachineName)))
{
List<CimInstance> instances = await Task.Run(() => session.QueryInstances(#"root\cimv2", "WQL", selectQuery).ToList());
List<Task<WMIProcessProperties>> tasks = new List<Task<WMIProcessProperties>>();
for (int i = 0; i < instances.Count; i++)
{
CimInstance currentInstance = instances[i];
tasks.Add(Task.Run(() =>
{
int id = Convert.ToInt32(currentInstance.CimInstanceProperties["ProcessID"].Value);
string owner;
using (CimMethodResult getOwnerResult = session.InvokeMethod(currentInstance, "GetOwner", null))
{
owner = getOwnerResult.OutParameters["User"]?.Value?.ToString();
}
currentInstance.Dispose();
return new WMIProcessProperties { Owner = owner, ID = id };
}));
}
WMIProcessProperties[] wmiProcessProperties = await Task.WhenAll(tasks).ConfigureAwait(false);
for (int i = 0; i < wmiProcessProperties.Length; i++)
{
result.Add(processes.Single(p => p.Id == wmiProcessProperties[i].ID), wmiProcessProperties[i]);
}
}
return result;
}
If you want to see little time comparison, see this answer.
Loop through collection to check for permissions.
Most cases current user will not be administrator
List<Process> processes = Process.GetProcessesByName(Text).ToList();
for (int i = processes.Count - 1; i > -1; i--)
{
try
{
if (processes[i].MainModule?.FileName is null)
processes.RemoveAt(i);
}
catch (Exception)
{
processes.RemoveAt(i);
}
}
System.Security.Principal.WindowsIdentity.GetCurrent().Name
I have a windows service which run a WPF application all the time for all the logged in users which works fine, now in the WPF application i can not get a current username as Environment.UserName; returns 'SYSTEM' which is understandable. so what i thought was to find session id of current process which could be retrieved by Process.GetCurrentProcess().SessionId and then get the list of all users logged in to the machine and looping through it to find the session id match with process session id and later his username.
but i don't how to get the list of all logged in users or i would appreciate if someone can help me with alternative.
I ran into a similar problem while building a Windows Service. Just like you, I had the Session ID and needed to get the corresponding username. Syed's answer above did not work on my machine (Windows 10) as Microsoft seems to have removed the quser executable. After several unsuccessful solution hereon SO, I ran into this particular answer and it inspired my solution:
Here's my code (all of them residing inside a class; in my case, the class inheriting ServiceBase)
[DllImport("Wtsapi32.dll")]
private static extern bool WTSQuerySessionInformation(IntPtr hServer, int sessionId, WtsInfoClass wtsInfoClass, out IntPtr ppBuffer, out int pBytesReturned);
[DllImport("Wtsapi32.dll")]
private static extern void WTSFreeMemory(IntPtr pointer);
private enum WtsInfoClass
{
WTSUserName = 5,
WTSDomainName = 7,
}
private static string GetUsername(int sessionId, bool prependDomain = true)
{
IntPtr buffer;
int strLen;
string username = "SYSTEM";
if (WTSQuerySessionInformation(IntPtr.Zero, sessionId, WtsInfoClass.WTSUserName, out buffer, out strLen) && strLen > 1)
{
username = Marshal.PtrToStringAnsi(buffer);
WTSFreeMemory(buffer);
if (prependDomain)
{
if (WTSQuerySessionInformation(IntPtr.Zero, sessionId, WtsInfoClass.WTSDomainName, out buffer, out strLen) && strLen > 1)
{
username = Marshal.PtrToStringAnsi(buffer) + "\\" + username;
WTSFreeMemory(buffer);
}
}
}
return username;
}
I solve it by executing powershell command "quser" in my WPF application which returns all the logged in users then I am iterating to find session id in which the application is running with user session id and then retrieving his name. below is the function which fetch the username by passing his session id
private string GetUserName(int SessionId)
{
try
{
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript("Quser");
pipeline.Commands.Add("Out-String");
Collection<PSObject> results = pipeline.Invoke();
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
foreach (string User in stringBuilder.ToString().Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).Skip(1))
{
string[] UserAttributes = User.Split(new string[]{" "},StringSplitOptions.RemoveEmptyEntries);
if (int.Parse(UserAttributes[2].Trim()) == SessionId)
{
return UserAttributes[0].Replace(">", string.Empty).Trim();
}
}
return stringBuilder.ToString();
}
catch (Exception ex)
{
}
return string.Empty;
}
the function can be called by
string CurrentUser = GetUserName(Process.GetCurrentProcess().SessionId);
You can try this Code spinet.
Whenever a user logged onto windows, the Username attribute will contain the username of the user. In the case when there are no users in the windows system, there will be no instances of the Win32_ComputerSystem class.
ManagementScope ms = new ManagementScope("\\\\.\\root\\cimv2");
ObjectQuery query = new ObjectQuery("SELECT * FROM Win32_ComputerSystem");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(ms, query);
foreach(ManagementObject mo in searcher.Get())
{
Console.WriteLine(mo["UserName"].ToString());
}
Found this solution on MSDN forums:
using System.Security.Principal;
.
.
.
WindowsPrincipal wp = new WindowsPrincipal(WindowsIdentity.GetCurrent());
String username = wp.Identity.Name;
Now this is easily tested by creating a new Console Application, pasting the above code, and writing the username string to Console. Seems to work fine, but for services it's apparently a more complex situation. Since all services are run in a container that runs under the SYSTEM user, that's what they return. For more information see the below link, esp. all Harry Zhu's answers.
http://social.msdn.microsoft.com/Forums/vstudio/en-US/3be119b0-88b4-442e-9613-6856cbb27adb/how-can-i-get-current-username-in-windows-service?forum=csharpgeneral
It would seem it's not possible to achieve what you are trying to get, as services are totally seperate from users' session.
i made a little search and find this code that should work for you:
it will get the username whom is running the process...
reference: How do I determine the owner of a process in C#?
public string GetProcessOwner(string processName)
{
string query = "Select * from Win32_Process Where Name = \"" + processName + "\"";
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
ManagementObjectCollection processList = searcher.Get();
foreach (ManagementObject obj in processList)
{
string[] argList = new string[] { string.Empty, string.Empty };
int returnVal = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList));
if (returnVal == 0)
{
// return DOMAIN\user
string owner = argList[1] + "\\" + argList[0];
return owner;
}
}
return "NO OWNER";
}
You can try this Code:
string username = "SYSTEM";
var explorer = Process.GetProcessesByName("explorer").FirstOrDefault();
if (explorer != null)
{
username = GetUsername(explorer.SessionId);
}
Implementation of method GetUsername here: https://stackoverflow.com/a/35810391/10412686
There used to be a way to get the active tab's URL from Google Chrome by using FindWindowEx in combination with a SendMessage call to get the text currently in the omnibox. A recent (?) update seems to have broken this method, since Chrome seems to be rendering everything itself now. (You can check with Spy++, AHK Window Spy or Window Detective)
To get the current URL on Firefox and Opera, you can use DDE and WWW_GetWindowInfo. This doesn't seem to be possible on Chrome (anymore?).
This question has an answer with more info about how it used to work, which is this piece of code (which, as I explained, doesn't work anymore - hAddressBox is 0):
var hAddressBox = FindWindowEx(
intPtr,
IntPtr.Zero,
"Chrome_OmniboxView",
IntPtr.Zero);
var sb = new StringBuilder(256);
SendMessage(hAddressBox, 0x000D, (IntPtr)256, sb);
temp = sb.ToString();
So my question is: Is there a new way to get the currently focused tab's URL? (Just the title is not enough)
Edit: Seems like the code in my answer here does not work anymore (though the idea of using AutomationElement does still work) for the later Chrome versions, so look through the other answers for different versions. For example, here's one for Chrome 54: https://stackoverflow.com/a/40638519/377618
The following code seems to work, (thanks to icemanind's comment) but is however resource intensive. It takes about 350ms to find elmUrlBar... a little slow.
Not to mention that we have the problem of working with multiple chrome processes running at the same time.
// there are always multiple chrome processes, so we have to loop through all of them to find the
// process with a Window Handle and an automation element of name "Address and search bar"
Process[] procsChrome = Process.GetProcessesByName("chrome");
foreach (Process chrome in procsChrome) {
// the chrome process must have a window
if (chrome.MainWindowHandle == IntPtr.Zero) {
continue;
}
// find the automation element
AutomationElement elm = AutomationElement.FromHandle(chrome.MainWindowHandle);
AutomationElement elmUrlBar = elm.FindFirst(TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "Address and search bar"));
// if it can be found, get the value from the URL bar
if (elmUrlBar != null) {
AutomationPattern[] patterns = elmUrlBar.GetSupportedPatterns();
if (patterns.Length > 0) {
ValuePattern val = (ValuePattern)elmUrlBar.GetCurrentPattern(patterns[0]);
Console.WriteLine("Chrome URL found: " + val.Current.Value);
}
}
}
Edit: I wasn't happy with the slow method above, so I made it faster (now 50ms) and added some URL validation to make sure we got the correct URL instead of something the user might be searching for on the web, or still being busy typing in the URL. Here's the code:
// there are always multiple chrome processes, so we have to loop through all of them to find the
// process with a Window Handle and an automation element of name "Address and search bar"
Process[] procsChrome = Process.GetProcessesByName("chrome");
foreach (Process chrome in procsChrome) {
// the chrome process must have a window
if (chrome.MainWindowHandle == IntPtr.Zero) {
continue;
}
// find the automation element
AutomationElement elm = AutomationElement.FromHandle(chrome.MainWindowHandle);
// manually walk through the tree, searching using TreeScope.Descendants is too slow (even if it's more reliable)
AutomationElement elmUrlBar = null;
try {
// walking path found using inspect.exe (Windows SDK) for Chrome 31.0.1650.63 m (currently the latest stable)
var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
if (elm1 == null) { continue; } // not the right chrome.exe
// here, you can optionally check if Incognito is enabled:
//bool bIncognito = TreeWalker.RawViewWalker.GetFirstChild(TreeWalker.RawViewWalker.GetFirstChild(elm1)) != null;
var elm2 = TreeWalker.RawViewWalker.GetLastChild(elm1); // I don't know a Condition for this for finding :(
var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""));
var elm4 = elm3.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar));
elmUrlBar = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom));
} catch {
// Chrome has probably changed something, and above walking needs to be modified. :(
// put an assertion here or something to make sure you don't miss it
continue;
}
// make sure it's valid
if (elmUrlBar == null) {
// it's not..
continue;
}
// elmUrlBar is now the URL bar element. we have to make sure that it's out of keyboard focus if we want to get a valid URL
if ((bool)elmUrlBar.GetCurrentPropertyValue(AutomationElement.HasKeyboardFocusProperty)) {
continue;
}
// there might not be a valid pattern to use, so we have to make sure we have one
AutomationPattern[] patterns = elmUrlBar.GetSupportedPatterns();
if (patterns.Length == 1) {
string ret = "";
try {
ret = ((ValuePattern)elmUrlBar.GetCurrentPattern(patterns[0])).Current.Value;
} catch { }
if (ret != "") {
// must match a domain name (and possibly "https://" in front)
if (Regex.IsMatch(ret, #"^(https:\/\/)?[a-zA-Z0-9\-\.]+(\.[a-zA-Z]{2,4}).*$")) {
// prepend http:// to the url, because Chrome hides it if it's not SSL
if (!ret.StartsWith("http")) {
ret = "http://" + ret;
}
Console.WriteLine("Open Chrome URL found: '" + ret + "'");
}
}
continue;
}
}
As of Chrome 54, the following code is working for me:
public static string GetActiveTabUrl()
{
Process[] procsChrome = Process.GetProcessesByName("chrome");
if (procsChrome.Length <= 0)
return null;
foreach (Process proc in procsChrome)
{
// the chrome process must have a window
if (proc.MainWindowHandle == IntPtr.Zero)
continue;
// to find the tabs we first need to locate something reliable - the 'New Tab' button
AutomationElement root = AutomationElement.FromHandle(proc.MainWindowHandle);
var SearchBar = root.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, "Address and search bar"));
if (SearchBar != null)
return (string)SearchBar.GetCurrentPropertyValue(ValuePatternIdentifiers.ValueProperty);
}
return null;
}
All the methods above are failing for me with Chrome V53 and above.
Here's what is working:
Process[] procsChrome = Process.GetProcessesByName("chrome");
foreach (Process chrome in procsChrome)
{
if (chrome.MainWindowHandle == IntPtr.Zero)
continue;
AutomationElement element = AutomationElement.FromHandle(chrome.MainWindowHandle);
if (element == null)
return null;
Condition conditions = new AndCondition(
new PropertyCondition(AutomationElement.ProcessIdProperty, chrome.Id),
new PropertyCondition(AutomationElement.IsControlElementProperty, true),
new PropertyCondition(AutomationElement.IsContentElementProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
AutomationElement elementx = element.FindFirst(TreeScope.Descendants, conditions);
return ((ValuePattern)elementx.GetCurrentPattern(ValuePattern.Pattern)).Current.Value as string;
}
Found it here:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/93001bf5-440b-4a3a-ad6c-478a4f618e32/how-can-i-get-urls-of-open-pages-from-chrome-and-firefox?forum=csharpgeneral
I got results for Chrome 38.0.2125.10 with the next code (the code
inside the 'try' block has to be replaced with this)
var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
if (elm1 == null) { continue; } // not the right chrome.exe
var elm2 = TreeWalker.RawViewWalker.GetLastChild(elm1);
var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.HelpTextProperty, "TopContainerView"));
var elm4 = elm3.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar));
var elm5 = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.HelpTextProperty, "LocationBarView"));
elmUrlBar = elm5.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
I took Angelo's solution and cleaned it up a bit... I have a fixation with LINQ :)
This is the main method as it were; it uses a couple of extension methods:
public IEnumerable<string> GetTabs()
{
// there are always multiple chrome processes, so we have to loop through all of them to find the
// process with a Window Handle and an automation element of name "Address and search bar"
var processes = Process.GetProcessesByName("chrome");
var automationElements = from chrome in processes
where chrome.MainWindowHandle != IntPtr.Zero
select AutomationElement.FromHandle(chrome.MainWindowHandle);
return from element in automationElements
select element.GetUrlBar()
into elmUrlBar
where elmUrlBar != null
where !((bool) elmUrlBar.GetCurrentPropertyValue(AutomationElement.HasKeyboardFocusProperty))
let patterns = elmUrlBar.GetSupportedPatterns()
where patterns.Length == 1
select elmUrlBar.TryGetValue(patterns)
into ret
where ret != ""
where Regex.IsMatch(ret, #"^(https:\/\/)?[a-zA-Z0-9\-\.]+(\.[a-zA-Z]{2,4}).*$")
select ret.StartsWith("http") ? ret : "http://" + ret;
}
Note that the comment is misleading, as comments tend to be - it doesn't actually look at a single AutomationElement. I left it there because Angelo's code had it.
Here's the extension class:
public static class AutomationElementExtensions
{
public static AutomationElement GetUrlBar(this AutomationElement element)
{
try
{
return InternalGetUrlBar(element);
}
catch
{
// Chrome has probably changed something, and above walking needs to be modified. :(
// put an assertion here or something to make sure you don't miss it
return null;
}
}
public static string TryGetValue(this AutomationElement urlBar, AutomationPattern[] patterns)
{
try
{
return ((ValuePattern) urlBar.GetCurrentPattern(patterns[0])).Current.Value;
}
catch
{
return "";
}
}
//
private static AutomationElement InternalGetUrlBar(AutomationElement element)
{
// walking path found using inspect.exe (Windows SDK) for Chrome 29.0.1547.76 m (currently the latest stable)
var elm1 = element.FindFirst(TreeScope.Children,
new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
var elm2 = TreeWalker.RawViewWalker.GetLastChild(elm1); // I don't know a Condition for this for finding :(
var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""));
var elm4 = elm3.FindFirst(TreeScope.Children,
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar));
var result = elm4.FindFirst(TreeScope.Children,
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom));
return result;
}
}
I discovered this post and was able to successfully pull the URL from chrome in C# using these methods, thank you everyone!
Unfortunately with the recent Chrome 69 update, the AutomationElement tree traversal broke again.
I found this article by Microsoft: Navigate Among UI Automation Elements with TreeWalker
And used it to whip up a simple function that searches for the AutomationElement with the "edit" control type we're looking for, instead of traversing a tree heirarchy that is always changing, and from there extract the url Value from that AutomationElement.
I wrote a simple class that wraps this all up: Google-Chrome-URL-Check-C-Sharp.
The readme explains a bit on how to use it.
All in all it might just be a little more reliable, and hope some of you find it useful.
Refering to the solution of Angelo Geels, here is a patch for version 35 - the code inside the "try" block has to be replaced with this:
var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
if (elm1 == null) { continue; } // not the right chrome.exe
var elm2 = TreeWalker.RawViewWalker.GetLastChild(elm1); // I don't know a Condition for this for finding
var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""));
var elm4 = TreeWalker.RawViewWalker.GetNextSibling(elm3); // I don't know a Condition for this for finding
var elm7 = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar));
elmUrlBar = elm7.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom));
I took it from here:
http://techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/nftechsupt.web+WinBatch/dotNet/System_CodeDom+Grab~URL~from~Chrome.txt
For me only the active chrome window has a MainWindowHandle. I got around this by looking through all windows for chrome windows, and then using those handles instead. For example:
public delegate bool Win32Callback(IntPtr hwnd, IntPtr lParam);
[DllImport("user32.dll")]
protected static extern bool EnumWindows(Win32Callback enumProc, IntPtr lParam);
private static bool EnumWindow(IntPtr handle, IntPtr pointer)
{
List<IntPtr> pointers = GCHandle.FromIntPtr(pointer).Target as List<IntPtr>;
pointers.Add(handle);
return true;
}
private static List<IntPtr> GetAllWindows()
{
Win32Callback enumCallback = new Win32Callback(EnumWindow);
List<IntPtr> pointers = new List<IntPtr>();
GCHandle listHandle = GCHandle.Alloc(pointers);
try
{
EnumWindows(enumCallback, GCHandle.ToIntPtr(listHandle));
}
finally
{
if (listHandle.IsAllocated) listHandle.Free();
}
return pointers;
}
And then to get all chrome windows:
[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 string GetTitle(IntPtr handle)
{
int length = GetWindowTextLength(handle);
StringBuilder sb = new StringBuilder(length + 1);
GetWindowText(handle, sb, sb.Capacity);
return sb.ToString();
}
and finally:
GetAllWindows()
.Select(GetTitle)
.Where(x => x.Contains("Google Chrome"))
.ToList()
.ForEach(Console.WriteLine);
Hopefully this saves someone else some time in figuring out how to actually get the handles of all the chrome windows.
For version 53.0.2785 got it working with this:
var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
if (elm1 == null) { continue; } // not the right chrome.exe
var elm2 = elm1.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""))[1];
var elm3 = elm2.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""))[1];
var elm4 = elm3.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "principal"));
var elm5 = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""));
elmUrlBar = elm5.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
I am trying to retrieve data from a particular installed application, such as the Installation Folder, Uninstall String, Version number, etc. When I run the following code, I get the Install Folder but it returns four rows of question marks for the UninstallString value. Any ideas?
public static void FindInstalled(string AppName)
{
StringBuilder sbProductCode = new StringBuilder(39);
int iIdx = 0;
while (
0 == MsiEnumProducts(iIdx++, sbProductCode))
{
Int32 productNameLen = 512;
StringBuilder sbProductName = new StringBuilder(productNameLen);
MsiGetProductInfo(sbProductCode.ToString(), "ProductName", sbProductName, ref productNameLen);
if (sbProductName.ToString().Contains(AppName))
{
Int32 installDirLen = 1024;
StringBuilder sbInstallDir = new StringBuilder(installDirLen);
MsiGetProductInfo(sbProductCode.ToString(), "InstallLocation", sbInstallDir, ref installDirLen);
Console.Writeline("Install Directory - {0}",sbInstallDir.ToString());
MsiGetProductInfo(sbProductCode.ToString(), "UninstallString", sbInstallDir, ref installDirLen);
Console.Writeline("Uninstall String - {0}", sbInstallDir.ToString());
}
}
}
UninstallString isn't a valid property. See http://msdn.microsoft.com/en-us/library/aa370130(VS.85).aspx for a list of valid properties.
If you open the Windows Installer header file ("msi.h") and search for the text "UninstallString", you won't find it. Also if you look in the property reference at http://msdn.microsoft.com/en-us/library/aa370905(VS.85).aspx and search that page for "UninstallString", you won't find it either.
My advice would be to read the properties out of the registry instead. See http://msdn.microsoft.com/en-us/library/aa372105(VS.85).aspx for details. You can get the details you need from that.
How about something like this:
public static void FindInstalled(AppName)
{
RegistryKey myRegKey = Registry.LocalMachine;
myRegKey = myRegKey.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall");
String[] subkeyNames = myRegKey.GetSubKeyNames();
foreach (String s in subkeyNames)
{
RegistryKey UninstallKey = Registry.LocalMachine;
UninstallKey = UninstallKey.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + s);
Object oValue = UninstallKey.GetValue("DisplayName");
if (oValue != null)
{
if (oValue.ToString() == AppName)
{
oValue = UninstallKey.GetValue("UninstallString");
Console.Writeline("Uninstall URL - {0}", oValue.ToString());
break;
}
}
}
}