So I have checked many many sites, researched for days now. And I have not found or come up with one of my own solution for this problem.
I know, apparently since Windows Vista, a Windows Service since its created in Session 0 its not able to interact with whats considered GUI executables like console apps and other softwares that are part of other Sessions that are not Session 0.
According to Microsoft, a Service that does this would be a potential 'Virus'. Which I understand the reasoning for their thinking. But this is the only solution for our problems.
//This is how I am calling the process.
public void startVM(string vmname) {
string cmdline = startvm --type headless VM2000";
ProcessStartInfo startInfo = new ProcessStartInfo("cmd.exe");
startInfo.WindowStyle = ProcessWindowStyle.Minimized;
startInfo.Arguments = string.Format(#"c:\vms\vboxmanage startvm {0}",vmname);
Process.Start(startInfo);
}
So this is what happens:
I create a Windows Service, this service on startup will start a Process. In this case "cmd.exe". I have checked many times and I am certain that the process is actually created. But the arguments, the actual commands, that I want that cmd.exe to execute...they are being ignored. They just never happen. I tested the code elsewhere, as a library, as a windows form application it is working like clockwork. But yet, as a Service it won't work.
I have tried solutions like enabling to interact with Desktop. Even from Registry Key. I have tried even calling different executables, and it happens the same thing: it creates the process, but it doesn't execute the commands or arguments.
I have read many have had this problem... however no solution have been found by all these sites that I have seen this problem for. Even users from StackOverflow.
//Located in the service class inheriting from ServiceBase
protected override void OnStart(string[] args)
{
//System.Diagnostics.Debugger.Launch();
IVBoxCom vBox = new VBoxCom();
//This method calls the method you see above.
vBox.StartVM("WIN2K");
}
This is the Service Installer Class:
ServiceInstaller installer = new ServiceInstaller();
installer.ServiceName = "Steven-VBoxService"; //This has to be the exact Name of the Service that has ServiceBase Class
installer.DisplayName = "Steven-VBoxService";
installer.StartType = ServiceStartMode.Manual;
base.Installers.Add(installer);
//Creates an Executable that convokes the Service previously installed.
//Note: In theory, I can create 10 Services, and run them in a single Service Process
ServiceProcessInstaller installer2 = new ServiceProcessInstaller();
installer2.Account = ServiceAccount.LocalSystem; //Windows service.
//installer2.Password = "sh9852"; //Why would I used these options?
//installer2.Username = #"FITZMALL\hernandezs";
installer2.Password = null;
installer2.Username = null;
base.Installers.Add(installer2);
I have noticed that when I want to start the service, it gets stuck at "Starting", then it just stops.
But the Process the cmd.exe or the VBoxManage.exe get created but never actually do anything at all.
So the only alternative to this is to trick the OS. And make an instance of the Process from the Kernel but changing who was the creator. Let me elaborate.
Since Windows Vista and greater...Microsoft thought that having the Windows Service as a Service that can interactive with User GUI was a bad idea(and I agree at some point) because it may be potentially a virus that will run everytime at startup. So they created something called a Session 0. All your services are in this Session so that they are not able to interact with your user(or Session 1 +) GUI. Meaning the Windows Service has no access to cmd.exe, VBoxManage.exe, any other app that has GUI interaction.
So... the solution to the problem is tricking the OS, creating the Process from the Kernel with Platform Invokes(Win 32 API) which is not that common for a day to day developer in C#.
When creating the Process from the KernelDLL you have access to change who the User or the Creator is. In this case instead of having the Session 0 creating the Process, I changed it to the current Session ID, or current User. This made it possible for my Windows Service Work like I wanted.
For this idea to work you have to read a lot about KernelDll, advapi32.dll, mostly their methods and enum declarations since its not something you can just reference into your project. Those two need to be P/Invoke in order to use them.
The Following Class that I created makes it possible for you to create a process as the current user and not as Session 0. Hence solving my original problem.
//Just use the Class Method no need to instantiate it:
ApplicationLoader.CreateProcessAsUser(string filename, string args)
[SuppressUnmanagedCodeSecurity]
class ApplicationLoader
{
/// <summary>
/// No Need to create the class.
/// </summary>
private ApplicationLoader() { }
enum TOKEN_INFORMATION_CLASS
{
TokenUser = 1,
TokenGroups,
TokenPrivileges,
TokenOwner,
TokenPrimaryGroup,
TokenDefaultDacl,
TokenSource,
TokenType,
TokenImpersonationLevel,
TokenStatistics,
TokenRestrictedSids,
TokenSessionId,
TokenGroupsAndPrivileges,
TokenSessionReference,
TokenSandBoxInert,
TokenAuditPolicy,
TokenOrigin,
TokenElevationType,
TokenLinkedToken,
TokenElevation,
TokenHasRestrictions,
TokenAccessInformation,
TokenVirtualizationAllowed,
TokenVirtualizationEnabled,
TokenIntegrityLevel,
TokenUIAccess,
TokenMandatoryPolicy,
TokenLogonSid,
MaxTokenInfoClass
}
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 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 Int32 dwProcessID;
public Int32 dwThreadID;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public Int32 Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
public enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
public const int GENERIC_ALL_ACCESS = 0x10000000;
public const int CREATE_NO_WINDOW = 0x08000000;
[DllImport("advapi32.dll", EntryPoint = "ImpersonateLoggedOnUser", SetLastError = true,
CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr ImpersonateLoggedOnUser(IntPtr hToken);
[
DllImport("kernel32.dll",
EntryPoint = "CloseHandle", SetLastError = true,
CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)
]
public static extern bool CloseHandle(IntPtr handle);
[
DllImport("advapi32.dll",
EntryPoint = "CreateProcessAsUser", SetLastError = true,
CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)
]
public static extern bool
CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandle, Int32 dwCreationFlags, IntPtr lpEnvrionment,
string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo,
ref PROCESS_INFORMATION lpProcessInformation);
[
DllImport("advapi32.dll",
EntryPoint = "DuplicateTokenEx")
]
public static extern bool
DuplicateTokenEx(IntPtr hExistingToken, Int32 dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
Int32 ImpersonationLevel, Int32 dwTokenType,
ref IntPtr phNewToken);
[DllImport("Kernel32.dll", SetLastError = true)]
//[return: MarshalAs(UnmanagedType.U4)]
public static extern IntPtr WTSGetActiveConsoleSessionId();
[DllImport("advapi32.dll")]
public static extern IntPtr SetTokenInformation(IntPtr TokenHandle, IntPtr TokenInformationClass, IntPtr TokenInformation, IntPtr TokenInformationLength);
[DllImport("wtsapi32.dll", SetLastError = true)]
public static extern bool WTSQueryUserToken(uint sessionId, out IntPtr Token);
private static int getCurrentUserSessionID()
{
uint dwSessionId = (uint)WTSGetActiveConsoleSessionId();
//Gets the ID of the User logged in with WinLogOn
Process[] processes = Process.GetProcessesByName("winlogon");
foreach (Process p in processes)
{
if ((uint)p.SessionId == dwSessionId)
{
//this is the process controlled by the same sessionID
return p.SessionId;
}
}
return -1;
}
/// <summary>
/// Actually calls and creates the application.
/// </summary>
/// <param name="filename"></param>
/// <param name="args"></param>
/// <returns></returns>
public static Process CreateProcessAsUser(string filename, string args)
{
//var replaces IntPtr
var hToken = WindowsIdentity.GetCurrent().Token; //gets Security Token of Current User.
var hDupedToken = IntPtr.Zero;
var pi = new PROCESS_INFORMATION();
var sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
try
{
if (!DuplicateTokenEx(
hToken,
GENERIC_ALL_ACCESS,
ref sa,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)TOKEN_TYPE.TokenPrimary,
ref hDupedToken
))
throw new Win32Exception(Marshal.GetLastWin32Error());
var si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "";
var path = Path.GetFullPath(filename);
var dir = Path.GetDirectoryName(path);
//Testing
uint curSessionid = (uint)ApplicationLoader.getCurrentUserSessionID();
if (!WTSQueryUserToken(curSessionid,out hDupedToken))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Revert to self to create the entire process; not doing this might
// require that the currently impersonated user has "Replace a process
// level token" rights - we only want our service account to need
// that right.
using (var ctx = WindowsIdentity.Impersonate(IntPtr.Zero))
{
if (!CreateProcessAsUser(
hDupedToken,
path,
string.Format("\"{0}\" {1}", filename.Replace("\"", "\"\""), args),
ref sa, ref sa,
false, CREATE_NO_WINDOW, IntPtr.Zero,
dir, ref si, ref pi
))
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return Process.GetProcessById(pi.dwProcessID);
}
finally
{
if (pi.hProcess != IntPtr.Zero)
CloseHandle(pi.hProcess);
if (pi.hThread != IntPtr.Zero)
CloseHandle(pi.hThread);
if (hDupedToken != IntPtr.Zero)
CloseHandle(hDupedToken);
}
}
}
Modify the class at your will. Just be careful not to touch a lot of the initial enum declarations or the external methods if you have no clue how those work yet.
The problem with your original code (as shown in the question) is very simple: you left out the /c argument to cmd.exe to tell it to run your command.
In other words, you were trying to do this:
cmd c:\vms\vboxmanage startvm {0}
whereas what you needed to do was this:
cmd /c c:\vms\vboxmanage startvm {0}
or this:
c:\vms\vboxmanage startvm {0}
Now, that said, there are some applications that don't like running in a service context. Note that this isn't because they display a GUI but for any one of several other reasons. (For example, some applications only work if Explorer is running on the same desktop.)
It's possible that vboxmanage is such an application, but it's more likely that your original code would have worked perfectly if you hadn't forgotten the /c.
Related
I have a C#/WPF application which I want to give different behaviour depending on whether it has been started from a pinned link on the windows taskbar.
Is there a way to detect whether my application has been pinned to the taskbar?
Is there a way to detect whether my application has been started from a pinned item on the taskbar?
You can detect if application is pinned to taskbar for current user by inspecting folder %appdata%\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar where shortcuts to all pinned applications are stored. For example (need to add COM reference to Windows Script Host Object Model):
private static bool IsCurrentApplicationPinned() {
// path to current executable
var currentPath = Assembly.GetEntryAssembly().Location;
// folder with shortcuts
string location = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), #"Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar");
if (!Directory.Exists(location))
return false;
foreach (var file in Directory.GetFiles(location, "*.lnk")) {
IWshShell shell = new WshShell();
var lnk = shell.CreateShortcut(file) as IWshShortcut;
if (lnk != null) {
// if there is shortcut pointing to current executable - it's pinned
if (String.Equals(lnk.TargetPath, currentPath, StringComparison.InvariantCultureIgnoreCase)) {
return true;
}
}
}
return false;
}
There is also a way to detect if application was started from a pinned item or not. For that you will need GetStartupInfo win api function. Among other info, it will provide you a full path to a shortcut (or just file) current process was started with. Example:
[DllImport("kernel32.dll", SetLastError = true, EntryPoint = "GetStartupInfoA")]
public static extern void GetStartupInfo(out STARTUPINFO lpStartupInfo);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct STARTUPINFO
{
public uint 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 ushort wShowWindow;
public ushort cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
Usage:
STARTUPINFO startInfo;
GetStartupInfo(out startInfo);
var startupPath = startInfo.lpTitle;
Now if you have started application from taskbar, startupPath will point to a shortcut from %appdata%\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar, so with all this info it's easy to check if application was started from taskbar or not.
On Windows 8.1 I have a service that starts PowerShell scripts.
The service runs as “nt authority\system” in Session 0 Isolation.
Any process that I spawn from PowerShell runs as “nt authority\system” in Session 0 Isolation.
I need to run a script that is under a user account out of session 0 and not the system account.
I have tried this
Start-Process "$PsHome\PowerShell.exe" -Credential $pp -ArgumentList $script -wait
and PsExec specifying which session I want with "-I 1" argument.
& PsExec.exe "Install.bat" -i 1 -accepteula -u "domain\user" -p "awesomePassword" -w "startdir" -h
I have tried setting "Allow service to interact with desktop".
I keep getting Access is denied errors when I try and start the process either from PowerShell or from the c# service.
Here is an example exception when I try to escape using c# on the service.
System.ComponentModel.Win32Exception (0x80004005): Access is denied
at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo)
How do I escape from session 0?
I can re-write the c# code to start a process under a different user.
or I can re-write the called PowerShell script to start another process as a user.
No matter what I try, I can't seem to break out of session 0.
Using the example I found at code project I got a partial solution. The example in the link above will launch a process as the user who is running the "winlogon" process. In order to launch a process as the user who is logged in I just changed the process to look for "explorer" instead.
Here is a snippet of the original code
// obtain the process id of the winlogon process that is
// running within the currently active session
Process[] processes = Process.GetProcessesByName("winlogon");
I just change the process to look for explorer.
Process[] processes = Process.GetProcessesByName("explorer");
Now the process launches as domain/me in Session 3 as a user not admin.
There has to be issues with this approach, such as Remote Desktop, but for what I want this will ultimately do.
Here is the final code for completeness in case the original link evaporates.
Here is how to launch it
// the name of the application to launch
String applicationName = "cmd.exe";
// launch the application
ApplicationLoader.PROCESS_INFORMATION procInfo;
ApplicationLoader.StartProcessAndBypassUAC(applicationName, out procInfo);
Here is the code
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
namespace SuperAwesomeNameSpaceOfJustice
{
/// <summary>
/// Class that allows running applications with full admin rights. In
/// addition the application launched will bypass the Vista UAC prompt.
/// </summary>
public class ApplicationLoader
{
#region Structures
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[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;
}
#endregion
#region Enumerations
enum TOKEN_TYPE : int
{
TokenPrimary = 1,
TokenImpersonation = 2
}
enum SECURITY_IMPERSONATION_LEVEL : int
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3,
}
#endregion
#region Constants
public const int TOKEN_DUPLICATE = 0x0002;
public const uint MAXIMUM_ALLOWED = 0x2000000;
public const int CREATE_NEW_CONSOLE = 0x00000010;
public const int IDLE_PRIORITY_CLASS = 0x40;
public const int NORMAL_PRIORITY_CLASS = 0x20;
public const int HIGH_PRIORITY_CLASS = 0x80;
public const int REALTIME_PRIORITY_CLASS = 0x100;
#endregion
#region Win32 API Imports
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hSnapshot);
[DllImport("kernel32.dll")]
static extern uint WTSGetActiveConsoleSessionId();
[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("kernel32.dll")]
static extern bool ProcessIdToSessionId(uint dwProcessId, ref uint pSessionId);
// Fixed invalid declaration from Code Projects code
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
public extern static bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes, int ImpersonationLevel,
int TokenType, ref IntPtr DuplicateTokenHandle);
[DllImport("kernel32.dll")]
static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("advapi32", SetLastError = true), SuppressUnmanagedCodeSecurity]
static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, ref IntPtr TokenHandle);
#endregion
/// <summary>
/// Launches the given application with full admin rights, and in addition bypasses the Vista UAC prompt
/// </summary>
/// <param name="applicationName">The name of the application to launch</param>
/// <param name="procInfo">Process information regarding the launched application that gets returned to the caller</param>
/// <returns></returns>
public static bool StartProcessAndBypassUAC(String applicationName, string startingDir, out PROCESS_INFORMATION procInfo)
{
uint winlogonPid = 0;
IntPtr hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;
procInfo = new PROCESS_INFORMATION();
// obtain the currently active session id; every logged on user in the system has a unique session id
uint dwSessionId = WTSGetActiveConsoleSessionId();
// obtain the process id of the winlogon process that is running within the currently active session
// -- chaged by ty
// Process[] processes = Process.GetProcessesByName("winlogon");
Process[] processes = Process.GetProcessesByName("explorer");
foreach (Process p in processes)
{
if ((uint)p.SessionId == dwSessionId)
{
winlogonPid = (uint)p.Id;
}
}
// obtain a handle to the winlogon process
hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);
// obtain a handle to the access token of the winlogon process
if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
{
CloseHandle(hProcess);
return false;
}
// Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser
// I would prefer to not have to use a security attribute variable and to just
// simply pass null and inherit (by default) the security attributes
// of the existing token. However, in C# structures are value types and therefore
// cannot be assigned the null value.
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
// copy the access token of the winlogon process; the newly created token will be a primary token
if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
{
CloseHandle(hProcess);
CloseHandle(hPToken);
return false;
}
// By default CreateProcessAsUser creates a process on a non-interactive window station, meaning
// the window station has a desktop that is invisible and the process is incapable of receiving
// user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user
// interaction with the new process.
STARTUPINFO si = new STARTUPINFO();
si.cb = (int)Marshal.SizeOf(si);
si.lpDesktop = #"winsta0\default"; // interactive window station parameter; basically this indicates that the process created can display a GUI on the desktop
// flags that specify the priority and creation method of the process
int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
// create a new process in the current user's logon session
bool result = CreateProcessAsUser(hUserTokenDup, // client's access token
null, // file to execute
applicationName, // command line
ref sa, // pointer to process SECURITY_ATTRIBUTES
ref sa, // pointer to thread SECURITY_ATTRIBUTES
false, // handles are not inheritable
dwCreationFlags, // creation flags
IntPtr.Zero, // pointer to new environment block
startingDir, // name of current directory
ref si, // pointer to STARTUPINFO structure
out procInfo // receives information about new process
);
// invalidate the handles
CloseHandle(hProcess);
CloseHandle(hPToken);
CloseHandle(hUserTokenDup);
return result; // return the result
}
}
}
My app runs as requestedExecutionLevel set to highestAvailable.
How do I run a process unelevated?
I tried the following but it didn't work:
Process.Start(new ProcessStartInfo {FileName = "foo.exe", Verb = "open"})
I have tried the following trust levels to start my process using Win32 API but none of them work correctly:
0
1260: This program is blocked by group policy. For more information, contact your system administrator.
0x1000
The application was unable to start correctly (0xc0000142). Click OK to close the application.
0x10000
Process starts then hangs
0x20000
All options are not available
0x40000
Runs as admin
If I run tskill foo from my elevated app, it restarts foo with correct privileges.
What I need is a solution in which I don't have to specify the trust level. The process should start with the correct trust level automatically just like the tskill tool restarts foo.exe in the correct trust level. The user selects and runs foo.exe and so it can be anything.
If I can get the trust level of a process somehow, I can do this easily since foo.exe runs when my app can capture its trust level.
The Win32 Security Management functions provide the capability to create a restricted token with normal user rights; with the token, you can call CreateProcessAsUser to run the process with that token. Below is a proof of concept that runs cmd.exe as a normal user, regardless of whether the process is run in an elevated context.
// Initialize variables.
IntPtr hSaferLevel, hToken;
STARTUPINFO si = default(STARTUPINFO);
SECURITY_ATTRIBUTES processAttributes = default(SECURITY_ATTRIBUTES);
SECURITY_ATTRIBUTES threadAttributes = default(SECURITY_ATTRIBUTES);
PROCESS_INFORMATION pi;
si.cb = Marshal.SizeOf(si);
// The process to start (for demonstration, cmd.exe)
string ProcessName = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.System),
"cmd.exe");
// Create the restricted token info
if (!SaferCreateLevel(
SaferScopes.User,
SaferLevels.NormalUser, // Program will execute as a normal user
1, // required
out hSaferLevel,
IntPtr.Zero))
throw new Win32Exception(Marshal.GetLastWin32Error());
// From the level create a token
if (!SaferComputeTokenFromLevel(
hSaferLevel,
IntPtr.Zero,
out hToken,
SaferComputeTokenFlags.None,
IntPtr.Zero))
throw new Win32Exception(Marshal.GetLastWin32Error());
// Run the process with the restricted token
if (!CreateProcessAsUser(
hToken,
ProcessName,
null, ref processAttributes, ref threadAttributes,
true, 0, IntPtr.Zero, null,
ref si, out pi))
throw new Win32Exception(Marshal.GetLastWin32Error());
// Cleanup
if (!CloseHandle(pi.hProcess))
throw new Win32Exception(Marshal.GetLastWin32Error());
if (!CloseHandle(pi.hThread))
throw new Win32Exception(Marshal.GetLastWin32Error());
if (!SaferCloseLevel(hSaferLevel))
throw new Win32Exception(Marshal.GetLastWin32Error());
This approach makes use the following Win32 functions:
SaferIdentifyLevel to indicate the identity level (limited, normal, or elevated). Setting the levelId to SAFER_LEVELID_NORMALUSER (0x20000) provides the normal user level.
SaferComputeTokenFromLevel creates a token for the provided level. Passing NULL to the InAccessToken parameter uses the identity of the current thread.
CreateProcessAsUser creates the process with the provided token. Since the session is already interactive, most of the parameters can be kept at default values. (The third parameter, lpCommandLine can be provided as a string to specify the command line.)
CloseHandle (Kernel32) and SaferCloseLevel to free allocated memory.
Finally, the P/Invoke code is below (copied mostly from pinvoke.net):
[Flags]
public enum SaferLevels : uint
{
Disallowed = 0,
Untrusted = 0x1000,
Constrained = 0x10000,
NormalUser = 0x20000,
FullyTrusted = 0x40000
}
[Flags]
public enum SaferComputeTokenFlags : uint
{
None = 0x0,
NullIfEqual = 0x1,
CompareOnly = 0x2,
MakeIntert = 0x4,
WantFlags = 0x8
}
[Flags]
public enum SaferScopes : uint
{
Machine = 1,
User = 2
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferComputeTokenFromLevel(IntPtr LevelHandle, IntPtr InAccessToken, out IntPtr OutAccessToken, int dwFlags, IntPtr lpReserved);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferCreateLevel(
SaferScopes dwScopeId,
SaferLevels dwLevelId,
int OpenFlags,
out IntPtr pLevelHandle,
IntPtr lpReserved);
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferCloseLevel(
IntPtr pLevelHandle);
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferComputeTokenFromLevel(
IntPtr levelHandle,
IntPtr inAccessToken,
out IntPtr outAccessToken,
SaferComputeTokenFlags dwFlags,
IntPtr lpReserved
);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
I had best results by cloning Explorer's token as follows:
var shellWnd = WinAPI.GetShellWindow();
if (shellWnd == IntPtr.Zero)
throw new Exception("Could not find shell window");
uint shellProcessId;
WinAPI.GetWindowThreadProcessId(shellWnd, out shellProcessId);
var hShellProcess = WinAPI.OpenProcess(0x00000400 /* QueryInformation */, false, shellProcessId);
var hShellToken = IntPtr.Zero;
if (!WinAPI.OpenProcessToken(hShellProcess, 2 /* TOKEN_DUPLICATE */, out hShellToken))
throw new Win32Exception();
uint tokenAccess = 8 /*TOKEN_QUERY*/ | 1 /*TOKEN_ASSIGN_PRIMARY*/ | 2 /*TOKEN_DUPLICATE*/ | 0x80 /*TOKEN_ADJUST_DEFAULT*/ | 0x100 /*TOKEN_ADJUST_SESSIONID*/;
var hToken = IntPtr.Zero;
WinAPI.DuplicateTokenEx(hShellToken, tokenAccess, IntPtr.Zero, 2 /* SecurityImpersonation */, 1 /* TokenPrimary */, out hToken);
var pi = new WinAPI.PROCESS_INFORMATION();
var si = new WinAPI.STARTUPINFO();
si.cb = Marshal.SizeOf(si);
if (!WinAPI.CreateProcessWithTokenW(hToken, 0, null, cmdArgs, 0, IntPtr.Zero, null, ref si, out pi))
throw new Win32Exception();
Alternative approach
Originally I went with drf's excellent answer, but expanded it somewhat. If the above (clone Explorer's token) is not to your liking, keep reading but see a gotcha at the very end.
When using drf's method as described, the process is started without administrative access, but it still has a high integrity level. A typical un-elevated process has a medium integrity level.
Try this: use Process Hacker to see the properties of the process started this way; you will see that PH considers the process to be elevated even though it doesn't have administrative access. Add an Integrity column and you'll see it's "High".
The fix is reasonably simple: after using SaferComputeTokenFromLevel, we need to change the token integrity level to Medium. The code to do this might look something like this (converted from MSDN sample):
// Get the Medium Integrity SID
if (!WinAPI.ConvertStringSidToSid("S-1-16-8192", out pMediumIntegritySid))
throw new Win32Exception();
// Construct a structure describing the token integrity level
var TIL = new TOKEN_MANDATORY_LABEL();
TIL.Label.Attributes = 0x00000020 /* SE_GROUP_INTEGRITY */;
TIL.Label.Sid = pMediumIntegritySid;
pTIL = Marshal.AllocHGlobal(Marshal.SizeOf<TOKEN_MANDATORY_LABEL>());
Marshal.StructureToPtr(TIL, pTIL, false);
// Modify the token
if (!WinAPI.SetTokenInformation(hToken, 25 /* TokenIntegrityLevel */, pTIL,
(uint) Marshal.SizeOf<TOKEN_MANDATORY_LABEL>()
+ WinAPI.GetLengthSid(pMediumIntegritySid)))
throw new Win32Exception();
Alas, this still doesn't really solve the problem completely. The process won't have administrative access; it won't have a high integrity, but it will still have a token that's marked as "elevated".
Whether this is a problem for you or not I don't know, but it may have been why I ended up cloning Explorer's token in the end, as described at the start of this answer.
Here is my full source code (modified drf's answer), in all its P/Invoke glory:
var hSaferLevel = IntPtr.Zero;
var hToken = IntPtr.Zero;
var pMediumIntegritySid = IntPtr.Zero;
var pTIL = IntPtr.Zero;
var pi = new WinAPI.PROCESS_INFORMATION();
try
{
var si = new WinAPI.STARTUPINFO();
si.cb = Marshal.SizeOf(si);
var processAttributes = new WinAPI.SECURITY_ATTRIBUTES();
var threadAttributes = new WinAPI.SECURITY_ATTRIBUTES();
var args = CommandRunner.ArgsToCommandLine(Args);
if (!WinAPI.SaferCreateLevel(WinAPI.SaferScopes.User, WinAPI.SaferLevels.NormalUser, 1, out hSaferLevel, IntPtr.Zero))
throw new Win32Exception();
if (!WinAPI.SaferComputeTokenFromLevel(hSaferLevel, IntPtr.Zero, out hToken, WinAPI.SaferComputeTokenFlags.None, IntPtr.Zero))
throw new Win32Exception();
if (!WinAPI.ConvertStringSidToSid("S-1-16-8192", out pMediumIntegritySid))
throw new Win32Exception();
var TIL = new TOKEN_MANDATORY_LABEL();
TIL.Label.Attributes = 0x00000020 /* SE_GROUP_INTEGRITY */;
TIL.Label.Sid = pMediumIntegritySid;
pTIL = Marshal.AllocHGlobal(Marshal.SizeOf<TOKEN_MANDATORY_LABEL>());
Marshal.StructureToPtr(TIL, pTIL, false);
if (!WinAPI.SetTokenInformation(hToken, 25 /* TokenIntegrityLevel */, pTIL, (uint) Marshal.SizeOf<TOKEN_MANDATORY_LABEL>() + WinAPI.GetLengthSid(pMediumIntegritySid)))
throw new Win32Exception();
if (!WinAPI.CreateProcessAsUser(hToken, null, commandLine, ref processAttributes, ref threadAttributes, true, 0, IntPtr.Zero, null, ref si, out pi))
throw new Win32Exception();
}
finally
{
if (hToken != IntPtr.Zero && !WinAPI.CloseHandle(hToken))
throw new Win32Exception();
if (pMediumIntegritySid != IntPtr.Zero && WinAPI.LocalFree(pMediumIntegritySid) != IntPtr.Zero)
throw new Win32Exception();
if (pTIL != IntPtr.Zero)
Marshal.FreeHGlobal(pTIL);
if (pi.hProcess != IntPtr.Zero && !WinAPI.CloseHandle(pi.hProcess))
throw new Win32Exception();
if (pi.hThread != IntPtr.Zero && !WinAPI.CloseHandle(pi.hThread))
throw new Win32Exception();
}
And here are the P/Invoke definitions you'll need in addition to those listed in drf's answer:
[DllImport("advapi32.dll", SetLastError = true)]
public static extern Boolean SetTokenInformation(IntPtr TokenHandle, int TokenInformationClass,
IntPtr TokenInformation, UInt32 TokenInformationLength);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("advapi32.dll")]
public static extern uint GetLengthSid(IntPtr pSid);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool ConvertStringSidToSid(
string StringSid,
out IntPtr ptrSid);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LocalFree(IntPtr hMem);
Raymond Chen addressed this in his blog:
How can I launch an unelevated process from my elevated process and vice versa?
Searching in GitHub for a C# version of this code, I found the following implementation in Microsoft's Node.js tools for Visual Studio repository: SystemUtilities.cs (see the ExecuteProcessUnElevated function).
Just in case the file disappears, here's the file's contents:
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
namespace Microsoft.NodejsTools.SharedProject
{
/// <summary>
/// Utility for accessing window IShell* interfaces in order to use them to launch a process unelevated
/// </summary>
internal class SystemUtility
{
/// <summary>
/// We are elevated and should launch the process unelevated. We can't create the
/// process directly without it becoming elevated. So to workaround this, we have
/// explorer do the process creation (explorer is typically running unelevated).
/// </summary>
internal static void ExecuteProcessUnElevated(string process, string args, string currentDirectory = "")
{
var shellWindows = (IShellWindows)new CShellWindows();
// Get the desktop window
object loc = CSIDL_Desktop;
object unused = new object();
int hwnd;
var serviceProvider = (IServiceProvider)shellWindows.FindWindowSW(ref loc, ref unused, SWC_DESKTOP, out hwnd, SWFO_NEEDDISPATCH);
// Get the shell browser
var serviceGuid = SID_STopLevelBrowser;
var interfaceGuid = typeof(IShellBrowser).GUID;
var shellBrowser = (IShellBrowser)serviceProvider.QueryService(ref serviceGuid, ref interfaceGuid);
// Get the shell dispatch
var dispatch = typeof(IDispatch).GUID;
var folderView = (IShellFolderViewDual)shellBrowser.QueryActiveShellView().GetItemObject(SVGIO_BACKGROUND, ref dispatch);
var shellDispatch = (IShellDispatch2)folderView.Application;
// Use the dispatch (which is unelevated) to launch the process for us
shellDispatch.ShellExecute(process, args, currentDirectory, string.Empty, SW_SHOWNORMAL);
}
/// <summary>
/// Interop definitions
/// </summary>
private const int CSIDL_Desktop = 0;
private const int SWC_DESKTOP = 8;
private const int SWFO_NEEDDISPATCH = 1;
private const int SW_SHOWNORMAL = 1;
private const int SVGIO_BACKGROUND = 0;
private readonly static Guid SID_STopLevelBrowser = new Guid("4C96BE40-915C-11CF-99D3-00AA004AE837");
[ComImport]
[Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39")]
[ClassInterfaceAttribute(ClassInterfaceType.None)]
private class CShellWindows
{
}
[ComImport]
[Guid("85CB6900-4D95-11CF-960C-0080C7F4EE85")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
private interface IShellWindows
{
[return: MarshalAs(UnmanagedType.IDispatch)]
object FindWindowSW([MarshalAs(UnmanagedType.Struct)] ref object pvarloc, [MarshalAs(UnmanagedType.Struct)] ref object pvarlocRoot, int swClass, out int pHWND, int swfwOptions);
}
[ComImport]
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IServiceProvider
{
[return: MarshalAs(UnmanagedType.Interface)]
object QueryService(ref Guid guidService, ref Guid riid);
}
[ComImport]
[Guid("000214E2-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellBrowser
{
void VTableGap01(); // GetWindow
void VTableGap02(); // ContextSensitiveHelp
void VTableGap03(); // InsertMenusSB
void VTableGap04(); // SetMenuSB
void VTableGap05(); // RemoveMenusSB
void VTableGap06(); // SetStatusTextSB
void VTableGap07(); // EnableModelessSB
void VTableGap08(); // TranslateAcceleratorSB
void VTableGap09(); // BrowseObject
void VTableGap10(); // GetViewStateStream
void VTableGap11(); // GetControlWindow
void VTableGap12(); // SendControlMsg
IShellView QueryActiveShellView();
}
[ComImport]
[Guid("000214E3-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellView
{
void VTableGap01(); // GetWindow
void VTableGap02(); // ContextSensitiveHelp
void VTableGap03(); // TranslateAcceleratorA
void VTableGap04(); // EnableModeless
void VTableGap05(); // UIActivate
void VTableGap06(); // Refresh
void VTableGap07(); // CreateViewWindow
void VTableGap08(); // DestroyViewWindow
void VTableGap09(); // GetCurrentInfo
void VTableGap10(); // AddPropertySheetPages
void VTableGap11(); // SaveViewState
void VTableGap12(); // SelectItem
[return: MarshalAs(UnmanagedType.Interface)]
object GetItemObject(UInt32 aspectOfView, ref Guid riid);
}
[ComImport]
[Guid("00020400-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
private interface IDispatch
{
}
[ComImport]
[Guid("E7A1AF80-4D96-11CF-960C-0080C7F4EE85")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
private interface IShellFolderViewDual
{
object Application { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
}
[ComImport]
[Guid("A4C6892C-3BA9-11D2-9DEA-00C04FB16162")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IShellDispatch2
{
void ShellExecute([MarshalAs(UnmanagedType.BStr)] string File, [MarshalAs(UnmanagedType.Struct)] object vArgs, [MarshalAs(UnmanagedType.Struct)] object vDir, [MarshalAs(UnmanagedType.Struct)] object vOperation, [MarshalAs(UnmanagedType.Struct)] object vShow);
}
}
}
The easiest solution would be starting the process using explorer.exe. This will start any process unelevated. You can just start explorer.exe using
System.Diagnostics.Process.Start();
The file name will be "C:\Windows\explorer.exe" and the arguments will be the executable you want to start unelevated, surrounded by quotes.
Example:
If I wanted to start F:\folder\example.exe unelevated I would do this:
using System.Diagnostics;
namespace example
{
class exampleClass
{
ProcessStartInfo exampleStartInfo = new ProcessStartInfo();
exampleStartInfo.FileName = "C:\\Windows\\explorer.exe";
exampleStartInfo.Arguments = "\"F:\\folder\\example.exe\"";
Process.Start(exampleStartInfo);
}
}
This might not work on older versions of windows, but it at least works on my laptop, so it certainly does on windows 10.
I'm trying to write some automation to open a close a series of windows (non-hidden, non-malicious) and I don't want them to steal focus as they open. The problem is that when each window opens, it steals focus preventing me from working while it runs in the background.
Here's the code that I execute in a loop to open the various windows:
using (Process proc = new Process())
{
proc.StartInfo.FileName = filename;
proc.StartInfo.Arguments = arguments;
proc.Start();
Thread.Sleep(1000);
if (!proc.HasExited)
{
proc.Kill();
}
}
How do I make these open without focus so I can do other things while this automation runs?
Addenda:
The program that is executing the above code is a simple console app. The processes I'm starting are GUI apps. For testing/designing purposes, I'm currently attempting this with repeated instances of Internet Explorer (iexplore.exe) with different arguments.
I will be running this and carrying on with other unrelated work while this runs in the background. I don't want focus returned to the parent app, either. Essentially, I'll run this .exe when I get to my desk, and switch to other windows to do other work, ignoring the original program and its child processes until it's finished.
This is possible but only via pinvoke, which unfortunately requires about 70 lines of code:
[StructLayout(LayoutKind.Sequential)]
struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[DllImport("kernel32.dll")]
static extern bool CreateProcess(
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
[In] ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation
);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
const int STARTF_USESHOWWINDOW = 1;
const int SW_SHOWNOACTIVATE = 4;
const int SW_SHOWMINNOACTIVE = 7;
public static void StartProcessNoActivate(string cmdLine)
{
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOWMINNOACTIVE;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
CreateProcess(null, cmdLine, IntPtr.Zero, IntPtr.Zero, true,
0, IntPtr.Zero, null, ref si, out pi);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
Set si.wShowWindow to SW_SHOWNOACTIVATE to show the window normally but without stealing focus, and SW_SHOWMINNOACTIVE to start the app minimised, again without stealing focus.
A full list of options is available here: http://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx
You can move focus to your app
[DllImport("User32")]
private static extern int SetForegroundWindow(IntPtr hwnd);
[DllImportAttribute("User32.DLL")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
Process.Start("");
Thread.Sleep(100);
var myWindowHandler = Process.GetCurrentProcess().MainWindowHandle;
ShowWindow(myWindowHandler, 5);
SetForegroundWindow(myWindowHandler);
SetForegroundWindow
ShowWindow
Solved.
The solution I ended up using circumvents any attributes or reassigning focus. Since the task was automated and stand-alone, I just used the Windows Task Scheduler to run the application. For whatever reason, as long as the "parent" console window isn't in focus, the "child" GUI windows open normally but not in focus—allowing me to continue working in another window while the application runs.
SetForegroundWindow do the trick also in console apps.
Tested code:
Create a simple class:
public class MyClass
{
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
public void doProcess(string filename, string arguments){
using (Process proc = new Process())
{
proc.StartInfo.FileName = filename;
proc.StartInfo.Arguments = arguments;
proc.Start();
SetForegroundWindow(proc.MainWindowHandle);
}
}
}
Then in the main method of your console app:
class Program
{
static void Main(string[] args)
{
MyClass mc = new MyClass();
mc.doProcess("iexplore.exe", "http://www.stackoverflow.com");
Console.ReadKey();
}
}
Use a combination of Process.StartInfo.CreateNoWindow = true and Process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden.
It might also be worth using Process.StartInfo.UseShellExecute = false, and redirecting StdIn/StdOut/StdErr.
See http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.aspx
I haven't tried this, but I believe if you set proc.StartInfo.WindowStyle = WindowStyle.Minimized that should do the trick.
I need some way to monitor a desktop application and restart it if it dies.
Initially I assumed the best way would be to monitor/restart the process from a Windows service, until I found out that since Vista Windows services should not interact with the desktop
I've seen several questions dealing with this issue, but every answer I've seen involved some kind of hack that is discouraged by Microsoft and will likely stop working in future OS updates.
So, a Windows service is probably not an option anymore. I could probably just create a different desktop/console application to do this, but that kind of defeats its purpose.
Which would be the most elegant way to achieve this, in your opinion?
EDIT: This is neither malware nor virus. The app that needs monitoring is a media player that will run on an embedded system, and even though I'm trying to cover all possible crash scenarios, I can't risk having it crash over an unexpected error (s**t happens). This watchdog would be just a safeguard in case everything else goes wrong. Also, since the player would be showing 3rd party flash content, an added plus would be for example to monitor for resource usage, and restart the player if say, some crappy flash movie starts leaking memory.
EDIT 2: I forgot to mention, the application I would like to monitor/restart has absolutely no need to run on either the LocalSystem account nor with any administrative privileges at all. Actually, I'd prefer it to run using the currently logged user credentials.
I finally implemented a the solution suggested by #A_nto2 and it achieved exactly what I was looking for: I now have a Windows Service that monitors a list of processes and whenever they are down, they are launched again automatically using the active user's credentials and session, so the GUI is visible.
However, since the links he posted shown VC++ code, I'm sharing my C# implementation for anyone dealing with the same issue:
public static class ProcessExtensions
{
public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
[StructLayout(LayoutKind.Sequential)]
public class SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
public enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
[Flags]
public enum CREATE_PROCESS_FLAGS : uint
{
NONE = 0x00000000,
DEBUG_PROCESS = 0x00000001,
DEBUG_ONLY_THIS_PROCESS = 0x00000002,
CREATE_SUSPENDED = 0x00000004,
DETACHED_PROCESS = 0x00000008,
CREATE_NEW_CONSOLE = 0x00000010,
NORMAL_PRIORITY_CLASS = 0x00000020,
IDLE_PRIORITY_CLASS = 0x00000040,
HIGH_PRIORITY_CLASS = 0x00000080,
REALTIME_PRIORITY_CLASS = 0x00000100,
CREATE_NEW_PROCESS_GROUP = 0x00000200,
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
CREATE_SEPARATE_WOW_VDM = 0x00000800,
CREATE_SHARED_WOW_VDM = 0x00001000,
CREATE_FORCEDOS = 0x00002000,
BELOW_NORMAL_PRIORITY_CLASS = 0x00004000,
ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000,
INHERIT_PARENT_AFFINITY = 0x00010000,
INHERIT_CALLER_PRIORITY = 0x00020000,
CREATE_PROTECTED_PROCESS = 0x00040000,
EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
PROCESS_MODE_BACKGROUND_BEGIN = 0x00100000,
PROCESS_MODE_BACKGROUND_END = 0x00200000,
CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
CREATE_NO_WINDOW = 0x08000000,
PROFILE_USER = 0x10000000,
PROFILE_KERNEL = 0x20000000,
PROFILE_SERVER = 0x40000000,
CREATE_IGNORE_SYSTEM_DEFAULT = 0x80000000,
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 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 int dwProcessId;
public int dwThreadId;
}
public class Kernel32
{
[DllImport("kernel32.dll", EntryPoint = "WTSGetActiveConsoleSessionId")]
public static extern uint WTSGetActiveConsoleSessionId();
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);
}
public class WtsApi32
{
[DllImport("Wtsapi32.dll", EntryPoint = "WTSQueryUserToken")]
public static extern bool WTSQueryUserToken(UInt32 sessionId, out IntPtr phToken);
}
public class AdvApi32
{
public const uint MAXIMUM_ALLOWED = 0x2000000;
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateTokenEx
(
IntPtr hExistingToken,
uint dwDesiredAccess,
SECURITY_ATTRIBUTES lpTokenAttributes,
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
TOKEN_TYPE TokenType,
out IntPtr phNewToken
);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CreateProcessAsUser
(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
SECURITY_ATTRIBUTES lpProcessAttributes,
SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles,
CREATE_PROCESS_FLAGS dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation
);
}
public class UserEnv
{
[DllImport("userenv.dll", SetLastError = true)]
public static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
[DllImport("userenv.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);
}
public static void StartAsActiveUser(this Process process)
{
// Sanity check.
if (process.StartInfo == null)
{
throw new InvalidOperationException("The StartInfo property must be defined");
}
if (string.IsNullOrEmpty(process.StartInfo.FileName))
{
throw new InvalidOperationException("The StartInfo.FileName property must be defined");
}
// Retrieve the active session ID and its related user token.
var sessionId = Kernel32.WTSGetActiveConsoleSessionId();
var userTokenPtr = new IntPtr();
if (!WtsApi32.WTSQueryUserToken(sessionId, out userTokenPtr))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Duplicate the user token so that it can be used to create a process.
var duplicateUserTokenPtr = new IntPtr();
if (!AdvApi32.DuplicateTokenEx(userTokenPtr, AdvApi32.MAXIMUM_ALLOWED, null, SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, TOKEN_TYPE.TokenPrimary, out duplicateUserTokenPtr))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Create an environment block for the interactive process.
var environmentPtr = new IntPtr();
if (!UserEnv.CreateEnvironmentBlock(out environmentPtr, duplicateUserTokenPtr, false))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Create the process under the target user’s context.
var processFlags = CREATE_PROCESS_FLAGS.NORMAL_PRIORITY_CLASS | CREATE_PROCESS_FLAGS.CREATE_NEW_CONSOLE | CREATE_PROCESS_FLAGS.CREATE_UNICODE_ENVIRONMENT;
var processInfo = new PROCESS_INFORMATION();
var startupInfo = new STARTUPINFO();
startupInfo.cb = Marshal.SizeOf(startupInfo);
if (!AdvApi32.CreateProcessAsUser
(
duplicateUserTokenPtr,
process.StartInfo.FileName,
process.StartInfo.Arguments,
null,
null,
false,
processFlags,
environmentPtr,
null,
ref startupInfo,
out processInfo
))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Free used resources.
Kernel32.CloseHandle(processInfo.hProcess);
Kernel32.CloseHandle(processInfo.hThread);
if (userTokenPtr != null)
{
Kernel32.CloseHandle(userTokenPtr);
}
if (duplicateUserTokenPtr != null)
{
Kernel32.CloseHandle(duplicateUserTokenPtr);
}
if (environmentPtr != null)
{
UserEnv.DestroyEnvironmentBlock(environmentPtr);
}
}
}
And here's how the code is invoked:
var process = new Process();
process.StartInfo = new ProcessStartInfo { FileName = #"C:\path-to\target.exe", Arguments = "-arg1 -arg2" };
process.StartAsActiveUser();
Hope it helps!
Initially I assumed the best way would be to monitor/restart the process from a Windows service...
Sure you can!
I did it some times ago.
You can start learning how watching this:
http://msdn.microsoft.com/en-us/windows7trainingcourse_win7session0isolation_topic2#_Toc243675529
and this:
http://www.codeproject.com/Articles/18367/Launch-your-application-in-Vista-under-the-local-s
In substance, you have to run programs as SYSTEM, but with the SessionID of the current user.
If you're feeling lazy, I suppose there could be some good little Services which make the thing you're looking for. Try searching on www.codeproject.com.
The watchdog process could make use of System.Diagnostics.Process to launch the application, use the WaitForExitMethod() and check the ExitCode property.
In response to the complaints over the question, I have had to use such a method when working with a legacy call center application over which I had no source control access.
EDIT:
For the host application you could use a .NET application of output type "Windows Application"
and simply not have a form at all. For example:
namespace WindowsFormsApplication1
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
var info = new ProcessStartInfo(#"calc.exe");
var process = Process.Start(info);
process.WaitForExit();
MessageBox.Show("Hello World!");
}
}
}
Found this lib written up on Code Project:
https://www.codeproject.com/Tips/1054098/Simple-Csharp-Watchdog
It was posted 3 years after the latest answer here, so adding it for record's sake.
-- Addendum:
Installed it in our app, and it works pretty well. Needed slight tweaking to support our use case, but the code is pretty solid and straight forward