Send window message to WPF application from another WPF application - c#

I used this code in the server side
void Window_Loaded(object sender, RoutedEventArgs e)
{
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
source.AddHook(new HwndSourceHook(WndProc));
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// Handle messages...
var htLocation = DefWindowProc(hwnd, msg, wParam, lParam).ToInt32();
if (msg == 1)
{
MessageBox.Show("" + msg);
}
return new IntPtr(1);
}
And I send the message from the client side like this
SendMessage(m_Process.MainWindowHandle, 1, (IntPtr)(-1), (IntPtr)(-1));
The problem is that the server side cannot receive this message, why?

I found the mistake
the message id I sent must be 0x0112 not 1
this is for windows command

Related

Argument Out Of Range Exception in DataGridView when Running Procedure / Bulk Insert

I'm making a program that register an product. This product have several fields, but only two of them are mandatory to start the registration (they can be edited later). Those informations are putted in a DataGridView, and then i convert it to a DataTable, so i can bulk insert it to a temporary table so i can process and clean all the data in it. Just after the bulk insert, i run a procedure that do all the data treatment and check if the informations are right. In the same procedure, after the checks, i insert the data into the original table.
All this process is ok, everything is running fine in C# and in the SQL Server until it. But there is an error that happens every time when i try to do a MessageBox.Show() or when i try to open another form.
Here is the function that i created to run the procedure. Everything is running fine, the only thing that throws the error is the message box. I commented all of them and runned the program again and it didn't throw anything at the end.
Here is the code:
public void RodarProcedureAddEdit(string procedureName, bool isAdd)
{
string result = String.Empty;
// Variáveis para definir os parâmetros do frmMessageBox.
string message = String.Empty;
string headerText = String.Empty;
bool isError = false;
using (SqlConnection conn = new SqlConnection(Globals.connectionString))
{
try
{
conn.Open();
using (SqlCommand comm = new SqlCommand(procedureName, conn))
{
comm.CommandType = CommandType.StoredProcedure;
if(isAdd) comm.Parameters.AddWithValue("#methodType", "A");
else comm.Parameters.AddWithValue("#methodType", "E");
comm.Parameters.AddWithValue("#userID", user.userID);
if(isAdd) comm.Parameters.AddWithValue("#requestID", 0);
else comm.Parameters.AddWithValue("#requestID", requestID);
comm.Parameters.Add("#responseMessage", SqlDbType.NVarChar, 250).Direction = ParameterDirection.Output;
comm.Parameters.Add("#responsibleUser", SqlDbType.NVarChar, 250).Direction = ParameterDirection.Output;
comm.ExecuteNonQuery();
result = comm.Parameters["#responseMessage"].Value.ToString();
string responsibleUser = comm.Parameters["#responsibleUser"].Value.ToString();
if (result == "OK")
{
if (isAdd)
{
message = "O cadastro foi concluído com sucesso";
headerText = "Cadastro";
isError = false;
//MessageBox.Show("O cadastro foi concluído com sucesso", "Cadastro", MessageBoxButtons.OK, MessageBoxIcon.Information);
this.Close();
}
else
{
// Enviar E-mail quando finalizar a requisição.
if (responsibleUser != string.Empty)
{
try
{
Outlook.Application app = new Outlook.Application();
Outlook.MailItem mailItem = app.CreateItem(Outlook.OlItemType.olMailItem);
mailItem.Subject = $"Requisição nº {requestID} foi finalizada - Product Catalog";
mailItem.To = DAO.User.getMailAddress(responsibleUser);
mailItem.Body = Globals.finalizedMailBody.Replace("$requisicao", requestID.ToString());
mailItem.Send();
}
catch (Exception ex)
{
message = ex.Message;
headerText = "Erro";
isError = true;
//MessageBox.Show(ex.Message, "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
message = "A alteração foi realizada com sucesso";
headerText = "Alteração";
isError = false;
//MessageBox.Show("A alteração foi realizada com sucesso!", "Cadastro", MessageBoxButtons.OK, MessageBoxIcon.Information);
this.Close();
}
}
message = result;
headerText = "Erro";
isError = true;
//else MessageBox.Show(result, "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception ex)
{
message = ex.Message;
headerText = "Erro";
isError = true;
//MessageBox.Show(ex.Message, "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
// Isso foi instanciado para conseguir "criar" uma MessageBox, mais detalhes no formulário.
if (message != String.Empty && headerText != String.Empty)
{
frmMessageBox frmMessageBox = new frmMessageBox(message, headerText, isError);
frmMessageBox.ShowDialog();
}
}
}
}
At the end, after investigating a lot i find that the error is throwing because of the index in the DataGridView.
Here is the complete exception:
at System.Collections.ArrayList.get_Item(Int32 index)
at System.Windows.Forms.DataGridViewColumnCollection.get_Item(Int32 index)
at System.Windows.Forms.DataGridView.PositionEditingControl(Boolean setLocation, Boolean setSize, Boolean setFocus)
at System.Windows.Forms.DataGridView.PerformLayoutPrivate(Boolean useRowShortcut, Boolean computeVisibleRows, Boolean invalidInAdjustFillingColumns, Boolean repositionEditingControl)
at System.Windows.Forms.DataGridView.ResetUIState(Boolean useRowShortcut, Boolean computeVisibleRows)
at System.Windows.Forms.DataGridViewRowCollection.OnCollectionChanged_PreNotification(CollectionChangeAction cca, Int32 rowIndex, Int32 rowCount, DataGridViewRow& dataGridViewRow, Boolean changeIsInsertion)
at System.Windows.Forms.DataGridViewRowCollection.OnCollectionChanged(CollectionChangeEventArgs e, Int32 rowIndex, Int32 rowCount)
at System.Windows.Forms.DataGridViewRowCollection.AddInternal(Boolean newRow, Object[] values)
at System.Windows.Forms.DataGridView.AddNewRow(Boolean createdByEditing)
at System.Windows.Forms.DataGridView.OnCurrentCellDirtyStateChanged(EventArgs e)
at System.Windows.Forms.DataGridView.set_IsCurrentCellDirtyInternal(Boolean value)
at System.Windows.Forms.DataGridView.NotifyCurrentCellDirty(Boolean dirty)
at System.Windows.Forms.DataGridViewComboBoxEditingControl.OnSelectedIndexChanged(EventArgs e)
at System.Windows.Forms.ComboBox.WmReflectCommand(Message& m)
at System.Windows.Forms.ComboBox.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.SendMessage(HandleRef hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at System.Windows.Forms.Control.SendMessage(Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.Control.ReflectMessageInternal(IntPtr hWnd, Message& m)
at System.Windows.Forms.Control.WmCommand(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ScrollableControl.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at System.Windows.Forms.NativeWindow.DefWndProc(Message& m)
at System.Windows.Forms.Control.DefWndProc(Message& m)
at System.Windows.Forms.Control.WmCommand(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ComboBox.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
I tried everything, i even created a new form that was exactly like a message box but it throwed the exception in the frmMessageBox.ShowDialog();
I'm using SQL Server as my database.
Can someone help me with it?
Any doubts please feel free to ask! Thanks a lot already!
Edit:
Here is the code of the DataGridView instantiation.
dataMKT = new DataGridView();
dataMKT.Location = new Point(17, 43);
dataMKT.Size = new Size(850, 268);
dataMKT.CellMouseClick += dataGridView_CellMouseClick;
dataMKT.CellEnter += dataGridView_CellEnter;
dataMKT.DataSourceChanged += dataMKT_DataSourceChanged;
dataMKT.DataError += dataMKT_DataError;
dataMKT.CellValueChanged += new DataGridViewCellEventHandler(dataMKT_CellValueChanged);
dataMKT.CurrentCellDirtyStateChanged += new EventHandler(dataMKT_CurrentCellDirtyStateChanged);
dataMKT.CellEndEdit += dataMKT_CellEndEdit;
tpgMKT.Controls.Add(dataMKT);
And Binding.
dataMKT.DataSource = Util.Util.GetView(requestID, viewName);
GetView method:
public static DataTable GetView(int requestID, string viewName)
{
DataTable dataTable = new DataTable();
string queryGetTable = $"SELECT * FROM {viewName} WHERE [Request ID] = {requestID}";
using (SqlConnection conn = new SqlConnection(Globals.connectionString))
{
try
{
conn.Open();
using(SqlDataAdapter adapter = new SqlDataAdapter(queryGetTable, conn))
{
adapter.Fill(dataTable);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
return dataTable;
}

Out Of Memory with Image.FromFile without stream

I have a code where I set the picturebox.image to the filePath below but when I run the program it throws a memory exeption. The image is a .jpg format. and as you can see in the code, I am not using a stream. Do I need to use I stream so that the exeption doesn't happen? If so then how do I use it?
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.IO;
using System.Net;
using System.Web;
using System.Runtime.InteropServices;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public WebClient web;
public String html;
public Form1()
{
InitializeComponent();
List<string> plants = new List<string>();
web = new WebClient();
html = web.DownloadString("https://bonnieplants.com/how-to-grow/");
MatchCollection m1 = Regex.Matches(html, "(.+?)", RegexOptions.Singleline);
foreach(Match m in m1)
{
string plant = m.Groups[1].Value;
plants.Add(plant);
}
plants.Sort();
comboBox1.Items.AddRange(plants.ToArray());
}
private void button1_Click(object sender, EventArgs e)
{
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Uri imageUrl = new Uri("https://edge.bonnieplants.com/www/img/products/artichokes-400px-30.jpg");
string fileName = System.IO.Path.GetFileName(imageUrl.LocalPath);
fileName = fileName.Replace("-400px-30", "");
web.DownloadFileAsync(imageUrl, fileName);
string filePath = Application.StartupPath.ToString() + #"\" + fileName;
if(#"C:\Users\user\source\repos\WindowsFormsApp1\WindowsFormsApp1\bin\Debug\artichokes.jpg" == filePath)
{
Console.WriteLine(filePath);
}
pictureBox1.Image = Image.FromFile(Application.StartupPath.ToString() + #"\" + fileName);
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
}
and here is the exeption :
System.OutOfMemoryException
HResult=0x8007000E
Message=Out of memory.
Source=System.Drawing
StackTrace:
at System.Drawing.Image.FromFile(String filename, Boolean useEmbeddedColorManagement)
at System.Drawing.Image.FromFile(String filename)
at WindowsFormsApp1.Form1.comboBox1_SelectedIndexChanged(Object sender, EventArgs e) in C:\Users\user\source\repos\WindowsFormsApp1\WindowsFormsApp1\Form1.cs:line 56
at System.Windows.Forms.ComboBox.OnSelectedIndexChanged(EventArgs e)
at System.Windows.Forms.ComboBox.WmReflectCommand(Message& m)
at System.Windows.Forms.ComboBox.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.SendMessage(HandleRef hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at System.Windows.Forms.Control.SendMessage(Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.Control.ReflectMessageInternal(IntPtr hWnd, Message& m)
at System.Windows.Forms.Control.WmCommand(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ScrollableControl.WndProc(Message& m)
at System.Windows.Forms.Form.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at System.Windows.Forms.NativeWindow.DefWndProc(Message& m)
at System.Windows.Forms.Control.DefWndProc(Message& m)
at System.Windows.Forms.Control.WmCommand(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ComboBox.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.Run(Form mainForm)
at WindowsFormsApp1.Program.Main() in C:\Users\user\source\repos\WindowsFormsApp1\WindowsFormsApp1\Program.cs:line 19
UPDATE:
I figured out that the actual image is not formatted correctly and does not open in the file explorer. Does anybody know why?
................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
web.DownloadFileAsync(imageUrl, fileName); downloads in the background, so you have to wait for it to finish before trying to open the picture. You can subscribe to the OnDownloadFileCompleted event to know when the download is finished.
An alternative is just to replace with a synchronous download:
web.DownloadFile(imageUrl, fileName);
However, if you execute that from the main thread, you will freeze the UI while the picture is downloading. So, use with care.

C# execute on command prompt running geth

I have a listener service where I can send a command to it, but with this service, it's unable to send a command to a command prompt running ethereuem's geth. Is there a way to forcibly get the keyboard strokes through?
I notice that I have other code that can find a command prompt by name or id and able to bring that window to the front, but when I attempt to do that with the command prompt that is running geth, it can't seem to bring that window to the front. I hope that bit of information may help.
namespace Listener
{
class Program
{
static void Main(string[] args)
{
using (var listener = new HttpListener())
{
listener.Prefixes.Add("http://localhost:8081/mytest/");
listener.Start();
string command = string.Empty;
for (; ; )
{
Console.WriteLine("Listening...");
HttpListenerContext context = listener.GetContext();
HttpListenerRequest request = context.Request;
// TODO: read and parse the JSON data from 'request.InputStream'
using (StreamReader reader = new StreamReader(request.InputStream))
{
// Would prefer string[] result = reader.ReadAllLines();
string[] result = reader.ReadToEnd().Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
foreach (var s in result)
{
command = s;
}
}
// send command to other geth window
sendKeystroke(command);
using (HttpListenerResponse response = context.Response)
{
// returning some test results
// TODO: return the results in JSON format
string responseString = "<HTML><BODY>Hello, world!</BODY></HTML>";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
using (var output = response.OutputStream)
{
output.Write(buffer, 0, buffer.Length);
}
}
}
}
}
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
private static void sendToOpenCmd(string command)
{
int processId = 13420;
//processId = int.Parse(13420);
System.Diagnostics.Process proc = (from n in System.Diagnostics.Process.GetProcesses()
where n.ProcessName == "geth"
//where n.Id == processId
select n).FirstOrDefault();
if (proc == null)
{
//MessageBox.Show("No such process.");
Console.WriteLine("No such process.");
}
else
{
SetForegroundWindow(proc.MainWindowHandle);
SendKeys.SendWait(command + "{enter}");
Console.WriteLine("Sent! " + command + " " + DateTime.Now.ToString());
}
}
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
public static void sendKeystroke(string command)
{
const uint WM_KEYDOWN = 0x100;
const uint WM_SYSCOMMAND = 0x018;
const uint SC_CLOSE = 0x053;
IntPtr WindowToFind = FindWindow(null, "geth");
//ushort[] result = command.Where(i => ushort.TryParse(i, out short s)).Select(ushort.Parse);
//ushort[] result = command.Where(i => { ushort r = 0; return ushort.TryParse(i, out r); }).Select(ushort.Parse);
ushort result;
ushort.TryParse(command, out result);
IntPtr result3 = SendMessage(WindowToFind, WM_KEYDOWN, ((IntPtr)result), (IntPtr)0);
//IntPtr result3 = SendMessage(WindowToFind, WM_KEYUP, ((IntPtr)c), (IntPtr)0);
}
}
}
ohhh there seems to be three process id's that windows has on that command prompt running geth. i'm not sure why there's three. i tried all three, and one of them worked for me. so i can't call the process by name of "geth", that doesn't seem to be the correct window or process to send the keystrokes to. hopefully this helps someone else!

Bring browser to front that is started by ShellExec/Process.Start

The complex solution below is justified by the need of bringing a browser window to front. It is working ~90% of the time. The problem is the 10%, when it doesn't.
I have an application that is running on a different desktop than the user's active one (it is a screensaver).
I also have a windows service that receives events from the screensaver. This service then does the following:
Impersonates the currently logged in user and starts a helper application with a URL in the command line arguments.
The helper application is started by CreateProcessAsUser - this is also the justification for the helper, I need to use ShellExec, so a separate process have to be used.
This helper application does the following:
Waits until the user's current desktop becomes active. It does a while loop with some sleep until then.
Then it finds out the user's default browser
Starts the default browser using ShellExec (Process.Start in C#), and passes the browser some command line arguments and the URL.
The actual command line invoked by the helper application is this:
cmd /C start "" C:\PathToBrowser\Browser.exe URL -someargument
Up to this point everything is working except one important thing: The browser is not brought to front in all possible cases.
Is there anything further than this, that I could do with these browsers to force them to come to front? My problem is this:
Let's say I start Chrome from command line. Chrome will just send a message to the already running instance, and quit. So I can't rely on the PID and the hWnd of the process I started, it will not be the same as the one actually showing the webpage.
Any help would be much appreciated.
Thanks to cubrr for the help, his idea worked with some extension from my part. First of all, I have to find out the Title of the webpage that will be displayed within the browser. After this I have to use EnumWindows to find the newly opened browser window, and call SetForegroundWindow on it.
My solution is based on these other sources:
How to use EnumWindows to find a certain window by partial title.
Get the title from a webpage.
Bring to forward window when minimized
The solution suggested by cubrr, using FindWindow (you have to know the exact window title to be able to use this):
[DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindow(String lpClassName, String lpWindowName);
[DllImport("USER32.DLL")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool IsIconic(IntPtr handle);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr handle, int nCmdShow);
void Main()
{
const int SW_RESTORE = 9;
var hWnd = FindWindow(null, "Google - Google Chrome");
if (IsIconic(hWnd))
ShowWindow(hWnd, SW_RESTORE);
SetForegroundWindow(hWnd);
}
Here is the final code I ended up using:
public class MyClass
{
private const int SW_RESTORE = 9;
[DllImport("User32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool BringWindowToTop(IntPtr hWnd);
[DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
private static extern IntPtr FindWindow(String lpClassName, String lpWindowName);
[DllImport("user32.dll")]
private static extern bool IsIconic(IntPtr handle);
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr handle, int nCmdShow);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
public static string GetWebPageTitle(string url)
{
// Create a request to the url
HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest;
// If the request wasn't an HTTP request (like a file), ignore it
if (request == null) return null;
// Use the user's credentials
request.UseDefaultCredentials = true;
// Obtain a response from the server, if there was an error, return nothing
HttpWebResponse response = null;
try { response = request.GetResponse() as HttpWebResponse; }
catch (WebException) { return null; }
// Regular expression for an HTML title
string regex = #"(?<=<title.*>)([\s\S]*)(?=</title>)";
// If the correct HTML header exists for HTML text, continue
if (new List<string>(response.Headers.AllKeys).Contains("Content-Type"))
if (response.Headers["Content-Type"].StartsWith("text/html"))
{
// Download the page
WebClient web = new WebClient();
web.UseDefaultCredentials = true;
string page = web.DownloadString(url);
// Extract the title
Regex ex = new Regex(regex, RegexOptions.IgnoreCase);
return ex.Match(page).Value.Trim();
}
// Not a valid HTML page
return null;
}
public static void BringToFront(string title)
{
try
{
if (!String.IsNullOrEmpty(title))
{
IEnumerable<IntPtr> listPtr = null;
// Wait until the browser is started - it may take some time
// Maximum wait is (200 + some) * 100 milliseconds > 20 seconds
int retryCount = 100;
do
{
listPtr = FindWindowsWithText(title);
if (listPtr == null || listPtr.Count() == 0)
{
Thread.Sleep(200);
}
} while (--retryCount > 0 || listPtr == null || listPtr.Count() == 0);
if (listPtr == null)
return;
foreach (var hWnd in listPtr)
{
if (IsIconic(hWnd))
ShowWindow(hWnd, SW_RESTORE);
SetForegroundWindow(hWnd);
}
}
}
catch (Exception)
{
// If it fails at least we tried
}
}
public static string GetWindowText(IntPtr hWnd)
{
int size = GetWindowTextLength(hWnd);
if (size++ > 0)
{
var builder = new StringBuilder(size);
GetWindowText(hWnd, builder, builder.Capacity);
return builder.ToString();
}
return String.Empty;
}
public static IEnumerable<IntPtr> FindWindowsWithText(string titleText)
{
IntPtr found = IntPtr.Zero;
List<IntPtr> windows = new List<IntPtr>();
EnumWindows(delegate(IntPtr wnd, IntPtr param)
{
if (GetWindowText(wnd).Contains(titleText))
{
windows.Add(wnd);
}
return true;
}, IntPtr.Zero);
return windows;
}
[STAThread]
public static int Main(string[] args)
{
try
{
if (args.Count() == 0)
return 0;
// ...
// Wait until the user's desktop is inactive (outside the scope of this solution)
// ...
String url = args[0];
System.Diagnostics.Process process = new System.Diagnostics.Process();
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
startInfo.FileName = "cmd.exe";
// ...
// Get the path to the default browser from registry, and create a StartupInfo object with it.
// ...
process.StartInfo = startInfo;
process.Start();
try
{
process.WaitForInputIdle();
}
catch (InvalidOperationException)
{
// if the process exited then it passed the URL on to the other browser process.
}
String title = GetWebPageTitle(url);
if (!String.IsNullOrEmpty(title))
{
BringToFront(title);
}
return 0;
}
catch (System.Exception ex)
{
return -1;
}
}
}

CreateProcessAsUser Multiple Application Instances?

I'm attempting to launch a service using CreateProcessAsUser but for some reason multiple (30+) instances of the EXE are being created when debugging. The processes begin to spawn on this line of code:
ret = CreateProcessAsUser(DupedToken, Path, null, ref sa, ref sa, false, 0, (IntPtr)0, "c:\\", ref si, out pi);
I used code from this example - http://support.microsoft.com/default.aspx?scid=kb;EN-US;889251.
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public int 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;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public extern static bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public extern static bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, String lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment,
String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
public extern static bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType,
int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);
string curFile2 = AppDomain.CurrentDomain.BaseDirectory + "OnStart.txt";
public void createProcessAsUser()
{
IntPtr Token = new IntPtr(0);
IntPtr DupedToken = new IntPtr(0);
bool ret;
//Label2.Text+=WindowsIdentity.GetCurrent().Name.ToString();
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.bInheritHandle = false;
sa.Length = Marshal.SizeOf(sa);
sa.lpSecurityDescriptor = (IntPtr)0;
Token = WindowsIdentity.GetCurrent().Token;
const uint GENERIC_ALL = 0x10000000;
const int SecurityImpersonation = 2;
const int TokenType = 1;
ret = DuplicateTokenEx(Token, GENERIC_ALL, ref sa, SecurityImpersonation, TokenType, ref DupedToken);
if (ret == false)
File.AppendAllText(curFile2, "DuplicateTokenEx failed with " + Marshal.GetLastWin32Error());
else
File.AppendAllText(curFile2, "DuplicateTokenEx SUCCESS");
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "";
string Path;
Path = #"C:\myEXEpath";
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
ret = CreateProcessAsUser(DupedToken, Path, null, ref sa, ref sa, false, 0, (IntPtr)0, "c:\\", ref si, out pi);
if (ret == false)
File.AppendAllText(curFile2, "CreateProcessAsUser failed with " + Marshal.GetLastWin32Error());
else
{
File.AppendAllText(curFile2, "CreateProcessAsUser SUCCESS. The child PID is" + pi.dwProcessId);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
ret = CloseHandle(DupedToken);
if (ret == false)
File.AppendAllText(curFile2, Marshal.GetLastWin32Error().ToString() );
else
File.AppendAllText(curFile2, "CloseHandle SUCCESS");
}
The steps you outlined above will generate one process per execution of the method createProcessAsUser(). Now this method does not contain any code to terminate or kill the process so repeatidly calling this method will generate more than one process. As your code is displayed the method will inface generate only one process.
I think the real answer is how are you calling this method. As you stated in the comment
I'm trying to launch the .exe in the user session
I can only assume you may be calling this process from the Session start, Application_BeginRequest or another method that may be executed multiple times depending on how your application is designed (the calling code for this method would be great as an edit).
As I stated earlier the exe is being executed every time the method is called and not terminated. If you only ever want one instance of the application running you will have to examine the process tree to identify if the process is already running. Now if you should have one process running per user you will need to do the above but also maintain a reference the process ID that was created the first time the application started.
Review the code below for the changes (simplified)
public void createProcessAsUser()
{
//one process per session
object sessionPID = Session["_servicePID"];
if (sessionPID != null && sessionPID is int && Process.GetProcessById((int)sessionPID) != null)
return; //<-- Return process already running for session
else
Session.Remove("_servicePID");
//one process per application
object applicationPID = Application["_applicationPID"];
if (applicationPID != null && applicationPID is int && Process.GetProcessById((int)applicationPID) != null)
return; //<-- Process running for application
else
Application.Remove("_applicationPID");
//omitted starting code
if (ret == false)
// omitted log failed
else
{
// omitted log started
//for one process per session
Session["_servicePID"] = Convert.ToInt32(pi.dwProcessId);
//for one process per application
Application["_applicationPID"] = Convert.ToInt32(pi.dwProcessId);
//close handles
}
// omitted the rest of the method
}
This simple saves a reference to the Process ID that was created for the application into either the Session state for one process per user or the Application state for one process per application instance.
Now if this is the intended result you may also want to look at Terminating the process either when the application shutdown (gracefully) or the session ends. That would be very similar to our first check but can be done as seen below. *note this doesn't take into account the worker process shutting down without calling the session \ application end events those should be handled as well possibly in the application start.
//session end
void Session_End(object sender, EventArgs e)
{
object sessionPID = Session["_servicePID"];
if (sessionPID != null && sessionPID is int)
{
Process runningProcess = Process.GetProcessById((int)sessionPID);
if (runningProcess != null)
runningProcess.Kill();
}
}
//application end
void Application_End(object sender, EventArgs e)
{
object applicationPID = Application["_applicationPID"];
if (applicationPID != null && applicationPID is int && Process.GetProcessById((int)applicationPID) != null)
{
Process runningProcess = Process.GetProcessById((int)applicationPID);
if (runningProcess != null)
runningProcess.Kill();
}
}
Again, back to the original question how do you stop the multiple instances. The answer is simply stop the ability to spawn multiple instances by examining how you start the instances (I.e. the calling code to the method createProcessAsUser()) and adjust your method accordingly to avoid multiple calls.
Please post an edit if this inst helpful with details on how the createProcessAsUser() method is called.
Update 1:
Session \ Application does not exist in the context. This will happen if the method createProcessUser() is in a different class than an ASPX page (as it is on the tutorial).
Because of this you will need to change for the existance of an HttpContext this can simply done by calling
HttpContext.Currrent
I have adapted the method above to include checks to the HttpContext
public void createProcessAsUser()
{
//find the http context
var ctx = HttpContext.Current;
if (ctx == null)
throw new Exception("No Http Context");
//use the following code for 1 process per user session
object sessionPID = ctx.Session["_servicePID"];
if (sessionPID != null && sessionPID is int && Process.GetProcessById((int)sessionPID) != null)
return; //<-- Return process already running for session
else
ctx.Session.Remove("_servicePID");
//use the following code for 1 process per application instance
object applicationPID = ctx.Application["_applicationPID"];
if (applicationPID != null && applicationPID is int && Process.GetProcessById((int)sessionPID) != null)
return; //<-- Process running for application
else
ctx.Application.Remove("_applicationPID");
// omitted code
if (ret == false)
{
//omitted logging
}
else
{
//omitted logging
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
//for one process per session
ctx.Session["_servicePID"] = Convert.ToInt32(pi.dwProcessId);
//for one process per application
ctx.Application["_applicationPID"] = Convert.ToInt32(pi.dwProcessId);
}
//omitted the rest
}
You will not the changes are in the first few lines where it gets the current HttpContext (you must add using System.Web) by calling var ctx = HttpContext.Current
Next we just check that the ctx variable is not null. If it is null I am throwing an exception, however you can handle this anyway you wish.
From there instead of directly calling Session and Application I have changed the references to ctx.Session... and ctx.Application...
Update 2:
This is a Windows Application calling the method above. Now this changes the ball game as the code above is really meant to start a process as the impersonated windows identity. Now Impersonation is typcially done in WebApplications not WinForms (can be done though).
If you are not impersonating a different user than the user who is running the application. Meaning the user logged in is the user that is running the application. If this is so then your code becomes ALOT easier.
Below is an example of how this can be achieved.
/// <summary>
/// static process ID value
/// </summary>
static int? processID = null;
public void startProcess()
{
//check if the processID has a value and if the process ID is active
if (processID.HasValue && Process.GetProcessById(processID.Value) != null)
return;
//start a new process
var process = new Process();
var processStartInfo = new ProcessStartInfo(#"C:\myProg.exe");
processStartInfo.CreateNoWindow = true;
processStartInfo.UseShellExecute = false;
process.StartInfo = processStartInfo;
process.Start();
//set the process id
processID = process.Id;
}
Again as this is a Win Forms application you can use the Process object to launch a process, this windows application will run as the user running the Windows Forms application. In this example we also hold a static reference to the processID and check the if the processID (if found) is already running.

Categories