Related
I want to find which files is currently opened by Excel, Word of PDF process.
In x64dbg i can see info about process and can see needed file, but C# and WMI looks like do not allow to get such information.
The handle.exe is not very good solution, I do not want to use it and parse data.
So is there any way to do it using C# and WMI, if not, then what Win32 API I can use to find Handles associated with process.
The ntdll.dll ->NtQueryInformationProcess it is allows me to get address of process but how to use it to read Handles?
Thanks to all, I have found a solution.NtQueryObject hang when FileTypePipe
So there is a lot of solutions in the internet but most of them have problem with hanging when getting name for FileTypePipe :)
public class ProcessUtility
{
/// <summary>
/// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle_table_entry.htm?ts=0,242
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct SYSTEM_HANDLE_INFORMATION
{ // Information Class 16
public ushort ProcessID;
public ushort CreatorBackTrackIndex;
public byte ObjectType;
public byte HandleAttribute;
public ushort Handle;
public IntPtr Object_Pointer;
public IntPtr AccessMask;
}
private enum OBJECT_INFORMATION_CLASS : int
{
ObjectBasicInformation = 0,
ObjectNameInformation = 1,
ObjectTypeInformation = 2,
ObjectAllTypesInformation = 3,
ObjectHandleInformation = 4
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct OBJECT_NAME_INFORMATION
{ // Information Class 1
public UNICODE_STRING Name;
}
[StructLayout(LayoutKind.Sequential)]
private struct UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
public IntPtr Buffer;
}
[Flags]
private enum PROCESS_ACCESS_FLAGS : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VMOperation = 0x00000008,
VMRead = 0x00000010,
VMWrite = 0x00000020,
DupHandle = 0x00000040,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
Synchronize = 0x00100000
}
private enum FileType : uint
{
FileTypeChar = 0x0002,
FileTypeDisk = 0x0001,
FileTypePipe = 0x0003,
FileTypeRemote = 0x8000,
FileTypeUnknown = 0x0000,
}
[DllImport("ntdll.dll")]
private static extern uint NtQuerySystemInformation(int SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength, ref int returnLength);
[DllImport("kernel32.dll")]
private static extern IntPtr OpenProcess(PROCESS_ACCESS_FLAGS dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions);
[DllImport("kernel32.dll")]
private static extern IntPtr GetCurrentProcess();
[DllImport("ntdll.dll")]
private static extern int NtQueryObject(IntPtr ObjectHandle, int ObjectInformationClass, IntPtr ObjectInformation, int ObjectInformationLength, ref int returnLength);
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax);
[DllImport("kernel32.dll")]
private static extern bool GetHandleInformation(IntPtr hObject, out uint lpdwFlags);
[DllImport("kernel32.dll")]
private static extern FileType GetFileType(IntPtr hFile);
private const int MAX_PATH = 260;
private const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
private const int DUPLICATE_SAME_ACCESS = 0x2;
private const uint FILE_SEQUENTIAL_ONLY = 0x00000004;
private const int CNST_SYSTEM_HANDLE_INFORMATION = 0x10;
private const int OBJECT_TYPE_FILE = 0x24;
public static List<string> FindFilesByExtension(List<Process> target_processes, List<string> target_extensions)
{
List<string> aFilePaths = new List<string>();
if (target_extensions == null || target_extensions.Count == 0)
{
throw new Exception("Exceptions not defined");
}
foreach (Process process in target_processes)
{
List<string> aProcessFiles = GetPrcessFiles(target_processes);
foreach (string file_path in aProcessFiles)
{
if (target_extensions.Contains(Path.GetExtension(file_path.ToLower()))
&& !Path.GetFileName(file_path).StartsWith("~"))
{
aFilePaths.Add(file_path);
}
}
}
return aFilePaths;
}
public static List<string> GetPrcessFiles(List<Process> target_processes)
{
List<string> aFiles = new List<string>();
foreach (Process process in target_processes)
{
List<SYSTEM_HANDLE_INFORMATION> aHandles = GetFileHandles(process).ToList();
foreach (SYSTEM_HANDLE_INFORMATION handle_info in aHandles)
{
string file_path = GetFilePath(handle_info, process);
if (!string.IsNullOrEmpty(file_path))
{
aFiles.Add(file_path);
}
}
}
return aFiles;
}
private static IEnumerable<SYSTEM_HANDLE_INFORMATION> GetFileHandles(Process process)
{
List<SYSTEM_HANDLE_INFORMATION> aHandles = new List<SYSTEM_HANDLE_INFORMATION>();
int handle_info_size = Marshal.SizeOf(new SYSTEM_HANDLE_INFORMATION()) * 20000;
IntPtr ptrHandleData = IntPtr.Zero;
try
{
ptrHandleData = Marshal.AllocHGlobal(handle_info_size);
int nLength = 0;
while (NtQuerySystemInformation(CNST_SYSTEM_HANDLE_INFORMATION, ptrHandleData, handle_info_size, ref nLength) == STATUS_INFO_LENGTH_MISMATCH)
{
handle_info_size = nLength;
Marshal.FreeHGlobal(ptrHandleData);
ptrHandleData = Marshal.AllocHGlobal(nLength);
}
long handle_count = Marshal.ReadIntPtr(ptrHandleData).ToInt64();
IntPtr ptrHandleItem = ptrHandleData + Marshal.SizeOf(ptrHandleData);
for (long lIndex = 0; lIndex < handle_count; lIndex++)
{
SYSTEM_HANDLE_INFORMATION oSystemHandleInfo = Marshal.PtrToStructure<SYSTEM_HANDLE_INFORMATION>(ptrHandleItem);
ptrHandleItem += Marshal.SizeOf(new SYSTEM_HANDLE_INFORMATION());
if (oSystemHandleInfo.ProcessID != process.Id || oSystemHandleInfo.ObjectType != OBJECT_TYPE_FILE)
{ continue; }
aHandles.Add(oSystemHandleInfo);
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
Marshal.FreeHGlobal(ptrHandleData);
}
return aHandles;
}
private static string GetFilePath(SYSTEM_HANDLE_INFORMATION systemHandleInformation, Process process)
{
IntPtr ipHandle = IntPtr.Zero;
IntPtr openProcessHandle = IntPtr.Zero;
IntPtr hObjectName = IntPtr.Zero;
try
{
PROCESS_ACCESS_FLAGS flags = PROCESS_ACCESS_FLAGS.DupHandle | PROCESS_ACCESS_FLAGS.VMRead;
openProcessHandle = OpenProcess(flags, false, process.Id);
if (!DuplicateHandle(openProcessHandle, new IntPtr(systemHandleInformation.Handle), GetCurrentProcess(), out ipHandle, 0, false, DUPLICATE_SAME_ACCESS))
{
return null;
}
if (GetFileType(ipHandle) != FileType.FileTypeDisk)
{ return null; }
int nLength = 0;
hObjectName = Marshal.AllocHGlobal(256 * 1024);
while ((uint)(NtQueryObject(ipHandle, (int)OBJECT_INFORMATION_CLASS.ObjectNameInformation, hObjectName, nLength, ref nLength)) == STATUS_INFO_LENGTH_MISMATCH)
{
Marshal.FreeHGlobal(hObjectName);
if (nLength == 0)
{
Console.WriteLine("Length returned at zero!");
return null;
}
hObjectName = Marshal.AllocHGlobal(nLength);
}
OBJECT_NAME_INFORMATION objObjectName = Marshal.PtrToStructure<OBJECT_NAME_INFORMATION>(hObjectName);
if (objObjectName.Name.Buffer != IntPtr.Zero)
{
string strObjectName = Marshal.PtrToStringUni(objObjectName.Name.Buffer);
return GetRegularFileNameFromDevice(strObjectName);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Marshal.FreeHGlobal(hObjectName);
CloseHandle(ipHandle);
CloseHandle(openProcessHandle);
}
return null;
}
private static string GetRegularFileNameFromDevice(string strRawName)
{
string strFileName = strRawName;
foreach (string strDrivePath in Environment.GetLogicalDrives())
{
var sbTargetPath = new StringBuilder(MAX_PATH);
if (QueryDosDevice(strDrivePath.Substring(0, 2), sbTargetPath, MAX_PATH) == 0)
{
return strRawName;
}
string strTargetPath = sbTargetPath.ToString();
if (strFileName.StartsWith(strTargetPath))
{
strFileName = strFileName.Replace(strTargetPath, strDrivePath.Substring(0, 2));
break;
}
}
return strFileName;
}
}
I'm making a interactive service on Windows 7, on visual studio 2015, that is able to initialize an application UI but the WTSQueryUserToken method is retuning false.
IntPtr hToken = IntPtr.Zero;
if (WTSQueryUserToken(tSessionInfo.SessionID, out hToken)) //FALSE returned
I really don't have much experience with C# so I've searched online to get an answer and I found that "To call this function (WTSQueryUserToken) successfully, the calling application must be running within the context of the LocalSystem account and have the SE_TCB_NAME privilege", but I don't know how can I give the application SE_TCB_NAME privilege privilege on the code. Does anyone know how can I code this?
Thank you.
You have to do impersonation to launch the application under the user account.
Here is a sample of that, showing also how to get the SE_TCB_NAME privilege.
In this sample we first get the security token for the Current Process and then escalate the privileges to include the SE_TCB_NAME privilege.
Once we are done with that then we take the SessionId of the explorer process, and duplicate the security token associated with it, which we then pass to CreateProcessAsUser.
public class Impersonation
{
#region DLL Imports
internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
internal const int TOKEN_QUERY = 0x00000008;
internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
internal const int TOKEN_ASSIGN_PRIMARY = 0x0001;
internal const int TOKEN_DUPLICATE = 0x0002;
internal const int TOKEN_IMPERSONATE = 0X00000004;
internal const int TOKEN_ADJUST_DEFAULT = 0x0080;
internal const int TOKEN_ADJUST_SESSIONID = 0x0100;
internal const int MAXIMUM_ALLOWED = 0x2000000;
internal const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;
internal const int NORMAL_PRIORITY_CLASS = 0x20;
internal const int CREATE_NEW_CONSOLE = 0x00000010;
internal const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
internal const string SE_TCB_NAME = "SeTcbPrivilege";
internal const string SE_RESTORE_NAME = "SeRestorePrivilege";
private static WindowsImpersonationContext impersonatedUser;
public static IntPtr hToken = IntPtr.Zero;
public static IntPtr dupeTokenHandle = IntPtr.Zero;
const string SE_INCREASE_QUOTA_NAME = "SeIncreaseQuotaPrivilege";
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct TokPriv1Luid
{
public int Count;
public long Luid;
public int Attr;
}
struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
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 short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
public enum ShowCommands : int
{
SW_HIDE = 0,
SW_SHOWNORMAL = 1,
SW_NORMAL = 1,
SW_SHOWMINIMIZED = 2,
SW_SHOWMAXIMIZED = 3,
SW_MAXIMIZE = 3,
SW_SHOWNOACTIVATE = 4,
SW_SHOW = 5,
SW_MINIMIZE = 6,
SW_SHOWMINNOACTIVE = 7,
SW_SHOWNA = 8,
SW_RESTORE = 9,
SW_SHOWDEFAULT = 10,
SW_FORCEMINIMIZE = 11,
SW_MAX = 11
}
[DllImport("shell32.dll")]
static extern IntPtr ShellExecute(
IntPtr hwnd,
string lpOperation,
string lpFile,
string lpParameters,
string lpDirectory,
ShowCommands nShowCmd);
[DllImport("advapi32.dll", SetLastError = true)]
static extern int ImpersonateLoggedOnUser(IntPtr hToken);
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
[DllImport("kernel32", SetLastError = true), SuppressUnmanagedCodeSecurityAttribute]
static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateToken(IntPtr ExistingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
static extern bool DuplicateTokenEx(IntPtr hExistingToken, Int32 dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
Int32 ImpersonationLevel, Int32 dwTokenType,
ref IntPtr phNewToken);
[DllImport("userenv.dll", SetLastError = true)]
static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
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);
#endregion
private static readonly ILog log = LogManager.GetLogger(typeof(Impersonation));
private static void WriteToLog(string message)
{
log.Debug(message);
}
/// <summary>
/// Duplicates the token information derived
/// from the logged in user's credentials. This
/// is required to run the application on the
/// logged in users desktop.
/// </summary>
/// <returns>Returns true if the application was successfully started in the user's desktop.</returns>
public static bool ExecuteAppAsLoggedOnUser(string AppName, string CmdLineArgs)
{
WriteToLog("In ExecuteAppAsLoggedOnUser for all users.");
IntPtr LoggedInUserToken = IntPtr.Zero;
IntPtr DuplicateToken = IntPtr.Zero;
IntPtr ShellProcessToken = IntPtr.Zero;
if (!OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_ADJUST_PRIVILEGES, ref LoggedInUserToken))
{
WriteToLog("OpenProcessToken failed: " + Marshal.GetLastWin32Error());
return false;
}
else
{
//Below part for increasing the UAC previleges to the token.
TokPriv1Luid tp = new TokPriv1Luid();
tp.Count = 1;
tp.Luid = 0;
if (!LookupPrivilegeValue(null, SE_INCREASE_QUOTA_NAME, ref tp.Luid))
{
WriteToLog("LookupPrivilegeValue failed: " + Marshal.GetLastWin32Error());
return false;
}
tp.Attr = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(LoggedInUserToken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero))
{
WriteToLog("OpenProcessToken failed: " + Marshal.GetLastWin32Error());
return false;
}
CloseHandle(LoggedInUserToken);
}
List<Process> explorerProcessList = new List<Process>();
string trayProcessName = AppName.Substring(AppName.LastIndexOf(#"\") + 1, AppName.Length - AppName.LastIndexOf(#"\") - 5);
foreach (Process explorerProcess in Process.GetProcessesByName("explorer"))
{
bool IsProcessRunningForUser = false;
foreach (Process PHTrayProcess in Process.GetProcessesByName(trayProcessName))
{
if (explorerProcess.SessionId == PHTrayProcess.SessionId)
{
if (log.IsDebugEnabled) log.Debug(trayProcessName + " is already running for user SessionId " + explorerProcess.SessionId);
IsProcessRunningForUser = true;
break;
}
}
if (((Environment.OSVersion.Version.Major > 5 && explorerProcess.SessionId > 0)
|| Environment.OSVersion.Version.Major == 5)
&& !IsProcessRunningForUser)
{
if (log.IsDebugEnabled) log.Debug(trayProcessName + " is not running for user SessionId " + explorerProcess.SessionId);
explorerProcessList.Add(explorerProcess);
}
}
if (null != explorerProcessList && explorerProcessList.Count > 0)
{
foreach (Process explorerProcess in explorerProcessList)
{
Process ShellProcess = explorerProcess;
ShellProcess.StartInfo.LoadUserProfile = true;
try
{
int tokenRights = MAXIMUM_ALLOWED; //TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID;
if (!OpenProcessToken(ShellProcess.Handle, tokenRights, ref ShellProcessToken))
{
WriteToLog("Unable to OpenProcessToken " + Marshal.GetLastWin32Error());
return false;
}
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.nLength = Marshal.SizeOf(sa);
if (!DuplicateTokenEx(ShellProcessToken, tokenRights, ref sa, 2, 1, ref DuplicateToken))
{
WriteToLog("Unable to duplicate token " + Marshal.GetLastWin32Error());
return false;
}
WriteToLog("Duplicated the token " + WindowsIdentity.GetCurrent().Name);
SECURITY_ATTRIBUTES processAttributes = new SECURITY_ATTRIBUTES();
SECURITY_ATTRIBUTES threadAttributes = new SECURITY_ATTRIBUTES();
PROCESS_INFORMATION pi;
STARTUPINFO si = new STARTUPINFO();
si.cb = (uint)Marshal.SizeOf(si);
IntPtr UserEnvironment = IntPtr.Zero;
uint dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
if (!CreateEnvironmentBlock(out UserEnvironment, ShellProcessToken, true))
{
WriteToLog("Unable to create user's enviroment block " + Marshal.GetLastWin32Error());
}
else
{
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
}
//string userName = getUserName(UserEnvironment);
//WriteToLog("UserName:::" + userName);
if (!CreateProcessAsUser(DuplicateToken, AppName, (CmdLineArgs == null) ? string.Empty : CmdLineArgs, ref processAttributes, ref threadAttributes, true, dwCreationFlags, UserEnvironment, AppName.Substring(0, AppName.LastIndexOf('\\')), ref si, out pi))
{
WriteToLog("Unable to create process " + Marshal.GetLastWin32Error());
if (Marshal.GetLastWin32Error() == 740)
{
WriteToLog("Please check the installation as some elevated permissions is required to execute the binaries");
}
return false;
}
Process trayApp = Process.GetProcessById(Convert.ToInt32(pi.dwProcessId));
trayApp.StartInfo.LoadUserProfile = true;
}
finally
{
if (ShellProcessToken != null) CloseHandle(ShellProcessToken);
if (DuplicateToken != null) CloseHandle(DuplicateToken);
}
}
}
else
{
WriteToLog("No user has been identified to have logged into the system.");
return false;
}
WriteToLog("Finished ExecuteAppAsLoggedOnUser for all users.");
return true;
}
/// <summary>
/// Impersonate the user credentials. This would be required by
/// the service applications to impersonate the logged in user
/// credentials to launch certain applications or applying the
/// power scheme.
/// </summary>
/// <returns>Returns true if the impersonation is successful.</returns>
public static bool ImpersonateUser()
{
// For simplicity I'm using the PID of System here
//if (log.IsDebugEnabled) log.Debug("GetaProcess for Explorer");
Process Pname = GetaProcess("explorer");
//This can be null if no user has not logged into the system.
if (Pname == null) return false;
int pid = Pname.Id;
Process proc = Process.GetProcessById(pid);
if (OpenProcessToken(proc.Handle, TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_DUPLICATE, ref hToken)) // != 0)
{
WindowsIdentity newId = new WindowsIdentity(hToken);
//log.Debug(newId.Owner);
try
{
const int SecurityImpersonation = 2;
dupeTokenHandle = DupeToken(hToken,
SecurityImpersonation);
if (IntPtr.Zero == dupeTokenHandle)
{
string s = String.Format("Dup failed {0}, privilege not held",
Marshal.GetLastWin32Error());
throw new Exception(s);
}
impersonatedUser = newId.Impersonate();
return true;
}
finally
{
CloseHandle(hToken);
}
}
else
{
string s = String.Format("OpenProcess Failed {0}, privilege not held", Marshal.GetLastWin32Error());
throw new Exception(s);
}
}
/// <summary>
/// Duplicate the token for user impersonation.
/// </summary>
/// <param name="token">Token to duplicate for impersonation</param>
/// <param name="Level">Impersonation security level, currently hardcored to 2</param>
/// <returns>Returns duplicated token</returns>
public static IntPtr DupeToken(IntPtr token, int Level)
{
IntPtr dupeTokenHandle = IntPtr.Zero;
bool retVal = DuplicateToken(token, Level, ref dupeTokenHandle);
return dupeTokenHandle;
}
/// <summary>
/// Get the process running locally on the machine.
/// If the specified process does not exists, it
/// returns back the current process.
/// </summary>
/// <param name="processname">Process name to get</param>
/// <returns>Returns back the process</returns>
public static Process GetaProcess(string processname)
{
Process[] aProc = Process.GetProcessesByName(processname);
if (aProc.Length > 0) return aProc[0];
else
{
//if (log.IsDebugEnabled) log.Debug("Explorer is not running");
Process currentProcess = Process.GetCurrentProcess();
return currentProcess;
}
}
/// <summary>
/// Roleback the impersonation if applied previously.
/// </summary>
public static void UndoImpersonate()
{
impersonatedUser.Undo();
if (hToken != IntPtr.Zero) CloseHandle(hToken);
if (dupeTokenHandle != IntPtr.Zero) CloseHandle(dupeTokenHandle);
return;
}
}
And then you can just do
Impersonation.ExecuteAppAsLoggedOnUser("applicationName", null);
You can either set Act as part of the operating system in Local security policy, or you can also set the privileges programmatically using LsaAddAccountRights.
I have the following problem:
From a service I need to start an application in a user session. No human user log on that machine, since it is a server. Launched application must have a session != 0.
Current "solution"
I used a scheduled task at machine startup, that task launch ( in session 0, of course ) an application launching a Remote Desktop logon on the same machine: this creates a user session > 0 and in the user startup the is the final application to launch. It works, but too tricky.
Is there some smartest way? It is critical that I can reuse a user session already on since there is potentially no user logged on.
MAJOR UPDATE
Well after a lot of research and partial successes, and also thanks to some SysAdmin inflexibility about creating an user for a specific pourpose, I decided to use OpenGL instead of WPF for render the 3d portion broken in Session 0.
Surprisingly it took less than expected. I think having this question as a reference could be useful to other who want try to render a Viewport3D from a service.
I'm not sure if this will work, but maybe this answer helps in your case.
Use the class from the answer I link i provided and the following method (with the appropriate values):
public static void EnableVideoDrivers(bool enable)
{
// every type of device has a hard-coded GUID, put here the one for
// video drivers
Guid videoGuid = new Guid("{device GUID}");
// get this from the properties dialog box of this device in Device Manager
string instancePath = #"Device Instance Path";
DeviceHelper.SetDeviceEnabled(videoGuid, instancePath, enable);
}
Here's a list of Popular Device Class GUIDs.
I'm not sure I understand correctly your needs, but maybe just starting process with given credentials and redirect input and output is what you need. Starting process with given credentials:
Process p = new Process();
p.StartInfo = new ProcessStartInfo(fileName, args);
p.StartInfo.UserName = userName;
p.StartInfo.Password = pass;
p.Start();
You may also need to redirect input and output of the application. That problem is well described on CodeProjecgt in this artice.
This is how I start a process for a particular usersession from a Local windows service.
It uses C#, with some DLL imports from kernel32.dll, wtsaspi.dll, userev.dll, and advapi32.dll.
For context, my code will search all user sessions. In my scenario, my service is running on a Windows Terminal server and wants to keep a particular app "alive" in each user's session. Meaning, if we check and its not running anymore, we restart it.
Here is the program logic (abbreviated), this is how you call the method that starts the user process:
foreach(var sesh in ProcessExtensions.GetSessions().Where(r => r.State == "Active").ToList())
{
var running = procs.Any(r => r.ProcessName == filename && r.SessionId == sesh.SessionId);
if (!running)
{
try
{
ProcessExtensions.StartProcessForSession(sesh.SessionId, (string)item, "/restart", System.IO.Path.GetDirectoryName((string)item), true);
}
catch (Exception ex)
{
Trace.TraceWarning("Error: {0}", ex);
}
}
}
Here is the implementation of ProcessExtensions where all of the good stuff is.
Disclaimer - I did not write this code, This is an example I found online and adjusted it to my needs. If you authored the original post. Apologies for the lack of footnote.
ProcessExtensions.cs
public static class ProcessExtensions
{
#region Win32 Constants
private const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;
private const int CREATE_NO_WINDOW = 0x08000000;
private const int CREATE_NEW_CONSOLE = 0x00000010;
private const uint INVALID_SESSION_ID = 0xFFFFFFFF;
private static readonly IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;
#endregion
#region DllImports
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
private static extern bool CreateProcessAsUser(
IntPtr hToken,
String lpApplicationName,
String lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandle,
uint dwCreationFlags,
IntPtr lpEnvironment,
String lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
private static extern bool DuplicateTokenEx(
IntPtr ExistingTokenHandle,
uint dwDesiredAccess,
IntPtr lpThreadAttributes,
int TokenType,
int ImpersonationLevel,
ref IntPtr DuplicateTokenHandle);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
[DllImport("userenv.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hSnapshot);
[DllImport("kernel32.dll")]
private static extern uint WTSGetActiveConsoleSessionId();
[DllImport("Wtsapi32.dll")]
private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken);
[DllImport("wtsapi32.dll", SetLastError = true)]
private static extern int WTSEnumerateSessions(
IntPtr hServer,
int Reserved,
int Version,
ref IntPtr ppSessionInfo,
ref int pCount);
#endregion
#region Win32 Structs
private enum SW
{
SW_HIDE = 0,
SW_SHOWNORMAL = 1,
SW_NORMAL = 1,
SW_SHOWMINIMIZED = 2,
SW_SHOWMAXIMIZED = 3,
SW_MAXIMIZE = 3,
SW_SHOWNOACTIVATE = 4,
SW_SHOW = 5,
SW_MINIMIZE = 6,
SW_SHOWMINNOACTIVE = 7,
SW_SHOWNA = 8,
SW_RESTORE = 9,
SW_SHOWDEFAULT = 10,
SW_MAX = 10
}
private enum WTS_CONNECTSTATE_CLASS
{
WTSActive,
WTSConnected,
WTSConnectQuery,
WTSShadow,
WTSDisconnected,
WTSIdle,
WTSListen,
WTSReset,
WTSDown,
WTSInit
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
private enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3,
}
[StructLayout(LayoutKind.Sequential)]
private 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;
}
private enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation = 2
}
[StructLayout(LayoutKind.Sequential)]
private struct WTS_SESSION_INFO
{
public readonly UInt32 SessionID;
[MarshalAs(UnmanagedType.LPStr)]
public readonly String pWinStationName;
public readonly WTS_CONNECTSTATE_CLASS State;
}
#endregion
public static IEnumerable<UserSessionData> GetSessions()
{
//var bResult = false;
var hImpersonationToken = IntPtr.Zero;
//var activeSessionId = INVALID_SESSION_ID;
var pSessionInfo = IntPtr.Zero;
var sessionCount = 0;
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0)
{
var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
var current = pSessionInfo;
for (var i = 0; i < sessionCount; i++)
{
var si = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO));
current += arrayElementSize;
var data = new UserSessionData
{
SessionId = (int)si.SessionID,
State = si.State.ToString().Substring(3),
Name = si.pWinStationName
};
yield return data;
}
}
}
private static bool GetUserTokenForSession(int sessionId, ref IntPtr phUserToken)
{
var bResult = false;
var hImpersonationToken = IntPtr.Zero;
var pSessionInfo = IntPtr.Zero;
if (WTSQueryUserToken((uint)sessionId, ref hImpersonationToken) != 0)
{
// Convert the impersonation token to a primary token
bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary,
ref phUserToken);
CloseHandle(hImpersonationToken);
}
return bResult;
}
private static bool GetCurrentUserSessionToken(ref IntPtr phUserToken)
{
var bResult = false;
var hImpersonationToken = IntPtr.Zero;
var activeSessionId = INVALID_SESSION_ID;
var pSessionInfo = IntPtr.Zero;
var sessionCount = 0;
// Get a handle to the user access token for the current active session.
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0)
{
var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
var current = pSessionInfo;
for (var i = 0; i < sessionCount; i++)
{
var si = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO));
current += arrayElementSize;
if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive)
{
activeSessionId = si.SessionID;
}
}
}
// If enumerating did not work, fall back to the old method
if (activeSessionId == INVALID_SESSION_ID)
{
activeSessionId = WTSGetActiveConsoleSessionId();
}
if (WTSQueryUserToken(activeSessionId, ref hImpersonationToken) != 0)
{
// Convert the impersonation token to a primary token
bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary,
ref phUserToken);
CloseHandle(hImpersonationToken);
}
return bResult;
}
public static bool StartProcessForSession(int sessionId, string appPath, string cmdLine = null, string workDir = null, bool visible = true)
{
var hUserToken = IntPtr.Zero;
var startInfo = new STARTUPINFO();
var procInfo = new PROCESS_INFORMATION();
var pEnv = IntPtr.Zero;
int iResultOfCreateProcessAsUser;
startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO));
try
{
if (!GetUserTokenForSession(sessionId, ref hUserToken))
{
throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed.");
}
uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW);
startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE);
startInfo.lpDesktop = "winsta0\\default";
if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false))
{
throw new Exception("StartProcessInSession: CreateEnvironmentBlock failed.");
}
if (!CreateProcessAsUser(hUserToken,
appPath, // Application Name
cmdLine, // Command Line
IntPtr.Zero,
IntPtr.Zero,
false,
dwCreationFlags,
pEnv,
workDir, // Working directory
ref startInfo,
out procInfo))
{
iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed. Error Code -" + iResultOfCreateProcessAsUser);
}
iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
}
finally
{
CloseHandle(hUserToken);
if (pEnv != IntPtr.Zero)
{
DestroyEnvironmentBlock(pEnv);
}
CloseHandle(procInfo.hThread);
CloseHandle(procInfo.hProcess);
}
return true;
}
public static bool StartProcessAsCurrentUser(string appPath, string cmdLine = null, string workDir = null, bool visible = true)
{
var hUserToken = IntPtr.Zero;
var startInfo = new STARTUPINFO();
var procInfo = new PROCESS_INFORMATION();
var pEnv = IntPtr.Zero;
int iResultOfCreateProcessAsUser;
startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO));
try
{
if (!GetCurrentUserSessionToken(ref hUserToken))
{
throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed.");
}
uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW);
startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE);
startInfo.lpDesktop = "winsta0\\default";
if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false))
{
throw new Exception("StartProcessAsCurrentUser: CreateEnvironmentBlock failed.");
}
if (!CreateProcessAsUser(hUserToken,
appPath, // Application Name
cmdLine, // Command Line
IntPtr.Zero,
IntPtr.Zero,
false,
dwCreationFlags,
pEnv,
workDir, // Working directory
ref startInfo,
out procInfo))
{
iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed. Error Code -" + iResultOfCreateProcessAsUser);
}
iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
}
finally
{
CloseHandle(hUserToken);
if (pEnv != IntPtr.Zero)
{
DestroyEnvironmentBlock(pEnv);
}
CloseHandle(procInfo.hThread);
CloseHandle(procInfo.hProcess);
}
return true;
}
}
I have some C# code (VS2010;fx2) which is used to carry out printer functions. This code works fine in Windows XP environment. Changing to Windows 7, it no longer works correctly.
The first different behaviour is that the GetPrinterNames() method now only returns local printers. As you can see, the flags are set to include NETWORK printers also. I've tried different flags, but with no success.
Is there a different library I should be referencing in Windows 7 / 64 bit version?
Printer helper class with code shown below:
internal class Printers
{
...
[DllImport("winspool.drv", SetLastError = true)]
static extern bool EnumPrintersW(Int32 flags, [MarshalAs(UnmanagedType.LPTStr)] string printerName,
Int32 level, IntPtr buffer, Int32 bufferSize, out Int32 requiredBufferSize,
out Int32 numPrintersReturned);
[DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool EnumPrinters(PrinterEnumFlags Flags, string Name, uint Level, IntPtr pPrinterEnum, uint cbBuf, ref uint pcbNeeded, ref uint pcReturned);
...
...
public static string[] GetPrinterNames()
{
List<string> returnVal = new List<string>();
foreach(PRINTER_INFO_2 info in enumPrinters(PrinterEnumFlags.PRINTER_ENUM_LOCAL | PrinterEnumFlags.PRINTER_ENUM_NETWORK))
{
returnVal.Add(info.pPrinterName);
}
return returnVal.ToArray();
}
...
private static PRINTER_INFO_2[] enumPrinters(PrinterEnumFlags Flags)
{
uint cbNeeded = 0;
uint cReturned = 0;
if (EnumPrinters(Flags, null, 2, IntPtr.Zero, 0, ref cbNeeded, ref cReturned))
{
return null;
}
int lastWin32Error = Marshal.GetLastWin32Error();
if (lastWin32Error == ERROR_INSUFFICIENT_BUFFER)
{
IntPtr pAddr = Marshal.AllocHGlobal((int)cbNeeded);
if (EnumPrinters(Flags, null, 2, pAddr, cbNeeded, ref cbNeeded, ref cReturned))
{
PRINTER_INFO_2[] printerInfo2 = new PRINTER_INFO_2[cReturned];
int offset = pAddr.ToInt32();
Type type = typeof(PRINTER_INFO_2);
int increment = Marshal.SizeOf(type);
for (int i = 0; i < cReturned; i++)
{
printerInfo2[i] = (PRINTER_INFO_2)Marshal.PtrToStructure(new IntPtr(offset), type);
offset += increment;
}
Marshal.FreeHGlobal(pAddr);
return printerInfo2;
}
lastWin32Error = Marshal.GetLastWin32Error();
}
throw new System.ComponentModel.Win32Exception(lastWin32Error);
}
...
[FlagsAttribute]
enum PrinterEnumFlags
{
PRINTER_ENUM_DEFAULT = 0x00000001,
PRINTER_ENUM_LOCAL = 0x00000002,
PRINTER_ENUM_CONNECTIONS = 0x00000004,
PRINTER_ENUM_FAVORITE = 0x00000004,
PRINTER_ENUM_NAME = 0x00000008,
PRINTER_ENUM_REMOTE = 0x00000010,
PRINTER_ENUM_SHARED = 0x00000020,
PRINTER_ENUM_NETWORK = 0x00000040,
PRINTER_ENUM_EXPAND = 0x00004000,
PRINTER_ENUM_CONTAINER = 0x00008000,
PRINTER_ENUM_ICONMASK = 0x00ff0000,
PRINTER_ENUM_ICON1 = 0x00010000,
PRINTER_ENUM_ICON2 = 0x00020000,
PRINTER_ENUM_ICON3 = 0x00040000,
PRINTER_ENUM_ICON4 = 0x00080000,
PRINTER_ENUM_ICON5 = 0x00100000,
PRINTER_ENUM_ICON6 = 0x00200000,
PRINTER_ENUM_ICON7 = 0x00400000,
PRINTER_ENUM_ICON8 = 0x00800000,
PRINTER_ENUM_HIDE = 0x01000000
}
EDIT: Code edited to reduce size (areas of less interest removed).
On 64 bit windows machine is the exception an OverflowException (Arithmetic operation resulted in an overflow.) Caused by converting a pointer in a 32 bits integer instead of a 64 bits integer.
int offset = pAddr.ToInt32();
Fix it with
long offset = pAddr.ToInt64();
The complete working code, checked on Windows 10 x64
public static string[] GetPrinterNames()
{
var results = new List<string>();
foreach (PRINTER_INFO_2 info in enumPrinters(PrinterEnumFlags.PRINTER_ENUM_LOCAL | PrinterEnumFlags.PRINTER_ENUM_NETWORK))
results.Add(info.pPrinterName);
return results.ToArray();
}
private static PRINTER_INFO_2[] enumPrinters(PrinterEnumFlags Flags)
{
uint cbNeeded = 0;
uint cReturned = 0;
if (EnumPrinters(Flags, null, 2, IntPtr.Zero, 0, ref cbNeeded, ref cReturned))
{
return null;
}
int lastWin32Error = Marshal.GetLastWin32Error();
if (lastWin32Error == ERROR_INSUFFICIENT_BUFFER)
{
IntPtr pAddr = Marshal.AllocHGlobal((int)cbNeeded);
if (EnumPrinters(Flags, null, 2, pAddr, cbNeeded, ref cbNeeded, ref cReturned))
{
PRINTER_INFO_2[] printerInfo2 = new PRINTER_INFO_2[cReturned];
long offset = pAddr.ToInt64();
Type type = typeof(PRINTER_INFO_2);
int increment = Marshal.SizeOf(type);
for (int i = 0; i < cReturned; i++)
{
printerInfo2[i] = (PRINTER_INFO_2)Marshal.PtrToStructure(new IntPtr(offset), type);
offset += increment;
}
Marshal.FreeHGlobal(pAddr);
return printerInfo2;
}
lastWin32Error = Marshal.GetLastWin32Error();
}
throw new System.ComponentModel.Win32Exception(lastWin32Error);
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct PRINTER_INFO_2
{
[MarshalAs(UnmanagedType.LPTStr)]
public string pServerName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pPrinterName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pShareName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pPortName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pDriverName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pComment;
[MarshalAs(UnmanagedType.LPTStr)]
public string pLocation;
public IntPtr pDevMode;
[MarshalAs(UnmanagedType.LPTStr)]
public string pSepFile;
[MarshalAs(UnmanagedType.LPTStr)]
public string pPrintProcessor;
[MarshalAs(UnmanagedType.LPTStr)]
public string pDatatype;
[MarshalAs(UnmanagedType.LPTStr)]
public string pParameters;
public IntPtr pSecurityDescriptor;
public uint Attributes;
public uint Priority;
public uint DefaultPriority;
public uint StartTime;
public uint UntilTime;
public uint Status;
public uint cJobs;
public uint AveragePPM;
}
const int ERROR_INSUFFICIENT_BUFFER = 122;
[FlagsAttribute]
enum PrinterEnumFlags
{
PRINTER_ENUM_DEFAULT = 0x00000001,
PRINTER_ENUM_LOCAL = 0x00000002,
PRINTER_ENUM_CONNECTIONS = 0x00000004,
PRINTER_ENUM_FAVORITE = 0x00000004,
PRINTER_ENUM_NAME = 0x00000008,
PRINTER_ENUM_REMOTE = 0x00000010,
PRINTER_ENUM_SHARED = 0x00000020,
PRINTER_ENUM_NETWORK = 0x00000040,
PRINTER_ENUM_EXPAND = 0x00004000,
PRINTER_ENUM_CONTAINER = 0x00008000,
PRINTER_ENUM_ICONMASK = 0x00ff0000,
PRINTER_ENUM_ICON1 = 0x00010000,
PRINTER_ENUM_ICON2 = 0x00020000,
PRINTER_ENUM_ICON3 = 0x00040000,
PRINTER_ENUM_ICON4 = 0x00080000,
PRINTER_ENUM_ICON5 = 0x00100000,
PRINTER_ENUM_ICON6 = 0x00200000,
PRINTER_ENUM_ICON7 = 0x00400000,
PRINTER_ENUM_ICON8 = 0x00800000,
PRINTER_ENUM_HIDE = 0x01000000
}
[DllImport("winspool.drv", SetLastError = true)]
static extern bool EnumPrintersW(Int32 flags, [MarshalAs(UnmanagedType.LPTStr)] string printerName,
Int32 level, IntPtr buffer, Int32 bufferSize, out Int32 requiredBufferSize,
out Int32 numPrintersReturned);
[DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool EnumPrinters(PrinterEnumFlags Flags, string Name, uint Level, IntPtr pPrinterEnum, uint cbBuf, ref uint pcbNeeded, ref uint pcReturned);
According to msdn notes for EnumPrinters, your code only works if the 3rd paramater is 1
from EnumPrinters....
PRINTER_ENUM_NETWORK
The function enumerates network printers in the computer's domain. This value is valid only if Level is 1.
Do I need to enable Interactive desktp for it to work and what is the correct code to start an EXE or cmd window? I'm still unable to start the service even when I had enable it to interact with desktop.
I would be using an chat engine so it is easier to manage as a windows service.
What wrong with my code?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceProcess;
using System.Diagnostics;
using System.ComponentModel;
using System.Threading;
namespace MyNewService
{
class Program : ServiceBase
{
static void Main(string[] args)
{
}
public Program()
{
this.ServiceName = "Chatter";
}
protected override void OnStart(string[] args)
{
base.OnStart(args);
//TODO: place your start code here
ThreadStart starter = new ThreadStart(bw_DoWork);
Thread t = new Thread(starter);
t.Start();
}
private void bw_DoWork()
{
Process p = new Process();
p.StartInfo = new ProcessStartInfo(#"C:\Windows\system32\cmd.exe");
p.Start();
p.WaitForExit();
base.Stop();
}
protected override void OnStop()
{
base.OnStop();
//TODO: clean up any variables and stop any threads
}
}
}
I have gone through all the pain of doing this.
Under windows 7/Vista/2008 it is not possible to load any interactive process from a service - without calling a number of Win API. = BLACK MAGIC
Have a look here and here.
The code below does the trick, use it with your own risk:
public static class ProcessAsCurrentUser
{
/// <summary>
/// Connection state of a session.
/// </summary>
public enum ConnectionState
{
/// <summary>
/// A user is logged on to the session.
/// </summary>
Active,
/// <summary>
/// A client is connected to the session.
/// </summary>
Connected,
/// <summary>
/// The session is in the process of connecting to a client.
/// </summary>
ConnectQuery,
/// <summary>
/// This session is shadowing another session.
/// </summary>
Shadowing,
/// <summary>
/// The session is active, but the client has disconnected from it.
/// </summary>
Disconnected,
/// <summary>
/// The session is waiting for a client to connect.
/// </summary>
Idle,
/// <summary>
/// The session is listening for connections.
/// </summary>
Listening,
/// <summary>
/// The session is being reset.
/// </summary>
Reset,
/// <summary>
/// The session is down due to an error.
/// </summary>
Down,
/// <summary>
/// The session is initializing.
/// </summary>
Initializing
}
[StructLayout(LayoutKind.Sequential)]
class 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;
}
enum LOGON_TYPE
{
LOGON32_LOGON_INTERACTIVE = 2,
LOGON32_LOGON_NETWORK,
LOGON32_LOGON_BATCH,
LOGON32_LOGON_SERVICE,
LOGON32_LOGON_UNLOCK = 7,
LOGON32_LOGON_NETWORK_CLEARTEXT,
LOGON32_LOGON_NEW_CREDENTIALS
}
enum LOGON_PROVIDER
{
LOGON32_PROVIDER_DEFAULT,
LOGON32_PROVIDER_WINNT35,
LOGON32_PROVIDER_WINNT40,
LOGON32_PROVIDER_WINNT50
}
[Flags]
enum CreateProcessFlags : uint
{
CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
CREATE_NEW_CONSOLE = 0x00000010,
CREATE_NEW_PROCESS_GROUP = 0x00000200,
CREATE_NO_WINDOW = 0x08000000,
CREATE_PROTECTED_PROCESS = 0x00040000,
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
CREATE_SEPARATE_WOW_VDM = 0x00000800,
CREATE_SHARED_WOW_VDM = 0x00001000,
CREATE_SUSPENDED = 0x00000004,
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
DEBUG_ONLY_THIS_PROCESS = 0x00000002,
DEBUG_PROCESS = 0x00000001,
DETACHED_PROCESS = 0x00000008,
EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
INHERIT_PARENT_AFFINITY = 0x00010000
}
[StructLayout(LayoutKind.Sequential)]
public struct WTS_SESSION_INFO
{
public int SessionID;
[MarshalAs(UnmanagedType.LPTStr)]
public string WinStationName;
public ConnectionState State;
}
[DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern Int32 WTSEnumerateSessions(IntPtr hServer, int reserved, int version,
ref IntPtr sessionInfo, ref int count);
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUserW", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
UInt32 dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("wtsapi32.dll")]
public static extern void WTSFreeMemory(IntPtr memory);
[DllImport("kernel32.dll")]
private static extern UInt32 WTSGetActiveConsoleSessionId();
[DllImport("wtsapi32.dll", SetLastError = true)]
static extern int WTSQueryUserToken(UInt32 sessionId, out IntPtr Token);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateTokenEx(
IntPtr hExistingToken,
uint dwDesiredAccess,
IntPtr lpTokenAttributes,
int ImpersonationLevel,
int TokenType,
out IntPtr phNewToken);
private const int TokenImpersonation = 2;
private const int SecurityIdentification = 1;
private const int MAXIMUM_ALLOWED = 0x2000000;
private const int TOKEN_DUPLICATE = 0x2;
private const int TOKEN_QUERY = 0x00000008;
/// <summary>
/// Launches a process for the current logged on user if there are any.
/// If none, return false as well as in case of
///
/// ##### !!! BEWARE !!! #### ------------------------------------------
/// This code will only work when running in a windows service (where it is really needed)
/// so in case you need to test it, it needs to run in the service. Reason
/// is a security privileg which only services have (SE_??? something, cant remember)!
/// </summary>
/// <param name="processExe"></param>
/// <returns></returns>
public static bool CreateProcessAsCurrentUser(string processExe)
{
IntPtr duplicate = new IntPtr();
STARTUPINFO info = new STARTUPINFO();
PROCESS_INFORMATION procInfo = new PROCESS_INFORMATION();
Debug.WriteLine(string.Format("CreateProcessAsCurrentUser. processExe: " + processExe));
IntPtr p = GetCurrentUserToken();
bool result = DuplicateTokenEx(p, MAXIMUM_ALLOWED | TOKEN_QUERY | TOKEN_DUPLICATE, IntPtr.Zero, SecurityIdentification, SecurityIdentification, out duplicate);
Debug.WriteLine(string.Format("DuplicateTokenEx result: {0}", result));
Debug.WriteLine(string.Format("duplicate: {0}", duplicate));
if (result)
{
result = CreateProcessAsUser(duplicate, processExe, null,
IntPtr.Zero, IntPtr.Zero, false, (UInt32)CreateProcessFlags.CREATE_NEW_CONSOLE, IntPtr.Zero, null,
ref info, out procInfo);
Debug.WriteLine(string.Format("CreateProcessAsUser result: {0}", result));
}
if (p.ToInt32() != 0)
{
Marshal.Release(p);
Debug.WriteLine(string.Format("Released handle p: {0}", p));
}
if (duplicate.ToInt32() != 0)
{
Marshal.Release(duplicate);
Debug.WriteLine(string.Format("Released handle duplicate: {0}", duplicate));
}
return result;
}
public static int GetCurrentSessionId()
{
uint sessionId = WTSGetActiveConsoleSessionId();
Debug.WriteLine(string.Format("sessionId: {0}", sessionId));
if (sessionId == 0xFFFFFFFF)
return -1;
else
return (int)sessionId;
}
public static bool IsUserLoggedOn()
{
List<WTS_SESSION_INFO> wtsSessionInfos = ListSessions();
Debug.WriteLine(string.Format("Number of sessions: {0}", wtsSessionInfos.Count));
return wtsSessionInfos.Where(x => x.State == ConnectionState.Active).Count() > 0;
}
private static IntPtr GetCurrentUserToken()
{
List<WTS_SESSION_INFO> wtsSessionInfos = ListSessions();
int sessionId = wtsSessionInfos.Where(x => x.State == ConnectionState.Active).FirstOrDefault().SessionID;
//int sessionId = GetCurrentSessionId();
Debug.WriteLine(string.Format("sessionId: {0}", sessionId));
if (sessionId == int.MaxValue)
{
return IntPtr.Zero;
}
else
{
IntPtr p = new IntPtr();
int result = WTSQueryUserToken((UInt32)sessionId, out p);
Debug.WriteLine(string.Format("WTSQueryUserToken result: {0}", result));
Debug.WriteLine(string.Format("WTSQueryUserToken p: {0}", p));
return p;
}
}
public static List<WTS_SESSION_INFO> ListSessions()
{
IntPtr server = IntPtr.Zero;
List<WTS_SESSION_INFO> ret = new List<WTS_SESSION_INFO>();
try
{
IntPtr ppSessionInfo = IntPtr.Zero;
Int32 count = 0;
Int32 retval = WTSEnumerateSessions(IntPtr.Zero, 0, 1, ref ppSessionInfo, ref count);
Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
Int64 current = (int)ppSessionInfo;
if (retval != 0)
{
for (int i = 0; i < count; i++)
{
WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTS_SESSION_INFO));
current += dataSize;
ret.Add(si);
}
WTSFreeMemory(ppSessionInfo);
}
}
catch (Exception exception)
{
Debug.WriteLine(exception.ToString());
}
return ret;
}
}
When running as a service you won't be able to launch anything that needs to interact with the desktop OR will spawn it's own windows.
As Aliostad said, you need to call Win API calls to CreateProcessAsUser and emulate the user in order with this to work. This involves emulating the logged in user and using their credentials to "lift" your process into process isolation level 1 (which gives you access to the windowing system and things like the GPU).
I am doing this in an app I wrote and it does work but I agree with Aliostad there is a bit of Black magic going on and it generally sucks
Having said all that, you can spawn worker processes from within a service as along as they don't require things that are in process isolation level 1 (Windowing, GPU etc..)
cmd.exe by default will try to create a window, this is why your example is failing. You could set the following ProcessStartInfo properties to get it work.
CreateNoWindow
WindowStyle
I wrote an application watchdog service which simply restarts an application (in my case a Console Window App).
I found a very good Hands-On Lab Tutorial (in C++) which I tried at it worked for Session 0 Isolation at: http://msdn.microsoft.com/en-us/Windows7TrainingCourse_Win7Session0Isolation
I converted that C++ Sample into C#. After a few tests it worked. As long as I stay logged on and don't log out and log on again that code works perfect. I have to do a little to catch the Session Logout/Login. But for simple log-in and Work Condition in Windows the watchdog works as expected.
Here is that required PInvoke code:
[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;
}
public enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public extern static bool CloseHandle(IntPtr handle);
[DllImport("kernel32.dll")]
public static extern uint WTSGetActiveConsoleSessionId();
[DllImport("wtsapi32.dll", SetLastError = true)]
public static extern bool WTSQueryUserToken(UInt32 sessionId, out IntPtr Token);
[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", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateTokenEx(
IntPtr hExistingToken,
uint dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpTokenAttributes,
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
TOKEN_TYPE TokenType,
out IntPtr phNewToken);
Here is the encapsuled method :
private void CreateUserProcess()
{
bool ret;
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
uint dwSessionID = WTSGetActiveConsoleSessionId();
this.EventLog.WriteEntry("WTSGetActiveConsoleSessionId: " + dwSessionID, EventLogEntryType.FailureAudit);
IntPtr Token = new IntPtr();
ret = WTSQueryUserToken((UInt32)dwSessionID, out Token);
if (ret == false)
{
this.EventLog.WriteEntry("WTSQueryUserToken failed with " + Marshal.GetLastWin32Error(), EventLogEntryType.FailureAudit);
}
const uint MAXIMUM_ALLOWED = 0x02000000;
IntPtr DupedToken = IntPtr.Zero;
ret = DuplicateTokenEx(Token,
MAXIMUM_ALLOWED,
ref sa,
SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
TOKEN_TYPE.TokenPrimary,
out DupedToken);
if (ret == false)
{
this.EventLog.WriteEntry("DuplicateTokenEx failed with " + Marshal.GetLastWin32Error(), EventLogEntryType.FailureAudit);
}
else
{
this.EventLog.WriteEntry("DuplicateTokenEx SUCCESS", EventLogEntryType.SuccessAudit);
}
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
//si.lpDesktop = "";
string commandLinePath;
// commandLinePath example: "c:\myapp.exe c:\myconfig.xml" . cmdLineArgs can be ommited
commandLinePath = AppPath + " " + CmdLineArgs;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
//CreateProcessAsUser(hDuplicatedToken, NULL, lpszClientPath, NULL, NULL, FALSE,
// 0,
// NULL, NULL, &si, &pi)
ret = CreateProcessAsUser(DupedToken, null, commandLinePath, ref sa, ref sa, false, 0, (IntPtr)0, null, ref si, out pi);
if (ret == false)
{
this.EventLog.WriteEntry("CreateProcessAsUser failed with " + Marshal.GetLastWin32Error(), EventLogEntryType.FailureAudit);
}
else
{
this.EventLog.WriteEntry("CreateProcessAsUser SUCCESS. The child PID is" + pi.dwProcessId, EventLogEntryType.SuccessAudit);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
ret = CloseHandle(DupedToken);
if (ret == false)
{
this.EventLog.WriteEntry("CloseHandle LastError: " + Marshal.GetLastWin32Error(), EventLogEntryType.Error);
}
else
{
this.EventLog.WriteEntry("CloseHandle SUCCESS", EventLogEntryType.Information);
}
}
I hope it is useful !
The function below will launch an executable as active user from a windows service.
//Function to run a process as active user from windows service
void ImpersonateActiveUserAndRun(WCHAR* path, WCHAR* args)
{
DWORD session_id = -1;
DWORD session_count = 0;
WTS_SESSION_INFOA *pSession = NULL;
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSession, &session_count))
{
//log success
}
else
{
//log error
return;
}
for (int i = 0; i < session_count; i++)
{
session_id = pSession[i].SessionId;
WTS_CONNECTSTATE_CLASS wts_connect_state = WTSDisconnected;
WTS_CONNECTSTATE_CLASS* ptr_wts_connect_state = NULL;
DWORD bytes_returned = 0;
if (::WTSQuerySessionInformation(
WTS_CURRENT_SERVER_HANDLE,
session_id,
WTSConnectState,
reinterpret_cast<LPTSTR*>(&ptr_wts_connect_state),
&bytes_returned))
{
wts_connect_state = *ptr_wts_connect_state;
::WTSFreeMemory(ptr_wts_connect_state);
if (wts_connect_state != WTSActive) continue;
}
else
{
//log error
continue;
}
HANDLE hImpersonationToken;
if (!WTSQueryUserToken(session_id, &hImpersonationToken))
{
//log error
continue;
}
//Get real token from impersonation token
DWORD neededSize1 = 0;
HANDLE *realToken = new HANDLE;
if (GetTokenInformation(hImpersonationToken, (::TOKEN_INFORMATION_CLASS) TokenLinkedToken, realToken, sizeof(HANDLE), &neededSize1))
{
CloseHandle(hImpersonationToken);
hImpersonationToken = *realToken;
}
else
{
//log error
continue;
}
HANDLE hUserToken;
if (!DuplicateTokenEx(hImpersonationToken,
//0,
//MAXIMUM_ALLOWED,
TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS | MAXIMUM_ALLOWED,
NULL,
SecurityImpersonation,
TokenPrimary,
&hUserToken))
{
//log error
continue;
}
// Get user name of this process
//LPTSTR pUserName = NULL;
WCHAR* pUserName;
DWORD user_name_len = 0;
if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session_id, WTSUserName, &pUserName, &user_name_len))
{
//log username contained in pUserName WCHAR string
}
//Free memory
if (pUserName) WTSFreeMemory(pUserName);
ImpersonateLoggedOnUser(hUserToken);
STARTUPINFOW StartupInfo;
GetStartupInfoW(&StartupInfo);
StartupInfo.cb = sizeof(STARTUPINFOW);
//StartupInfo.lpDesktop = "winsta0\\default";
PROCESS_INFORMATION processInfo;
SECURITY_ATTRIBUTES Security1;
Security1.nLength = sizeof SECURITY_ATTRIBUTES;
SECURITY_ATTRIBUTES Security2;
Security2.nLength = sizeof SECURITY_ATTRIBUTES;
void* lpEnvironment = NULL;
// Get all necessary environment variables of logged in user
// to pass them to the new process
BOOL resultEnv = CreateEnvironmentBlock(&lpEnvironment, hUserToken, FALSE);
if (!resultEnv)
{
//log error
continue;
}
WCHAR PP[1024]; //path and parameters
ZeroMemory(PP, 1024 * sizeof WCHAR);
wcscpy(PP, path);
wcscat(PP, L" ");
wcscat(PP, args);
// Start the process on behalf of the current user
BOOL result = CreateProcessAsUserW(hUserToken,
NULL,
PP,
//&Security1,
//&Security2,
NULL,
NULL,
FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,
//lpEnvironment,
NULL,
//"C:\\ProgramData\\some_dir",
NULL,
&StartupInfo,
&processInfo);
if (!result)
{
//log error
}
else
{
//log success
}
DestroyEnvironmentBlock(lpEnvironment);
CloseHandle(hImpersonationToken);
CloseHandle(hUserToken);
CloseHandle(realToken);
RevertToSelf();
}
WTSFreeMemory(pSession);
}