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);
}
}
}
Related
I have created a program that can also be ran as a service and it will allow me to debug it as well using the following in the Program.cs startup file.
using System;
using System.Linq;
using System.Windows.Forms;
using System.ServiceProcess;
using System.Reflection;
using System.Threading;
using crs.Includes;
using crs.Service;
using System.IO;
namespace crs
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
//Convert all arguments to lower
args = Array.ConvertAll(args, e => e.ToLower());
//Create the container object for the settings to be stored
Settings.Bag = new SettingsBag();
//Check if we want to run this as a service
bool runAsService = args.Contains("-service");
//Check if debugging
bool debug = Environment.UserInteractive;
//Catch all unhandled exceptions as well
if (!debug || debug)
{
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
}
if (runAsService)
{
//Create service array
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new CRSService()
};
//Run services in interactive mode if needed
if (debug)
RunInteractive(ServicesToRun);
else
ServiceBase.Run(ServicesToRun);
}
else
{
//Start the main gui
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainGUI());
}
}
#region Functions
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Exception ex = e.ExceptionObject as Exception;
string stackTrace = ex.Message + "/n";
while (ex.InnerException != null)
{
ex = ex.InnerException;
stackTrace += ex.Message + "/n";
}
stackTrace = stackTrace.Substring(0, stackTrace.Length - 2);
string msg = "UNHANDLED EXCEPTION!/n/n" + stackTrace;
//Write all log messages to a debug log
try
{
string currentDate = DateTime.Now.ToString("yyyy-MM-dd");
string debugFilePath = AppDomain.CurrentDomain.BaseDirectory + #"debugLogs\";
string debugFilename = Application.ProductName + "-debug-" + currentDate + ".log";
if (!Directory.Exists(debugFilePath))
{
//Create the debug log files directory
Directory.CreateDirectory(debugFilePath);
}
if (!File.Exists(debugFilePath + debugFilename))
{
//Create the new file
using (StreamWriter w = File.CreateText(debugFilePath + debugFilename))
{
w.WriteLine("Debug log file for " + Application.ProductName + ".");
w.WriteLine("Created on " + currentDate + ".");
w.WriteLine("");
}
}
//Write the log message to the file
using (StreamWriter w = File.AppendText(debugFilePath + debugFilename))
{
w.WriteLine(DateTime.Now.ToString() + " :: " + msg);
}
}
catch
{ }
}
private static void RunInteractive(ServiceBase[] servicesToRun)
{
Console.WriteLine("Services running in interactive mode.");
Console.WriteLine();
MethodInfo onStartMethod = typeof(ServiceBase).GetMethod("OnStart", BindingFlags.Instance | BindingFlags.NonPublic);
foreach (ServiceBase service in servicesToRun)
{
Console.Write("Starting {0}...", service.ServiceName);
onStartMethod.Invoke(service, new object[] { new string[] { } });
Console.Write("Started");
}
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("Press any key to stop the services and end the process...");
Console.ReadKey();
Console.WriteLine();
MethodInfo onStopMethod = typeof(ServiceBase).GetMethod("OnStop", BindingFlags.Instance | BindingFlags.NonPublic);
foreach (ServiceBase service in servicesToRun)
{
Console.Write("Stopping {0}...", service.ServiceName);
onStopMethod.Invoke(service, null);
Console.WriteLine("Stopped");
}
//Keep the console alive for a second to allow the user to see the message.
Console.WriteLine("All services stopped.");
Thread.Sleep(1000);
}
#endregion
}
}
Everything works as expected except for the line Console.ReadKey(); under the RunInteractive() method. If I was to try and run this service manually in a console window I would have no issues what so ever, it runs great and waits for me to hit enter to start the service stopping process. However, when running it in the IDE it's spitting everything out to the DEBUG window and there is nothing for it to grab a ReadKey on.
How can I go about getting around this when debugging in the IDE? Is it possible to somehow force it to run in a command window when debugging in the IDE?
After some digging around I came up with a new class to suit my needs. Thanks for the post by Pavlo I was able to actually get text to read and write to the new console window I needed to create when one was not present.
My altered RunInteractive function from my original question:
private static void RunInteractive(ServiceBase[] servicesToRun)
{
//Account for running this application without a console window (debugging in IDE)
if (!ConsoleWindow.Exists() && !ConsoleWindow.Create())
return;
Console.WriteLine("Services running in interactive mode.");
Console.WriteLine();
MethodInfo onStartMethod = typeof(ServiceBase).GetMethod("OnStart", BindingFlags.Instance | BindingFlags.NonPublic);
foreach (ServiceBase service in servicesToRun)
{
Console.Write("Starting {0}...", service.ServiceName);
onStartMethod.Invoke(service, new object[] { new string[] { } });
Console.Write("Started");
}
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("Press any key to stop the services and end the process...");
Console.ReadKey();
Console.WriteLine();
MethodInfo onStopMethod = typeof(ServiceBase).GetMethod("OnStop", BindingFlags.Instance | BindingFlags.NonPublic);
foreach (ServiceBase service in servicesToRun)
{
Console.Write("Stopping {0}...", service.ServiceName);
onStopMethod.Invoke(service, null);
Console.WriteLine("Stopped");
}
//Keep the console alive for a second to allow the user to see the message.
Console.WriteLine("All services stopped.");
Thread.Sleep(1000);
}
Note: The only thing that was added here was this little bit at the top of the function.
//Account for running this application without a console window (debugging in IDE)
if (!ConsoleWindow.Exists() && !ConsoleWindow.Create())
return;
My new ConsoleWindow class:
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace crs.Includes
{
public class ConsoleWindow
{
#region Constants
private const UInt32 GENERIC_WRITE = 0x40000000;
private const UInt32 GENERIC_READ = 0x80000000;
private const UInt32 FILE_SHARE_READ = 0x00000001;
private const UInt32 FILE_SHARE_WRITE = 0x00000002;
private const UInt32 OPEN_EXISTING = 0x00000003;
private const UInt32 FILE_ATTRIBUTE_NORMAL = 0x80;
#endregion
#region WinAPI external functions
[DllImport("kernel32.dll")]
private static extern IntPtr GetConsoleWindow();
[DllImport(
"kernel32.dll",
SetLastError = true
)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool FreeConsole();
[DllImport(
"kernel32.dll",
SetLastError = true
)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool AllocConsole();
[DllImport(
"kernel32.dll",
EntryPoint = "CreateFileW",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall
)]
private static extern IntPtr CreateFileW(
string lpFileName,
UInt32 dwDesiredAccess,
UInt32 dwShareMode,
IntPtr lpSecurityAttributes,
UInt32 dwCreationDisposition,
UInt32 dwFlagsAndAttributes,
IntPtr hTemplateFil
);
#endregion
#region Public class methods
public static bool Exists()
{
if (GetConsoleWindow() == IntPtr.Zero)
return false;
else
return true;
}
public static bool Create()
{
try
{
if (!AllocConsole())
throw new Exception("Error! Could not get a lock on a console window and could not create one.");
InitializeOutStream();
InitializeInStream();
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return false;
}
#endregion
#region Functions
private static void InitializeOutStream()
{
FileStream fs = CreateFileStream("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, FileAccess.Write);
if (fs != null)
{
StreamWriter writer = new StreamWriter(fs) { AutoFlush = true };
Console.SetOut(writer);
Console.SetError(writer);
}
}
private static void InitializeInStream()
{
FileStream fs = CreateFileStream("CONIN$", GENERIC_READ, FILE_SHARE_READ, FileAccess.Read);
if (fs != null)
Console.SetIn(new StreamReader(fs));
}
private static FileStream CreateFileStream(string name, uint win32DesiredAccess, uint win32ShareMode, FileAccess dotNetFileAccess)
{
SafeFileHandle file = new SafeFileHandle(CreateFileW(name, win32DesiredAccess, win32ShareMode, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero), true);
if (!file.IsInvalid)
{
FileStream fs = new FileStream(file, dotNetFileAccess);
return fs;
}
return null;
}
#endregion
}
}
Instead of console.write, you can create a log file in which you can log the status of the program. I recommend using the log4net nuget package.
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;
}
}
}
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.
I'm writing an add-in for a fujitsu scanner in C#. Documents are scanned to a local directory, and I wanted to copy them to a pc on the network. I keep getting System.IO.IOException: Logon failure: unknown user name or bad password though. I tried copying them to a public directory (at least I think its public), and I still get the same result.
Any ideas for stuff I could try? I don't think my code here is the issue, but here it is anyway.
private bool moveTheFile(String source, String destination)
{
System.DirectoryServices.DirectoryEntry dntry = null;
try
{
//System.IO.File.Move(source, destination);
System.IO.File.Copy(source, destination);
System.IO.File.Delete(source);
if (System.IO.File.Exists(destination))
{
return true;
}
else
{
return false;
}
}
catch (Exception err)
{
_host.WriteSystemLog(LogType.Information, "E1000099", "File.Move Error " + err.ToString());
return false;
}
}
Your problem is due to the fact that the machine is using a LOCAL SERVICE account or something that does not have access even to the shared public folder. You need to impersonate a different account I believe. I found the following code on a different site to do this:
public WindowsImpersonationContext
ImpersonateUser(string sUsername, string sDomain, string sPassword)
{
// initialize tokens
IntPtr pExistingTokenHandle = new IntPtr(0);
IntPtr pDuplicateTokenHandle = new IntPtr(0);
pExistingTokenHandle = IntPtr.Zero;
pDuplicateTokenHandle = IntPtr.Zero;
// if domain name was blank, assume local machine
if (sDomain == "")
sDomain = System.Environment.MachineName;
try
{
string sResult = null;
const int LOGON32_PROVIDER_DEFAULT = 0;
// create token
const int LOGON32_LOGON_INTERACTIVE = 2;
//const int SecurityImpersonation = 2;
// get handle to token
bool bImpersonated = LogonUser(sUsername, sDomain, sPassword,
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
ref pExistingTokenHandle);
// did impersonation fail?
if (false == bImpersonated)
{
int nErrorCode = Marshal.GetLastWin32Error();
sResult = "LogonUser() failed with error code: " +
nErrorCode + "\r\n";
// show the reason why LogonUser failed
MessageBox.Show(this, sResult, "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
// Get identity before impersonation
sResult += "Before impersonation: " +
WindowsIdentity.GetCurrent().Name + "\r\n";
bool bRetVal = DuplicateToken(pExistingTokenHandle,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
ref pDuplicateTokenHandle);
// did DuplicateToken fail?
if (false == bRetVal)
{
int nErrorCode = Marshal.GetLastWin32Error();
// close existing handle
CloseHandle(pExistingTokenHandle);
sResult += "DuplicateToken() failed with error code: "
+ nErrorCode + "\r\n";
// show the reason why DuplicateToken failed
MessageBox.Show(this, sResult, "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
else
{
// create new identity using new primary token
WindowsIdentity newId = new WindowsIdentity
(pDuplicateTokenHandle);
WindowsImpersonationContext impersonatedUser =
newId.Impersonate();
// check the identity after impersonation
sResult += "After impersonation: " +
WindowsIdentity.GetCurrent().Name + "\r\n";
MessageBox.Show(this, sResult, "Success",
MessageBoxButtons.OK, MessageBoxIcon.Information);
return impersonatedUser;
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
// close handle(s)
if (pExistingTokenHandle != IntPtr.Zero)
CloseHandle(pExistingTokenHandle);
if (pDuplicateTokenHandle != IntPtr.Zero)
CloseHandle(pDuplicateTokenHandle);
}
}
Here are the supporting methods:
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool LogonUser(string pszUsername, string pszDomain, string pszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
// closes open handes returned by LogonUser
[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
// creates duplicate token handle
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public extern static bool DuplicateToken(IntPtr ExistingTokenHandle,
int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
// group type enum
public enum SECURITY_IMPERSONATION_LEVEL : int
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3
}
i would like create new window station & windows desktop and attach my process to it. how can i do that
i need to know
Creating Window station and attach my desktop
Creating & switching between Desktop effectively
how do i attaching process to winlogon desktop(if it is possible )
Destroy created desktop and return back to windows desktop
There is only one interactive window station per 'session' but you can have multiple sessions.
http://blogs.technet.com/markrussinovich/archive/2010/02/24/3315174.aspx
I'm not aware of an API to directly create a login session but if you are using a Windows Server version you could use Remote Desktop to create a local session, autorun your program there, then logout again once the program has ended (the program you are running in the Remote Desktop Session could logout when finished).
The code below will use the MSTSC ActiveX control to programmatically create an RDP session. You will need to manually generate the ActiveX stubs and add them to your project.
From the Visual Studio Command Prompt type the following: aximp.exe %windir%\system32\mstscax.dll
Copy the generated files (MSTSCLib.dll and AxMSTSCLib.dll) to the project directory.
Add both files to the project references.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using AxMSTSCLib;
using MSTSCLib;
using System.Runtime.InteropServices;
namespace AutoLogin
{
public partial class Form1 : Form
{
private AxMSTSCLib.AxMsRdpClient5 rdpClient;
public Form1()
{
InitializeComponent();
rdpClient = new AxMSTSCLib.AxMsRdpClient5();
((ISupportInitialize)rdpClient).BeginInit();
rdpClient.Enabled = true;
rdpClient.Location = new System.Drawing.Point(0, 0);
rdpClient.Name = "MsRdpClient";
rdpClient.Size = ClientSize;
rdpClient.TabIndex = 1;
rdpClient.Anchor = (AnchorStyles)
(AnchorStyles.Top | AnchorStyles.Bottom |
AnchorStyles.Left | AnchorStyles.Right);
Controls.Add(rdpClient);
((ISupportInitialize)rdpClient).EndInit();
}
void axRemoteDesktop_OnDisconnected
(object sender, IMsTscAxEvents_OnDisconnectedEvent e)
{
Application.Idle += ExitTimerEvent;
}
public void ExitTimerEvent(object source, EventArgs e)
{
Application.Idle -= ExitTimerEvent;
// Attempt to close down the session we just connected to (there
// appears to be no way to get the session id, so we just close all
// disconnected sessions.
if (rdpClient.Connected == 1) {
rdpClient.Disconnect();
}
LogoffDisconnectedSessions();
Close();
}
private Timer logoffTimer;
private void Form1_Load(object sender, EventArgs e)
{
// Close down any existing disconnected sessions, the number of
// available sessions is limited.
LogoffDisconnectedSessions();
String username = "username";
String password = "password";
rdpClient.Server = "localhost";
rdpClient.UserName = username;
rdpClient.AdvancedSettings2.ClearTextPassword = password;
rdpClient.Domain = "";
rdpClient.FullScreen = false;
rdpClient.AdvancedSettings2.RedirectDrives = false;
rdpClient.AdvancedSettings2.RedirectPrinters = false;
rdpClient.AdvancedSettings2.RedirectPorts = false;
rdpClient.AdvancedSettings2.RedirectSmartCards = false;
rdpClient.AdvancedSettings6.RedirectClipboard = false;
rdpClient.AdvancedSettings6.MinutesToIdleTimeout = 1;
rdpClient.OnDisconnected += new
AxMSTSCLib.IMsTscAxEvents_OnDisconnectedEventHandler
(axRemoteDesktop_OnDisconnected);
rdpClient.Connect();
logoffTimer = new Timer();
logoffTimer.Tick += new EventHandler(LogoutTimerEvent);
logoffTimer.Interval = 150000;
logoffTimer.Start();
}
private void Form1_Close(object sender, FormClosedEventArgs e)
{
Application.Idle -= ExitTimerEvent;
if (rdpClient.Connected == 1) {
rdpClient.Disconnect();
}
}
public void LogoutTimerEvent(object source, EventArgs e)
{
logoffTimer.Stop();
rdpClient.Disconnect();
}
enum WTS_CONNECTSTATE_CLASS
{
WTSActive,
WTSConnected,
WTSConnectQuery,
WTSShadow,
WTSDisconnected,
WTSIdle,
WTSListen,
WTSReset,
WTSDown,
WTSInit
};
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
struct WTS_SESSION_INFO
{
public int SessionId;
public string pWinStationName;
public WTS_CONNECTSTATE_CLASS State;
}
[DllImport("wtsapi32.dll")]
private static extern bool WTSLogoffSession(IntPtr hServer, int SessionId, bool bWait);
private static IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;
[DllImport("wtsapi32.dll", CharSet = CharSet.Auto)]
private static extern bool WTSEnumerateSessions(
IntPtr hServer,
[MarshalAs(UnmanagedType.U4)]
int Reserved,
[MarshalAs(UnmanagedType.U4)]
int Version,
ref IntPtr ppSessionInfo,
[MarshalAs(UnmanagedType.U4)]
ref int pCount);
[DllImport("wtsapi32.dll")]
private static extern void WTSFreeMemory(IntPtr pMemory);
private void LogoffDisconnectedSessions()
{
IntPtr buffer = IntPtr.Zero;
int count = 0;
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1,
ref buffer, ref count)) {
WTS_SESSION_INFO sessionInfo = new WTS_SESSION_INFO();
for (int index = 0; index < count; index++) {
sessionInfo = (WTS_SESSION_INFO)Marshal.PtrToStructure(
new IntPtr(buffer.ToInt32() +
(Marshal.SizeOf(sessionInfo) * index)),
typeof(WTS_SESSION_INFO));
WTS_CONNECTSTATE_CLASS state = sessionInfo.State;
if (state == WTS_CONNECTSTATE_CLASS.WTSDisconnected)
{
WTSLogoffSession(WTS_CURRENT_SERVER_HANDLE,
sessionInfo.SessionId, true);
}
}
}
WTSFreeMemory(buffer);
}
}
}
Although Windows supports multiple "window stations", the documentation states that:
The interactive window station, Winsta0, is the only window station that can display a user interface or receive user input. It is assigned to the logon session of the interactive user, and contains the keyboard, mouse, and display device. All other window stations are noninteractive, which means they cannot display a user interface or receive user input.
This indicates that the ability to switch between window stations in the way you are proposing is not possible.