CreateProcessAsUser Multiple Application Instances? - c#

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.

Related

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!

How to Start a process that interacts with the server's desktop in ASP.NET 3.5 through IIS7?

There are lots of answers, try this-that, but nothing works. Access denied.
We are starting an application on the server, and automating it to do certain Quick tasks. Of course I can start it... (but it cannot run hidden, it must run in a real-desktop mode).
I've tried all manner of different elevation/impersonation techniques. Yes I've selected the IIS-Interact with desktop box. In the web.config I've got the impersonation flag...
Here is relevant code with some commented out attempts:
private const int WM_CLOSE = 16;
private const int BN_CLICKED = 245;
private const int LB_GETTEXT = 0x0189;
private const int LB_GETTEXTLEN = 0x018A;
private const int WM_SETTEXT = 0X000C;
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_PROVIDER_DEFAULT = 0;
public const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
WindowsImpersonationContext impersonationContext;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
protected static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
protected static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
protected static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);
[DllImport("advapi32.dll")]
public static extern int LogonUserA(String lpszUserName,
String lpszDomain,
String lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool CloseHandle(IntPtr handle);
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommands nCmdShow);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr GetProcessWindowStation();
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr GetThreadDesktop(int dwThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern int GetCurrentThreadId();
public void findOurProcess(string filePath)
{
IntPtr hwnd = IntPtr.Zero;
IntPtr hwnd_select = IntPtr.Zero;
IntPtr hwndChild = IntPtr.Zero;
DateTime timer;
TimeSpan diff;
int processid;
string username = "Programmer";
clsImpersonate cls = new clsImpersonate();
try
{
IntPtr token = cls.ImpersonateUser(username, Environment.MachineName, "RoboMan");
using (WindowsImpersonationContext impersonatedUser = WindowsIdentity.Impersonate(token))
{
//Process process = new Process();
//ProcessStartInfo info = new ProcessStartInfo();
//info.FileName = fileName;
//info.Arguments = argument;
//process.StartInfo = info;
//process.Start();
//if (impersonateValidUser("Programmer", "", "Roboman"))
//if (impersonateValidUser(username, "DESKTOP", "Roboman"))
//{
ProcessStartInfo psi = new ProcessStartInfo("OUR PROCESS");
//psi.UserName = username;
//psi.Domain = Environment.MachineName;
//psi.Password = new System.Security.SecureString();
//psi.Password.AppendChar('R');
//psi.Password.AppendChar('o');
//psi.Password.AppendChar('b');
//psi.Password.AppendChar('o');
//psi.Password.AppendChar('m');
//psi.Password.AppendChar('a');
//psi.Password.AppendChar('n');
psi.Arguments = "-batch";
psi.WorkingDirectory = "OUR DIRECTORY";
psi.UseShellExecute = false;
//myProcess.StartInfo.CreateNoWindow = true; //Maybe?
//myProcess.Start();
//The following security adjustments are necessary to give the new
//process sufficient permission to run in the service's window station
//and desktop. This uses classes from the AsproLock library also from
//Asprosys.
//IntPtr hWinSta = GetProcessWindowStation();
//WindowStationSecurity ws = new WindowStationSecurity(hWinSta,
// System.Security.AccessControl.AccessControlSections.Access);
////ws.AddAccessRule(new WindowStationAccessRule(username,
// // WindowStationRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
//ws.AddAccessRule(new WindowStationAccessRule(username,
// WindowStationRights.CreateDesktop, System.Security.AccessControl.AccessControlType.Allow));
//ws.AcceptChanges();
//IntPtr hDesk = GetThreadDesktop(GetCurrentThreadId());
//DesktopSecurity ds = new DesktopSecurity(hDesk,
// System.Security.AccessControl.AccessControlSections.Access);
//ds.AddAccessRule(new DesktopAccessRule(username,
// DesktopRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
//ds.AcceptChanges();
using (Process process = Process.Start(psi))
{
processid = process.Id;
}
the cls.ImpersonateUser The above attempts to run an elevated section of code as another user. But fails. You can see I've attempted to use this version as well. ImpersonateValidUser Example
The AsProSys code would also throw an Access-denied exception right on the ws.AcceptChanges();
WebServers run as Windows Services. And Windows services are by default prohibited from accessing the Desktop as of Windows Vista.
In addition to the general Service Limitations, webservers are also customary run in the most limited userrights possible. Readaccess to it's programm and content directory is about the best they get. They are always on, so they are highly vulnerable to hacking.
As I understand currently you try to start a Dekstop applciation from a Webserver. And that is pretty much a no-go. If that did work, I would first wonder how quickly I can uninstall it. And then how I did not manage to limits right in the first place to prevent this. For every admin that will ever have to run your Webpage: Stop trying to do that!
Instead just have a Helper application that is normally installed on the Windows. Have it start automatically via the TaskSheduler on user login. and have it and the WebServer communicate via Pipes, the Loopback device or similar IPC ways that are acceptable for a WebServer.

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;
}
}
}

how to delay shutdown and run a process in window service

I have to run a process ie a application on windows shutdown, is there any method to delay the windows shutdown and run the application in windows service...
protected override void OnShutdown()
{
// Add your save code here
// Add your save code here
StreamWriter str = new StreamWriter("D:\\Log.txt", true);
str.WriteLine("Service stoped due to on" + DateTime.Now.ToString());
str.Close();
base.OnShutdown();
}
I have used function above which overrides the shutdown and i was able to write a log entry to a text file but i was not able to run an application after that On searching i found that the delay was below only some seconds after user fires shutdown
this.RequestAdditionalTime(250000);
this gives an addition time delay of 25 seconds on shutdown event but i was not able to run the application. Can anyone suggest method or ideas to run application on shutdown.
The ability of applications to block a pending system shutdown was severely restricted in Windows Vista. The details are summarized in two handy articles on MSDN: Shutdown Changes for Windows Vista and Application Shutdown Changes in Windows Vista.
As that page indicates, you shouldn't rely on the ability to block shutdown for any longer than 5 seconds. If you wish to attempt to block a pending shutdown event, your application should use the new ShutdownBlockReasonCreate function, which allows you to register a string that explains to the user the reason why you think the shutdown should be blocked. The user reserves the ability to heed your advice and cancel the shutdown, or throw caution to the wind and cancel anyway.
As soon as your application finishes doing whatever it is that should not be interrupted by a shutdown, you should call the corresponding ShutdownBlockReasonDestroy function, which frees the reason string and indicates that the system can now be shut down.
Also remember that Windows Services now run in an isolated session and are prohibited from interacting with the user. My answer here also provides more details, as well as a pretty diagram.
Basically, this is impossible. Windows is going to fight you tooth and nail over starting up a separate process from your Service, as well as any attempt you make to block a pending shutdown. Ultimately, the user has the power to override anything you try to pull. This sounds like something you should solve using security policies, rather than an application—ask questions about that on Server Fault.
On Windows Vista SP1 and higher, the new SERVICE_CONTROL_PRESHUTDOWN is available. Unfortunately it is not supported by .NET framework yet, but here is workaround using reflection. Just inherit your service class from ServicePreshutdownBase, override OnStop and periodically call RequestAdditionalTime(). Note that CanShutdown should be set to false.
public class ServicePreshutdownBase : ServiceBase
{
public bool Preshutdown { get; private set; }
public ServicePreshutdownBase()
{
Version versionWinVistaSp1 = new Version(6, 0, 6001);
if (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version >= versionWinVistaSp1)
{
var acceptedCommandsField = typeof (ServiceBase).GetField("acceptedCommands", BindingFlags.Instance | BindingFlags.NonPublic);
if (acceptedCommandsField == null)
throw new InvalidOperationException("Private field acceptedCommands not found on ServiceBase");
int acceptedCommands = (int) acceptedCommandsField.GetValue(this);
acceptedCommands |= 0x00000100; //SERVICE_ACCEPT_PRESHUTDOWN;
acceptedCommandsField.SetValue(this, acceptedCommands);
}
}
protected override void OnCustomCommand(int command)
{
// command is SERVICE_CONTROL_PRESHUTDOWN
if (command == 0x0000000F)
{
var baseCallback = typeof(ServiceBase).GetMethod("ServiceCommandCallback", BindingFlags.Instance | BindingFlags.NonPublic);
if (baseCallback == null)
throw new InvalidOperationException("Private method ServiceCommandCallback not found on ServiceBase");
try
{
Preshutdown = true;
//now pretend stop was called 0x00000001
baseCallback.Invoke(this, new object[] {0x00000001});
}
finally
{
Preshutdown = false;
}
}
}
}
Here is example usage:
public partial class Service1 : ServicePreshutdownBase
{
public Service1()
{
InitializeComponent();
this.CanShutdown = false;
}
protected override void OnStop()
{
WriteLog(Preshutdown ? "Service OnPreshutdown" : "Service OnStop");
for (int i = 0; i < 180; i++)
{
Thread.Sleep(1000);
WriteLog("Service stop in progress...");
RequestAdditionalTime(2000);
}
WriteLog(Preshutdown ? "Service preshutdown completed" : "Service stop completed");
}
}
This will work for 3min 20s, if you need more time, then you need to configure service. The best place to do so is during installation. Just use the ServicePreshutdownInstaller instead of ServiceInstaller and set the PreshutdownTimeout to maximum time you will ever need.
public class ServicePreshutdownInstaller : ServiceInstaller
{
private int _preshutdownTimeout = 200000;
/// <summary>
/// Gets or sets the preshutdown timeout for the service.
/// </summary>
///
/// <returns>
/// The preshutdown timeout of the service. The default is 200000ms (200s).
/// </returns>
[DefaultValue(200000)]
[ServiceProcessDescription("ServiceInstallerPreshutdownTimeout")]
public int PreshutdownTimeout
{
get
{
return _preshutdownTimeout;
}
set
{
_preshutdownTimeout = value;
}
}
public override void Install(System.Collections.IDictionary stateSaver)
{
base.Install(stateSaver);
Version versionWinVistaSp1 = new Version(6, 0, 6001);
if (Environment.OSVersion.Platform != PlatformID.Win32NT || Environment.OSVersion.Version < versionWinVistaSp1)
{
//Preshutdown is not supported
return;
}
Context.LogMessage(string.Format("Setting preshutdown timeout {0}ms to service {1}", PreshutdownTimeout, ServiceName));
IntPtr service = IntPtr.Zero;
IntPtr sCManager = IntPtr.Zero;
try
{
// Open the service control manager
sCManager = OpenSCManager(null, null, ServiceControlAccessRights.SC_MANAGER_CONNECT);
if (sCManager == IntPtr.Zero)
throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to open Service Control Manager.");
// Open the service
service = OpenService(sCManager, ServiceName, ServiceAccessRights.SERVICE_CHANGE_CONFIG);
if (service == IntPtr.Zero) throw new Win32Exception();
// Set up the preshutdown timeout structure
SERVICE_PRESHUTDOWN_INFO preshutdownInfo = new SERVICE_PRESHUTDOWN_INFO();
preshutdownInfo.dwPreshutdownTimeout = (uint)_preshutdownTimeout;
// Make the change
int changeResult = ChangeServiceConfig2(
service,
ServiceConfig2InfoLevel.SERVICE_CONFIG_PRESHUTDOWN_INFO,
ref preshutdownInfo);
// Check that the change occurred
if (changeResult == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to change the Service configuration.");
}
Context.LogMessage(string.Format("Preshutdown timeout {0}ms set to service {1}", PreshutdownTimeout, ServiceName));
}
finally
{
// Clean up
if (service != IntPtr.Zero)CloseServiceHandle(service);
if (sCManager != IntPtr.Zero)Marshal.FreeHGlobal(sCManager);
}
}
[StructLayout(LayoutKind.Sequential)]
public struct SERVICE_PRESHUTDOWN_INFO
{
public UInt32 dwPreshutdownTimeout;
}
[Flags]
public enum ServiceControlAccessRights : int
{
SC_MANAGER_CONNECT = 0x0001, // Required to connect to the service control manager.
SC_MANAGER_CREATE_SERVICE = 0x0002, // Required to call the CreateService function to create a service object and add it to the database.
SC_MANAGER_ENUMERATE_SERVICE = 0x0004, // Required to call the EnumServicesStatusEx function to list the services that are in the database.
SC_MANAGER_LOCK = 0x0008, // Required to call the LockServiceDatabase function to acquire a lock on the database.
SC_MANAGER_QUERY_LOCK_STATUS = 0x0010, // Required to call the QueryServiceLockStatus function to retrieve the lock status information for the database
SC_MANAGER_MODIFY_BOOT_CONFIG = 0x0020, // Required to call the NotifyBootConfigStatus function.
SC_MANAGER_ALL_ACCESS = 0xF003F // Includes STANDARD_RIGHTS_REQUIRED, in addition to all access rights in this table.
}
[Flags]
public enum ServiceAccessRights : int
{
SERVICE_QUERY_CONFIG = 0x0001, // Required to call the QueryServiceConfig and QueryServiceConfig2 functions to query the service configuration.
SERVICE_CHANGE_CONFIG = 0x0002, // Required to call the ChangeServiceConfig or ChangeServiceConfig2 function to change the service configuration. Because this grants the caller the right to change the executable file that the system runs, it should be granted only to administrators.
SERVICE_QUERY_STATUS = 0x0004, // Required to call the QueryServiceStatusEx function to ask the service control manager about the status of the service.
SERVICE_ENUMERATE_DEPENDENTS = 0x0008, // Required to call the EnumDependentServices function to enumerate all the services dependent on the service.
SERVICE_START = 0x0010, // Required to call the StartService function to start the service.
SERVICE_STOP = 0x0020, // Required to call the ControlService function to stop the service.
SERVICE_PAUSE_CONTINUE = 0x0040, // Required to call the ControlService function to pause or continue the service.
SERVICE_INTERROGATE = 0x0080, // Required to call the ControlService function to ask the service to report its status immediately.
SERVICE_USER_DEFINED_CONTROL = 0x0100, // Required to call the ControlService function to specify a user-defined control code.
SERVICE_ALL_ACCESS = 0xF01FF // Includes STANDARD_RIGHTS_REQUIRED in addition to all access rights in this table.
}
public enum ServiceConfig2InfoLevel : int
{
SERVICE_CONFIG_DESCRIPTION = 0x00000001, // The lpBuffer parameter is a pointer to a SERVICE_DESCRIPTION structure.
SERVICE_CONFIG_FAILURE_ACTIONS = 0x00000002, // The lpBuffer parameter is a pointer to a SERVICE_FAILURE_ACTIONS structure.
SERVICE_CONFIG_PRESHUTDOWN_INFO = 0x00000007 // The lpBuffer parameter is a pointer to a SERVICE_PRESHUTDOWN_INFO structure.
}
[DllImport("advapi32.dll", EntryPoint = "OpenSCManager")]
public static extern IntPtr OpenSCManager(
string machineName,
string databaseName,
ServiceControlAccessRights desiredAccess);
[DllImport("advapi32.dll", EntryPoint = "CloseServiceHandle")]
public static extern int CloseServiceHandle(IntPtr hSCObject);
[DllImport("advapi32.dll", EntryPoint = "OpenService")]
public static extern IntPtr OpenService(
IntPtr hSCManager,
string serviceName,
ServiceAccessRights desiredAccess);
[DllImport("advapi32.dll", EntryPoint = "ChangeServiceConfig2")]
public static extern int ChangeServiceConfig2(
IntPtr hService,
ServiceConfig2InfoLevel dwInfoLevel,
ref SERVICE_PRESHUTDOWN_INFO lpInfo);
}
I have a similar problem, and there is one trick that might work in your case. You can start application in question before shutdown is initiated with CREATE_SUSPENDED flag (see this). This will ensure that the process will be created, but never run. On shutdown you can ResumeThread that process, and it will go on with execution.
Note, that it might be possible that the process will not be able to initialize and run anyway, since during shutdown some OS functions will fail.
Another implication is: the process which is supposed to run on shutdown will show in task manager. It would be possible to kill that process.
Here is an article on the Shutdown Event Tracker. You can activate it in Windows XP. It prompts the user for a reason for shutdown.
namespace WindowsService1
{
[StructLayout(LayoutKind.Sequential)]
public struct SERVICE_STATUS
{
public int serviceType;
public int currentState;
public int controlsAccepted;
public int win32ExitCode;
public int serviceSpecificExitCode;
public int checkPoint;
public int waitHint;
}
public enum SERVICE_STATE : uint
{
SERVICE_STOPPED = 0x00000001,
SERVICE_START_PENDING = 0x00000002,
SERVICE_STOP_PENDING = 0x00000003,
SERVICE_RUNNING = 0x00000004,
SERVICE_CONTINUE_PENDING = 0x00000005,
SERVICE_PAUSE_PENDING = 0x00000006,
SERVICE_PAUSED = 0x00000007
}
public enum ControlsAccepted
{
ACCEPT_STOP = 1,
ACCEPT_PAUSE_CONTINUE = 2,
ACCEPT_SHUTDOWN = 4,
ACCEPT_PRESHUTDOWN = 0xf,
ACCEPT_POWER_EVENT = 64,
ACCEPT_SESSION_CHANGE = 128
}
[Flags]
public enum SERVICE_CONTROL : uint
{
STOP = 0x00000001,
PAUSE = 0x00000002,
CONTINUE = 0x00000003,
INTERROGATE = 0x00000004,
SHUTDOWN = 0x00000005,
PARAMCHANGE = 0x00000006,
NETBINDADD = 0x00000007,
NETBINDREMOVE = 0x00000008,
NETBINDENABLE = 0x00000009,
NETBINDDISABLE = 0x0000000A,
DEVICEEVENT = 0x0000000B,
HARDWAREPROFILECHANGE = 0x0000000C,
POWEREVENT = 0x0000000D,
SESSIONCHANGE = 0x0000000E
}
public enum INFO_LEVEL : uint
{
SERVICE_CONFIG_DESCRIPTION = 0x00000001,
SERVICE_CONFIG_FAILURE_ACTIONS = 0x00000002,
SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 0x00000003,
SERVICE_CONFIG_FAILURE_ACTIONS_FLAG = 0x00000004,
SERVICE_CONFIG_SERVICE_SID_INFO = 0x00000005,
SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO = 0x00000006,
SERVICE_CONFIG_PRESHUTDOWN_INFO = 0x00000007,
SERVICE_CONFIG_TRIGGER_INFO = 0x00000008,
SERVICE_CONFIG_PREFERRED_NODE = 0x00000009
}
[StructLayout(LayoutKind.Sequential)]
public struct SERVICE_PRESHUTDOWN_INFO
{
public UInt32 dwPreshutdownTimeout;
}
[Flags]
public enum SERVICE_ACCESS : uint
{
STANDARD_RIGHTS_REQUIRED = 0xF0000,
SERVICE_QUERY_CONFIG = 0x00001,
SERVICE_CHANGE_CONFIG = 0x00002,
SERVICE_QUERY_STATUS = 0x00004,
SERVICE_ENUMERATE_DEPENDENTS = 0x00008,
SERVICE_START = 0x00010,
SERVICE_STOP = 0x00020,
SERVICE_PAUSE_CONTINUE = 0x00040,
SERVICE_INTERROGATE = 0x00080,
SERVICE_USER_DEFINED_CONTROL = 0x00100,
SERVICE_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED |
SERVICE_QUERY_CONFIG |
SERVICE_CHANGE_CONFIG |
SERVICE_QUERY_STATUS |
SERVICE_ENUMERATE_DEPENDENTS |
SERVICE_START |
SERVICE_STOP |
SERVICE_PAUSE_CONTINUE |
SERVICE_INTERROGATE |
SERVICE_USER_DEFINED_CONTROL)
}
[Flags]
public enum SCM_ACCESS : uint
{
STANDARD_RIGHTS_REQUIRED = 0xF0000,
SC_MANAGER_CONNECT = 0x00001,
SC_MANAGER_CREATE_SERVICE = 0x00002,
SC_MANAGER_ENUMERATE_SERVICE = 0x00004,
SC_MANAGER_LOCK = 0x00008,
SC_MANAGER_QUERY_LOCK_STATUS = 0x00010,
SC_MANAGER_MODIFY_BOOT_CONFIG = 0x00020,
SC_MANAGER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED |
SC_MANAGER_CONNECT |
SC_MANAGER_CREATE_SERVICE |
SC_MANAGER_ENUMERATE_SERVICE |
SC_MANAGER_LOCK |
SC_MANAGER_QUERY_LOCK_STATUS |
SC_MANAGER_MODIFY_BOOT_CONFIG
}
public partial class Service1 : ServiceBase
{
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
[DllImport("advapi32.dll")]
internal static extern bool SetServiceStatus(IntPtr hServiceStatus, ref SERVICE_STATUS lpServiceStatus);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ChangeServiceConfig2(IntPtr hService, int dwInfoLevel, IntPtr lpInfo);
[DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
const int SERVICE_ACCEPT_PRESHUTDOWN = 0x100;
const int SERVICE_CONTROL_PRESHUTDOWN = 0xf;
public Service1()
{
InitializeComponent();
CanShutdown = true;
tim = new Timer();
tim.Interval = 5000;
tim.Elapsed += tim_Elapsed;
FieldInfo acceptedCommandsFieldInfo = typeof(ServiceBase).GetField("acceptedCommands", BindingFlags.Instance | BindingFlags.NonPublic);
int value = (int)acceptedCommandsFieldInfo.GetValue(this);
acceptedCommandsFieldInfo.SetValue(this, value | SERVICE_ACCEPT_PRESHUTDOWN);
StreamWriter writer = new StreamWriter("D:\\LogConst.txt", true);
try
{
IntPtr hMngr = OpenSCManager("localhost", null, (uint)SCM_ACCESS.SC_MANAGER_ALL_ACCESS);
IntPtr hSvc = OpenService(hMngr, "WindowsService1", (uint)SCM_ACCESS.SC_MANAGER_ALL_ACCESS);
SERVICE_PRESHUTDOWN_INFO spi = new SERVICE_PRESHUTDOWN_INFO();
spi.dwPreshutdownTimeout = 5000;
IntPtr lpInfo = Marshal.AllocHGlobal(Marshal.SizeOf(spi));
if (lpInfo == IntPtr.Zero)
{
writer.WriteLine(String.Format("Unable to allocate memory for service action, error was: 0x{0:X} -- {1}", Marshal.GetLastWin32Error(), DateTime.Now.ToLongTimeString()));
}
Marshal.StructureToPtr(spi, lpInfo, false);
// apply the new timeout value
if (!ChangeServiceConfig2(hSvc, (int)INFO_LEVEL.SERVICE_CONFIG_PRESHUTDOWN_INFO, lpInfo))
writer.WriteLine(DateTime.Now.ToLongTimeString() + " Failed to change service timeout");
else
writer.WriteLine(DateTime.Now.ToLongTimeString() + " change service timeout : " + spi.dwPreshutdownTimeout);
}
catch (Exception ex)
{
writer.WriteLine(DateTime.Now.ToLongTimeString() + " " + ex.Message);
}
writer.Close();
}
void tim_Elapsed(object sender, ElapsedEventArgs e)
{
result = false;
StreamWriter writer = new StreamWriter("D:\\hede.txt", true);
writer.WriteLine(DateTime.Now.ToLongTimeString());
//System.Threading.Thread.Sleep(5000);
writer.Close();
result = true;
tim.Stop();
}
Timer tim;
bool result = false;
protected override void OnStart(string[] args)
{
RequestAdditionalTime(1000);
tim.Start();
}
protected override void OnStop()
{
}
protected override void OnCustomCommand(int command)
{
StreamWriter writer = new StreamWriter("D:\\Log.txt", true);
try
{
if (command == SERVICE_CONTROL_PRESHUTDOWN)
{
int checkpoint = 1;
writer.WriteLine(DateTime.Now.ToLongTimeString());
while (!result)
{
SERVICE_STATUS myServiceStatus = new SERVICE_STATUS();
myServiceStatus.currentState = (int)SERVICE_STATE.SERVICE_STOP_PENDING;
myServiceStatus.serviceType = 16;
myServiceStatus.serviceSpecificExitCode = 0;
myServiceStatus.checkPoint = checkpoint;
SetServiceStatus(this.ServiceHandle, ref myServiceStatus);
checkpoint++;
}
writer.WriteLine(DateTime.Now.ToLongTimeString());
}
}
catch (Exception ex)
{
writer.WriteLine(DateTime.Now.ToLongTimeString() + " " + ex.Message);
}
writer.Close();
base.OnCustomCommand(command);
}
}
}

Automating regression analysis excel with C# [duplicate]

I have an AddIn which I want to invoke through Excel interop from a C# winforms application.
I can't get the addin etc. to load unless I uninstall and resinstall it each time (this is apparantly something to do with Excel not loading addins when you use interop - btw, can't get their example to work in C#). Unfortunately this is slow and annoying to the user so I need to streamline it.
I want to have one instance of Excel but load an already installed addin without forcing this install/reinstall problem.
I've searched and searched but everything I find on google gives the solution to install/reinstall. Is there any other way? The add-in is installed, I just want excel to load it.
This is what I am doing at the moment (taken from google'd advice):
// loop over the add-ins and if you find it uninstall it.
foreach (AddIn addIn in excel.AddIns)
if (addIn.Name.Contains("My Addin"))
addin.Installed = false;
// install the addin
var addin = excel.AddIns.Add("my_addin.xll", false);
addin.Installed = true;
After a while I found the answer hidden in strange places in the MS help: and this blog post.
That isn't all the info you need though. Things to note: you must have at least one workbook open or otherwise Excel barfs. Here's some rudementry code to get started:
var excel = new Application();
var workbook = excel.workbooks.Add(Type.Missing);
excel.RegisterXLL(pathToXll);
excel.ShowExcel();
If you want you can close the temporary workbook (if you've run some macros etc.) and remember to tidy everything up with plenty of calls to Marshal.ReleaseComObject!
It seems that you have to get the correct Excel process to work with. Use this class to open the Excel document:
class ExcelInteropService
{
private const string EXCEL_CLASS_NAME = "EXCEL7";
private const uint DW_OBJECTID = 0xFFFFFFF0;
private static Guid rrid = new Guid("{00020400-0000-0000-C000-000000000046}");
public delegate bool EnumChildCallback(int hwnd, ref int lParam);
[DllImport("Oleacc.dll")]
public static extern int AccessibleObjectFromWindow(int hwnd, uint dwObjectID, byte[] riid, ref Window ptr);
[DllImport("User32.dll")]
public static extern bool EnumChildWindows(int hWndParent, EnumChildCallback lpEnumFunc, ref int lParam);
[DllImport("User32.dll")]
public static extern int GetClassName(int hWnd, StringBuilder lpClassName, int nMaxCount);
public static Application GetExcelInterop(int? processId = null)
{
var p = processId.HasValue ? Process.GetProcessById(processId.Value) : Process.Start("excel.exe");
try
{
Thread.Sleep(5000);
return new ExcelInteropService().SearchExcelInterop(p);
}
catch (Exception)
{
Debug.Assert(p != null, "p != null");
return GetExcelInterop(p.Id);
}
}
private bool EnumChildFunc(int hwndChild, ref int lParam)
{
var buf = new StringBuilder(128);
GetClassName(hwndChild, buf, 128);
if (buf.ToString() == EXCEL_CLASS_NAME) { lParam = hwndChild; return false; }
return true;
}
private Application SearchExcelInterop(Process p)
{
Window ptr = null;
int hwnd = 0;
int hWndParent = (int)p.MainWindowHandle;
if (hWndParent == 0) throw new Exception();
EnumChildWindows(hWndParent, EnumChildFunc, ref hwnd);
if (hwnd == 0) throw new Exception();
int hr = AccessibleObjectFromWindow(hwnd, DW_OBJECTID, rrid.ToByteArray(), ref ptr);
if (hr < 0) throw new Exception();
return ptr.Application;
}
}
Use the class in your application like this:
static void Main(string[] args)
{
Microsoft.Office.Interop.Excel.Application oExcel = ExcelInteropService.GetExcelInterop();
foreach (AddIn addIn in oExcel.AddIns)
{
addIn.Installed = true;
}
}
I had a similar problem and none of the above solutions seemed to work for me. So, I finally came up with the idea of simply opening the ".xla" file as a workbook :
//I first get the path to file, in my case it was in the same dir as the .exe
var XLA_NAME = Path.Combine(Directory.GetCurrentDirectory(), ".xla");
Excel.Application xlRep = new Excel.Application();
xlRep.visible = true;
xlRep.Workbooks.Open("XLA_NAME");

Categories