I'd like to use C# to determine which privileges are assigned to my process/thread token, and adjust them as necessary. For example, in order for my program to restart the computer, it must first enable the SeShutdownPrivilege privilege.
How can that be done safely from managed code?
This turns out to be non-trivial because there's no built-in mechanism for it. Not only is P/Invoke required, but you must code carefully to make sure that you don't "leak" privileges by enabling them and then not disabling them soon enough (though not an issue if you're restarting the computer).
For a complete code sample with description, read the MSDN magazine article from March 2005 "Manipulate Privileges in Managed Code Reliably, Securely, and Efficiently" by Mark Novak.
Here's the P/Invoke declarations:
using System;
using System.Runtime.InteropServices;
using System.Runtime.ConstrainedExecution;
namespace PrivilegeClass
{
[Flags]
internal enum TokenAccessLevels
{
AssignPrimary = 0x00000001,
Duplicate = 0x00000002,
Impersonate = 0x00000004,
Query = 0x00000008,
QuerySource = 0x00000010,
AdjustPrivileges = 0x00000020,
AdjustGroups = 0x00000040,
AdjustDefault = 0x00000080,
AdjustSessionId = 0x00000100,
Read = 0x00020000 | Query,
Write = 0x00020000 | AdjustPrivileges | AdjustGroups | AdjustDefault,
AllAccess = 0x000F0000 |
AssignPrimary |
Duplicate |
Impersonate |
Query |
QuerySource |
AdjustPrivileges |
AdjustGroups |
AdjustDefault |
AdjustSessionId,
MaximumAllowed = 0x02000000
}
internal enum SecurityImpersonationLevel
{
Anonymous = 0,
Identification = 1,
Impersonation = 2,
Delegation = 3,
}
internal enum TokenType
{
Primary = 1,
Impersonation = 2,
}
internal sealed class NativeMethods
{
internal const uint SE_PRIVILEGE_DISABLED = 0x00000000;
internal const uint SE_PRIVILEGE_ENABLED = 0x00000002;
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
internal struct LUID
{
internal uint LowPart;
internal uint HighPart;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
internal struct LUID_AND_ATTRIBUTES
{
internal LUID Luid;
internal uint Attributes;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
internal struct TOKEN_PRIVILEGE
{
internal uint PrivilegeCount;
internal LUID_AND_ATTRIBUTES Privilege;
}
internal const string ADVAPI32 = "advapi32.dll";
internal const string KERNEL32 = "kernel32.dll";
internal const int ERROR_SUCCESS = 0x0;
internal const int ERROR_ACCESS_DENIED = 0x5;
internal const int ERROR_NOT_ENOUGH_MEMORY = 0x8;
internal const int ERROR_NO_TOKEN = 0x3f0;
internal const int ERROR_NOT_ALL_ASSIGNED = 0x514;
internal const int ERROR_NO_SUCH_PRIVILEGE = 0x521;
internal const int ERROR_CANT_OPEN_ANONYMOUS = 0x543;
[DllImport(
KERNEL32,
SetLastError=true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
internal static extern bool CloseHandle(IntPtr handle);
[DllImport(
ADVAPI32,
CharSet=CharSet.Unicode,
SetLastError=true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
internal static extern bool AdjustTokenPrivileges (
[In] SafeTokenHandle TokenHandle,
[In] bool DisableAllPrivileges,
[In] ref TOKEN_PRIVILEGE NewState,
[In] uint BufferLength,
[In,Out] ref TOKEN_PRIVILEGE PreviousState,
[In,Out] ref uint ReturnLength);
[DllImport(
ADVAPI32,
CharSet=CharSet.Auto,
SetLastError=true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
internal static extern
bool RevertToSelf();
[DllImport(
ADVAPI32,
EntryPoint="LookupPrivilegeValueW",
CharSet=CharSet.Auto,
SetLastError=true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
internal static extern
bool LookupPrivilegeValue (
[In] string lpSystemName,
[In] string lpName,
[In,Out] ref LUID Luid);
[DllImport(
KERNEL32,
CharSet=CharSet.Auto,
SetLastError=true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
internal static extern
IntPtr GetCurrentProcess();
[DllImport(
KERNEL32,
CharSet=CharSet.Auto,
SetLastError=true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
internal static extern
IntPtr GetCurrentThread ();
[DllImport(
ADVAPI32,
CharSet=CharSet.Unicode,
SetLastError=true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
internal static extern
bool OpenProcessToken (
[In] IntPtr ProcessToken,
[In] TokenAccessLevels DesiredAccess,
[In,Out] ref SafeTokenHandle TokenHandle);
[DllImport
(ADVAPI32,
CharSet=CharSet.Unicode,
SetLastError=true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
internal static extern
bool OpenThreadToken(
[In] IntPtr ThreadToken,
[In] TokenAccessLevels DesiredAccess,
[In] bool OpenAsSelf,
[In,Out] ref SafeTokenHandle TokenHandle);
[DllImport
(ADVAPI32,
CharSet=CharSet.Unicode,
SetLastError=true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
internal static extern
bool DuplicateTokenEx(
[In] SafeTokenHandle ExistingToken,
[In] TokenAccessLevels DesiredAccess,
[In] IntPtr TokenAttributes,
[In] SecurityImpersonationLevel ImpersonationLevel,
[In] TokenType TokenType,
[In,Out] ref SafeTokenHandle NewToken);
[DllImport
(ADVAPI32,
CharSet=CharSet.Unicode,
SetLastError=true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
internal static extern
bool SetThreadToken(
[In] IntPtr Thread,
[In] SafeTokenHandle Token);
static NativeMethods()
{
}
}
}
Here's what I use. It is based off of the Mark Novak article, but with less paranoia for untrusted stack frames, CER's, or reentrance (since I assume you are not writing internet explorer or a SQL Server Add-in).
Privilege.cs:
using System;
using Microsoft.Win32.SafeHandles;
using System.Collections.Specialized;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.ConstrainedExecution;
using System.Threading;
using Luid = Esatto.Win32.Processes.NativeMethods.LUID;
using Win32Exception = System.ComponentModel.Win32Exception;
using PrivilegeNotHeldException = System.Security.AccessControl.PrivilegeNotHeldException;
using static Esatto.Win32.Processes.NativeMethods;
using System.Linq;
using System.Security.Principal;
// http://msdn.microsoft.com/en-us/magazine/cc163823.aspx
namespace Esatto.Win32.Processes
{
public static class Privilege
{
#region Privilege names
public const string
CreateToken = "SeCreateTokenPrivilege",
AssignPrimaryToken = "SeAssignPrimaryTokenPrivilege",
LockMemory = "SeLockMemoryPrivilege",
IncreaseQuota = "SeIncreaseQuotaPrivilege",
UnsolicitedInput = "SeUnsolicitedInputPrivilege",
MachineAccount = "SeMachineAccountPrivilege",
TrustedComputingBase = "SeTcbPrivilege",
Security = "SeSecurityPrivilege",
TakeOwnership = "SeTakeOwnershipPrivilege",
LoadDriver = "SeLoadDriverPrivilege",
SystemProfile = "SeSystemProfilePrivilege",
SystemTime = "SeSystemtimePrivilege",
ProfileSingleProcess = "SeProfileSingleProcessPrivilege",
IncreaseBasePriority = "SeIncreaseBasePriorityPrivilege",
CreatePageFile = "SeCreatePagefilePrivilege",
CreatePermanent = "SeCreatePermanentPrivilege",
Backup = "SeBackupPrivilege",
Restore = "SeRestorePrivilege",
Shutdown = "SeShutdownPrivilege",
Debug = "SeDebugPrivilege",
Audit = "SeAuditPrivilege",
SystemEnvironment = "SeSystemEnvironmentPrivilege",
ChangeNotify = "SeChangeNotifyPrivilege",
RemoteShutdown = "SeRemoteShutdownPrivilege",
Undock = "SeUndockPrivilege",
SyncAgent = "SeSyncAgentPrivilege",
EnableDelegation = "SeEnableDelegationPrivilege",
ManageVolume = "SeManageVolumePrivilege",
Impersonate = "SeImpersonatePrivilege",
CreateGlobal = "SeCreateGlobalPrivilege",
TrustedCredentialManagerAccess = "SeTrustedCredManAccessPrivilege",
ReserveProcessor = "SeReserveProcessorPrivilege";
#endregion
public static void RunWithPrivileges(Action action, params string[] privs)
{
if (privs == null || privs.Length == 0)
{
throw new ArgumentNullException(nameof(privs));
}
var luids = privs
.Select(e => new LUID_AND_ATTRIBUTES { Luid = GetLuidForName(e), Attributes = SE_PRIVILEGE_ENABLED })
.ToArray();
RuntimeHelpers.PrepareConstrainedRegions();
try { /* CER */ }
finally
{
using (var threadToken = new ThreadTokenScope())
using (new ThreadPrivilegeScope(threadToken, luids))
{
action();
}
}
}
private static LUID_AND_ATTRIBUTES[] AdjustTokenPrivileges2(SafeTokenHandle token, LUID_AND_ATTRIBUTES[] attrs)
{
var sizeofAttr = Marshal.SizeOf<LUID_AND_ATTRIBUTES>();
var pDesired = Marshal.AllocHGlobal(4 /* count */ + attrs.Length * sizeofAttr);
try
{
// Fill pStruct
{
Marshal.WriteInt32(pDesired, attrs.Length);
var pAttr = pDesired + 4;
for (int i = 0; i < attrs.Length; i++)
{
Marshal.StructureToPtr(attrs[i], pAttr, false);
pAttr += sizeofAttr;
}
}
// Call Adjust
const int cbPrevious = 16384 /* some arbitrarily high number */;
var pPrevious = Marshal.AllocHGlobal(cbPrevious);
try
{
if (!AdjustTokenPrivileges(token, false, pDesired, cbPrevious, pPrevious, out var retLen))
{
throw new Win32Exception();
}
// Parse result
{
var result = new LUID_AND_ATTRIBUTES[Marshal.ReadInt32(pPrevious)];
var pAttr = pPrevious + 4;
for (int i = 0; i < result.Length; i++)
{
result[i] = Marshal.PtrToStructure<LUID_AND_ATTRIBUTES>(pAttr);
}
return result;
}
}
finally { Marshal.FreeHGlobal(pPrevious); }
}
finally { Marshal.FreeHGlobal(pDesired); }
}
private static Luid GetLuidForName(string priv)
{
if (!LookupPrivilegeValue(null, priv, out var result))
{
throw new Win32Exception();
}
return result;
}
private class ThreadPrivilegeScope : IDisposable
{
private LUID_AND_ATTRIBUTES[] RevertTo;
private Thread OwnerThread;
private readonly ThreadTokenScope Token;
public ThreadPrivilegeScope(ThreadTokenScope token, LUID_AND_ATTRIBUTES[] setTo)
{
this.OwnerThread = Thread.CurrentThread;
this.Token = token ?? throw new ArgumentNullException(nameof(token));
this.RevertTo = AdjustTokenPrivileges2(token.Handle, setTo);
}
public void Dispose()
{
if (OwnerThread != Thread.CurrentThread)
{
throw new InvalidOperationException("Wrong thread");
}
if (RevertTo == null)
{
return;
}
AdjustTokenPrivileges2(Token.Handle, RevertTo);
}
}
private class ThreadTokenScope : IDisposable
{
private bool IsImpersonating;
private readonly Thread OwnerThread;
public readonly SafeTokenHandle Handle;
[ThreadStatic]
private static ThreadTokenScope Current;
public ThreadTokenScope()
{
if (Current != null)
{
throw new InvalidOperationException("Reentrance to ThreadTokenScope");
}
this.OwnerThread = Thread.CurrentThread;
if (!OpenThreadToken(GetCurrentThread(), TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, true, out var token))
{
var error = Marshal.GetLastWin32Error();
if (error != ERROR_NO_TOKEN)
{
throw new Win32Exception(error);
}
// No token is on the thread, copy from process
if (!OpenProcessToken(GetCurrentProcess(), TokenAccessLevels.Duplicate, out var processToken))
{
throw new Win32Exception();
}
if (!DuplicateTokenEx(processToken, TokenAccessLevels.Impersonate | TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges,
IntPtr.Zero, SecurityImpersonationLevel.Impersonation, TokenType.Impersonation, out token))
{
throw new Win32Exception();
}
if (!SetThreadToken(IntPtr.Zero, token))
{
throw new Win32Exception();
}
this.IsImpersonating = true;
}
this.Handle = token;
Current = this;
}
public void Dispose()
{
if (OwnerThread != Thread.CurrentThread)
{
throw new InvalidOperationException("Wrong thread");
}
if (Current != this)
{
throw new ObjectDisposedException(nameof(ThreadTokenScope));
}
Current = null;
if (IsImpersonating)
{
RevertToSelf();
}
IsImpersonating = false;
}
}
}
}
NativeMethods.cs:
using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
namespace Esatto.Win32.Processes
{
internal static class NativeMethods
{
const string Advapi32 = "advapi32.dll";
const string Kernel32 = "kernel32.dll";
const string Wtsapi32 = "wtsapi32.dll";
const string Userenv = "userenv.dll";
#region constants
public const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000;
public const uint SE_PRIVILEGE_DISABLED = 0x00000000;
public const uint SE_PRIVILEGE_ENABLED = 0x00000002;
public const int ERROR_SUCCESS = 0x0;
public const int ERROR_ACCESS_DENIED = 0x5;
public const int ERROR_NOT_ENOUGH_MEMORY = 0x8;
public const int ERROR_NO_TOKEN = 0x3f0;
public const int ERROR_NOT_ALL_ASSIGNED = 0x514;
public const int ERROR_NO_SUCH_PRIVILEGE = 0x521;
public const int ERROR_CANT_OPEN_ANONYMOUS = 0x543;
public const uint STANDARD_RIGHTS_REQUIRED = 0x000F0000;
public const uint STANDARD_RIGHTS_READ = 0x00020000;
public const uint NORMAL_PRIORITY_CLASS = 0x0020;
public const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
public const uint MAX_PATH = 260;
public const uint CREATE_NO_WINDOW = 0x08000000;
public const uint INFINITE = 0xFFFFFFFF;
#endregion
#region Advapi32
[DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public static extern bool AdjustTokenPrivileges(SafeTokenHandle TokenHandle, bool DisableAllPrivileges,
IntPtr NewState, uint BufferLength, IntPtr PreviousState, out uint ReturnLength);
[DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public static extern bool RevertToSelf();
[DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID Luid);
[DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public static extern bool OpenProcessToken(IntPtr ProcessToken, TokenAccessLevels DesiredAccess, out SafeTokenHandle TokenHandle);
[DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public static extern bool OpenThreadToken(IntPtr ThreadToken, TokenAccessLevels DesiredAccess, bool OpenAsSelf, out SafeTokenHandle TokenHandle);
[DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public static extern bool DuplicateTokenEx(SafeTokenHandle ExistingToken, TokenAccessLevels DesiredAccess,
IntPtr TokenAttributes, SecurityImpersonationLevel ImpersonationLevel, TokenType TokenType, out SafeTokenHandle NewToken);
[DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public static extern bool SetThreadToken(IntPtr Thread, SafeTokenHandle Token);
[DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CreateProcessAsUser(SafeTokenHandle hToken,
StringBuilder appExeName, StringBuilder commandLine, IntPtr processAttributes,
IntPtr threadAttributes, bool inheritHandles, uint dwCreationFlags,
EnvironmentBlockSafeHandle environment, string currentDirectory, ref STARTUPINFO startupInfo,
out PROCESS_INFORMATION startupInformation);
[DllImport(Advapi32, CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetTokenInformation(IntPtr TokenHandle,
TokenInformationClass TokenInformationClass, out int TokenInformation,
uint TokenInformationLength, out uint ReturnLength);
#endregion
#region Kernel32
[DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public static extern bool CloseHandle(IntPtr handle);
[DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public static extern IntPtr GetCurrentProcess();
[DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public static extern IntPtr GetCurrentThread();
[DllImport(Kernel32, CharSet = CharSet.Auto, SetLastError = true)]
public static extern SafeJobHandle CreateJobObject(IntPtr lpJobAttributes, string lpName);
[DllImport(Kernel32, SetLastError = true)]
public static extern bool SetInformationJobObject(SafeJobHandle hJob, JobObjectInfoType infoType,
ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION lpJobObjectInfo, int cbJobObjectInfoLength);
[DllImport(Kernel32, SetLastError = true)]
public static extern bool AssignProcessToJobObject(SafeJobHandle job, IntPtr process);
#endregion
#region Wtsapi
[DllImport(Wtsapi32, ExactSpelling = true, SetLastError = true)]
public static extern bool WTSQueryUserToken(int sessionid, out SafeTokenHandle handle);
#endregion
#region Userenv
[DllImport(Userenv, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CreateEnvironmentBlock(out EnvironmentBlockSafeHandle lpEnvironment, SafeTokenHandle hToken, bool bInherit);
[DllImport(Userenv, ExactSpelling = true, SetLastError = true)]
public extern static bool DestroyEnvironmentBlock(IntPtr hEnvironment);
#endregion
#region Structs
[StructLayout(LayoutKind.Sequential)]
public struct IO_COUNTERS
{
public ulong ReadOperationCount;
public ulong WriteOperationCount;
public ulong OtherOperationCount;
public ulong ReadTransferCount;
public ulong WriteTransferCount;
public ulong OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public long PerProcessUserTimeLimit;
public long PerJobUserTimeLimit;
public uint LimitFlags;
public UIntPtr MinimumWorkingSetSize;
public UIntPtr MaximumWorkingSetSize;
public uint ActiveProcessLimit;
public UIntPtr Affinity;
public uint PriorityClass;
public uint SchedulingClass;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public uint nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UIntPtr ProcessMemoryLimit;
public UIntPtr JobMemoryLimit;
public UIntPtr PeakProcessMemoryUsed;
public UIntPtr PeakJobMemoryUsed;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public uint LowPart;
public uint HighPart;
}
[StructLayout(LayoutKind.Sequential)]
public struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
public uint Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_PRIVILEGE
{
public uint PrivilegeCount;
public LUID_AND_ATTRIBUTES Privilege;
}
#endregion
#region Enums
public enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}
public enum SecurityImpersonationLevel
{
Anonymous = 0,
Identification = 1,
Impersonation = 2,
Delegation = 3,
}
public enum TokenType
{
Primary = 1,
Impersonation = 2,
}
public enum TokenInformationClass
{
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,
TokenIsAppContainer,
TokenCapabilities,
TokenAppContainerSid,
TokenAppContainerNumber,
TokenUserClaimAttributes,
TokenDeviceClaimAttributes,
TokenRestrictedUserClaimAttributes,
TokenRestrictedDeviceClaimAttributes,
TokenDeviceGroups,
TokenRestrictedDeviceGroups,
// MaxTokenInfoClass should always be the last enum
MaxTokenInfoClass
}
#endregion
#region SafeHandles
public sealed class EnvironmentBlockSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public EnvironmentBlockSafeHandle()
: base(true)
{
}
protected override bool ReleaseHandle()
{
return DestroyEnvironmentBlock(handle);
}
}
public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeTokenHandle()
: base(true)
{
}
override protected bool ReleaseHandle()
{
return CloseHandle(handle);
}
}
internal sealed class SafeJobHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeJobHandle()
: base(true)
{
}
protected override bool ReleaseHandle()
{
return CloseHandle(this.handle);
}
}
#endregion
}
}
Example Usage to create a process in another user's session:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Esatto.Win32.Processes
{
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;
using static NativeMethods;
#if ESATTO_WIN32
public
#else
internal
#endif
static class ProcessInterop
{
public static void CreateProcessForSession(int sessionId, string exePath, string commandLine)
{
var privs = new[] { Privilege.TrustedComputingBase, Privilege.AssignPrimaryToken, Privilege.IncreaseQuota };
Privilege.RunWithPrivileges(() => CreateProcessForSessionInternal(sessionId, exePath, commandLine), privs);
}
private static void CreateProcessForSessionInternal(int sessionId, string exePath, string commandLine)
{
SafeTokenHandle hDupToken;
{
SafeTokenHandle hToken;
if (!WTSQueryUserToken(sessionId, out hToken))
{
throw new Win32Exception();
}
using (hToken)
{
if (!DuplicateTokenEx(hToken, TokenAccessLevels.AllAccess, IntPtr.Zero, SecurityImpersonationLevel.Impersonation, TokenType.Primary, out hDupToken))
{
throw new Win32Exception();
}
}
}
using (hDupToken)
{
EnvironmentBlockSafeHandle env;
if (!CreateEnvironmentBlock(out env, hDupToken, false))
{
throw new Win32Exception();
}
using (env)
{
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
PROCESS_INFORMATION procInfo;
if (!CreateProcessAsUser(hDupToken, new StringBuilder(exePath), new StringBuilder(commandLine),
IntPtr.Zero, IntPtr.Zero, false, NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, env,
null, ref si, out procInfo))
{
throw new Win32Exception();
}
CloseHandle(procInfo.hProcess);
CloseHandle(procInfo.hThread);
}
}
}
public static int GetSessionId(this WindowsIdentity ident)
{
if (ident == null)
{
throw new ArgumentNullException();
}
int sessionId;
uint unused;
if (!GetTokenInformation(ident.Token, TokenInformationClass.TokenSessionId, out sessionId, 4, out unused))
{
throw new Win32Exception();
}
// since we are not passing a SafeHandle, we need to keep our reference on the SafeHandle
// contained in the WindowsIdentity
GC.KeepAlive(ident);
return sessionId;
}
}
}
Related
I'm trying to get the list of groups and privileges in a process access token and some info about them (name, flags and description).
To do this I'm using the GetTokenInfomation function and the TOKEN_GROUPS_AND_PRIVILEGES structure.
These are the structure definitions:
TOKEN_GROUPS_AND_PRIVILEGES
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_GROUPS_AND_PRIVILEGES
{
public uint SidCount;
public uint SidLength;
public IntPtr Sids;
public uint RestrictedSidCount;
public uint RestrictedSidLength;
public IntPtr RestrictedSids;
public uint PrivilegeCount;
public uint PrivilegeLength;
public IntPtr Privileges;
public LUID AuthenticationID;
}
LUID_AND_ATTRIBUTES
[StructLayout(LayoutKind.Sequential)]
public struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
public uint Attributes;
}
LUID
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public uint LowPart;
public int HighPart;
}
I can read the groups SIDs and get info on them fine but when I try to read the privileges I always get an Access violation.
This is the code that I use to read the privileges:
for (int i = 0; i < GroupsAndPrivilegesInfo.PrivilegeCount; i++)
{
Privilege = (Win32Structures.LUID_AND_ATTRIBUTES)Marshal.PtrToStructure(GroupsAndPrivilegesInfo.Privileges, typeof(Win32Structures.LUID_AND_ATTRIBUTES));
PrivilegeName = GetPrivilegeName(Privilege.Luid) ?? "Non disponibile";
PrivilegeStatus = GetPrivilegeFlags((Win32Enumerations.PrivilegeLUIDAttribute)Privilege.Attributes) ?? "Non disponibile";
PrivilegeDescription = GetPrivilegeDescription(PrivilegeName) ?? "Non disponibile";
Privileges.Add(new TokenPrivilegeInfo(PrivilegeName, PrivilegeStatus, PrivilegeDescription));
GroupsAndPrivilegesInfo.Privileges += Marshal.SizeOf(typeof(Win32Structures.LUID_AND_ATTRIBUTES));
}
The exception occurs when calling the GetPrivilegeName method which uses the function LookupPrivilegeNameW defined as follows:
[DllImport("Advapi32.dll", EntryPoint = "LookupPrivilegeNameW", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool LookupPrivilegeName(string SystemName, Win32Structures.LUID LUID, StringBuilder PrivilegeName, ref uint NameLength);
I tried different methods to read the data such as:
Manually calculating the offset to the privileges array element from the start of the structure
Declare the privileges field as an array and set a SizeConst to an obviously oversized value
Even those methods threw an Access violation exception.
I don't undestand what is the problem given that I use the same code to read the SIDs and it works normally.
First, The second parameter of LookupPrivilegeNameW is A pointer to a LUID. You need to declare it as a reference type like ref Win32Structures.LUID LUID.
And then the TOKEN_GROUPS_AND_PRIVILEGES.Privileges is an array of the privileges.
Your code does not retrieve all Privileges, but repeatedly reads the first Privilege every time.
Here is the working sample:
namespace ConsoleApp2
{
class Program
{
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_GROUPS_AND_PRIVILEGES
{
public uint SidCount;
public uint SidLength;
public IntPtr Sids;
public uint RestrictedSidCount;
public uint RestrictedSidLength;
public IntPtr RestrictedSids;
public uint PrivilegeCount;
public uint PrivilegeLength;
public IntPtr Privileges;
public LUID AuthenticationID;
}
[StructLayout(LayoutKind.Sequential)]
public struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
public uint Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public uint LowPart;
public int HighPart;
}
public enum TOKEN_INFORMATION_CLASS
{
TokenUser = 1,
TokenGroups,
TokenPrivileges,
TokenOwner,
TokenPrimaryGroup,
TokenDefaultDacl,
TokenSource,
TokenType,
TokenImpersonationLevel,
TokenStatistics,
TokenRestrictedSids,
TokenSessionId,
TokenGroupsAndPrivileges,
TokenSessionReference,
TokenSandBoxInert,
TokenAuditPolicy,
TokenOrigin
}
[DllImport("Advapi32.dll", EntryPoint = "LookupPrivilegeNameW", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool LookupPrivilegeName(string SystemName, ref LUID LUID, StringBuilder PrivilegeName, ref uint NameLength);
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool OpenProcessToken(IntPtr ProcessHandle,
UInt32 DesiredAccess, out IntPtr TokenHandle);
[DllImport("kernel32.dll")]
public static extern IntPtr GetCurrentProcess();
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool GetTokenInformation(IntPtr TokenHandle,
TOKEN_INFORMATION_CLASS TokenInformationClass,
IntPtr TokenInformation,
Int32 TokenInformationLength,
out Int32 ReturnLength);
public static UInt32 TOKEN_QUERY = 0x0008;
static void Main(string[] args)
{
bool ret = false;
IntPtr token;
ret = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, out token);
Int32 ReturnLength;
ret = GetTokenInformation(token, TOKEN_INFORMATION_CLASS.TokenGroupsAndPrivileges, IntPtr.Zero, 0, out ReturnLength);
IntPtr pGroupsAndPrivilegesInfo = Marshal.AllocHGlobal(ReturnLength);
ret = GetTokenInformation(token, TOKEN_INFORMATION_CLASS.TokenGroupsAndPrivileges, pGroupsAndPrivilegesInfo, ReturnLength, out ReturnLength);
TOKEN_GROUPS_AND_PRIVILEGES GroupsAndPrivilegesInfo = (TOKEN_GROUPS_AND_PRIVILEGES)Marshal.PtrToStructure(pGroupsAndPrivilegesInfo, typeof(TOKEN_GROUPS_AND_PRIVILEGES));
for (int i = 0; i < GroupsAndPrivilegesInfo.PrivilegeCount; i++)
{
//IntPtr ptr = GroupsAndPrivilegesInfo.Privileges + i * Marshal.SizeOf(typeof(LUID_AND_ATTRIBUTES));
LUID_AND_ATTRIBUTES Privilege = (LUID_AND_ATTRIBUTES)Marshal.PtrToStructure(GroupsAndPrivilegesInfo.Privileges + i * Marshal.SizeOf(typeof(LUID_AND_ATTRIBUTES)), typeof(LUID_AND_ATTRIBUTES));
StringBuilder name = new StringBuilder(50);
uint cchName = 50;
ret = LookupPrivilegeName(null, ref Privilege.Luid, name, ref cchName);
Console.WriteLine(name);
}
Marshal.FreeHGlobal(pGroupsAndPrivilegesInfo);
}
}
}
Process32FirstW is suppose to return true when it finds process and return the first process in a PROCESSENTRY32W structure but it doesnt, so i cant enumerate it in Process32Next sadly. What is the reason it doesnt return true as a bool function? [([([( I have changed the struct deceleration )])])])]
[StructLayout(LayoutKind.Sequential)]
public struct PROCESSENTRY32W
{
public uint dwSize;
public uint cntUsage;
public uint th32ProcessID;
public IntPtr th32DefaultHeapID;
public uint th32ModuleID;
public uint cntThreads;
public uint th32ParentProcessID;
public int pcPriClassBase;
public uint dwFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szExeFile;
};
[DllImport("kernel32.dll", EntryPoint = "CreateToolhelp32Snapshot")]
public static extern IntPtr CreateToolhelp32SnapshotRtlMoveMemory(UInt32 dwFlagsdes, UInt32 th32ProcessID);
[DllImport("kernel32", EntryPoint = "Process32FirstW", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool Process32FirstW(IntPtr hSnapshot, IntPtr lppe);
[DllImport("kernel32", EntryPoint = "Process32Next")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool Process32Next(IntPtr hSnapshot, IntPtr lppe);
[DllImport("kernel32", EntryPoint = "CloseHandle")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr handle);
public static Dictionary<int, Process> get_process_list()
{
//Dictionary<string, Process> returnable_processes_ = new Dictionary<string, Process>();
Dictionary<int, Process> returnable_processes_ = new Dictionary<int, Process>();
IntPtr HANLDE_Processes = CreateToolhelp32SnapshotRtlMoveMemory(2,0);
PROCESSENTRY32W p32Iw = new PROCESSENTRY32W();
p32Iw.dwSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(PROCESSENTRY32W));
IntPtr p32IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(p32Iw));
Marshal.StructureToPtr(p32Iw, p32IntPtr, false);
bool blFirstProcess = Process32FirstW(HANLDE_Processes, p32IntPtr); // returns false no matter what?
int x = Marshal.GetLastWin32Error();
if(blFirstProcess)
{
do
{
Marshal.PtrToStructure(p32IntPtr, p32Iw);
int PID = (int) p32Iw.th32ProcessID;
returnable_processes_.Add(PID, new Process());
}
while(Process32Next(HANLDE_Processes, p32IntPtr));
}
Marshal.FreeHGlobal(p32IntPtr);
return returnable_processes_;
}
It's really a charset problem. You use the ***W version of api. The actual type of PROCESSENTRY32W.szExeFile is wchar_t, which is 2 bytes, so the structure size you passed is 260 bytes shorter(you can simply try p32Iw.dwSize + 260, Process32FirstW will return ture, if we are regardless of the subsequent execution).
Fix the struct declaration and run the "C/C++ Programming" C# code.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct PROCESSENTRY32
{
public uint dwSize;
public uint cntUsage;
public uint th32ProcessID;
public IntPtr th32DefaultHeapID;
public uint th32ModuleID;
public uint cntThreads;
public uint th32ParentProcessID;
public int pcPriClassBase;
public uint dwFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=260)] public string szExeFile;
};
But according to the document, Marshal.StructureToPtr and Marshal.PtrToStructure are out of date and we should avoid using it. And this way of passing values through pointers is usually replaced by references in C#.
First, declare charset and reference parameters for Process32First and Process32Next
[DllImport("kernel32", EntryPoint = "Process32First", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool Process32First(IntPtr hSnapshot, ref PROCESSENTRY32W lppe);
[DllImport("kernel32", EntryPoint = "Process32Next", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool Process32Next(IntPtr hSnapshot, ref PROCESSENTRY32W lppe);
Then, Pass the ref type of PROCESSENTRY32W:
bool blFirstProcess = Process32First(HANLDE_Processes, ref p32Iw); // returns false no matter what?
if (blFirstProcess)
{
do
{
//Marshal.PtrToStructure(p32IntPtr, p32Iw);
int PID = (int)p32Iw.th32ProcessID;
returnable_processes_.Add(PID, new Process());
Console.WriteLine(p32Iw.szExeFile);
}
while (Process32Next(HANLDE_Processes, ref p32Iw));
}
Summarize, a minimal, compilable example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace ConsoleApp
{
class Program
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct PROCESSENTRY32W
{
public uint dwSize;
public uint cntUsage;
public uint th32ProcessID;
public IntPtr th32DefaultHeapID;
public uint th32ModuleID;
public uint cntThreads;
public uint th32ParentProcessID;
public int pcPriClassBase;
public uint dwFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szExeFile;
};
[DllImport("kernel32.dll", EntryPoint = "CreateToolhelp32Snapshot")]
public static extern IntPtr CreateToolhelp32SnapshotRtlMoveMemory(UInt32 dwFlagsdes, UInt32 th32ProcessID);
[DllImport("kernel32", EntryPoint = "Process32First", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool Process32First(IntPtr hSnapshot, ref PROCESSENTRY32W lppe);
[DllImport("kernel32", EntryPoint = "Process32Next", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool Process32Next(IntPtr hSnapshot, ref PROCESSENTRY32W lppe);
[DllImport("kernel32", EntryPoint = "CloseHandle")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr handle);
public static Dictionary<int, Process> get_process_list()
{
//Dictionary<string, Process> returnable_processes_ = new Dictionary<string, Process>();
Dictionary<int, Process> returnable_processes_ = new Dictionary<int, Process>();
IntPtr HANLDE_Processes = CreateToolhelp32SnapshotRtlMoveMemory(2, 0);
PROCESSENTRY32W p32Iw = new PROCESSENTRY32W();
int size = System.Runtime.InteropServices.Marshal.SizeOf(typeof(PROCESSENTRY32W));
p32Iw.dwSize = Convert.ToUInt32(size);
//IntPtr p32IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(p32Iw));
//Marshal.StructureToPtr(p32Iw, p32IntPtr, false);
bool blFirstProcess = Process32First(HANLDE_Processes, ref p32Iw); // returns false no matter what?
int x = Marshal.GetLastWin32Error();
if (blFirstProcess)
{
do
{
int PID = (int)p32Iw.th32ProcessID;
returnable_processes_.Add(PID, new Process());
Console.WriteLine(p32Iw.szExeFile);
}
while (Process32Next(HANLDE_Processes, ref p32Iw));
}
return returnable_processes_;
}
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
get_process_list();
Console.ReadKey();
}
}
}
The first Call to Launch Method Write Into file echo1, but I can't capture the standard output of the second line of the test, what I am doing wrong?
[TestMethod]
public void Launch()
{
var resp = ProcessAsUserTest.ProcessAsUser.Launch("cmd.exe /c \"echo 1\" >> c:\\temp\\echo1");
var resp1 = ProcessAsUserTest.ProcessAsUser.Launch("cmd.exe /c \"echo 1\"");
Assert.AreEqual("1", resp1);
}
I was follow this thread but It has not answer: Capture standard output from CreateProcessAsUser in C#
I get the implementation from https://social.msdn.microsoft.com/Forums/vstudio/en-US/0c0ca087-5e7b-4046-93cb-c7b3e48d0dfb/how-run-client-application-as-a-windows-service-in-c?forum=csharpgeneral
and I've edited the code to get standard output same as the first link without answer.
Here the implementation:
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace ProcessAsUserTest
{
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SECURITY_ATTRIBUTES
{
public uint nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
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 short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
internal enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
internal enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
[Flags]
enum HANDLE_FLAGS
{
INHERIT = 1,
}
public class ProcessAsUser
{
[DllImport("advapi32.dll", SetLastError = true)]
private 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.dll", EntryPoint = "DuplicateTokenEx", SetLastError = true)]
private static extern bool DuplicateTokenEx(
IntPtr hExistingToken,
uint dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
Int32 ImpersonationLevel,
Int32 dwTokenType,
ref IntPtr phNewToken);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(
IntPtr ProcessHandle,
UInt32 DesiredAccess,
ref IntPtr TokenHandle);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool CreateEnvironmentBlock(
ref IntPtr lpEnvironment,
IntPtr hToken,
bool bInherit);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool DestroyEnvironmentBlock(
IntPtr lpEnvironment);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(
IntPtr hObject);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CreateNamedPipe(string name, int openMode, int pipeMode, int maxInstances, int outBufSize, int inBufSize, int timeout, IntPtr lpPipeAttributes);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, HandleRef hTemplateFile);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
private static extern IntPtr GetStdHandle(int whichHandle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetExitCodeProcess(IntPtr process, ref UInt32 exitCode);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetHandleInformation(IntPtr hObject, HANDLE_FLAGS dwMask, HANDLE_FLAGS dwFlags);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetConsoleOutputCP();
[DllImport("kernel32.dll")]
static extern int WaitForSingleObject(IntPtr hHandle, int dwMilliseconds);
private const short SW_SHOW = 5;
private const short SW_HIDE = 0;
private const uint TOKEN_QUERY = 0x0008;
private const uint TOKEN_DUPLICATE = 0x0002;
private const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
private const int GENERIC_ALL_ACCESS = 0x10000000;
private const int STARTF_USESHOWWINDOW = 0x00000001;
private const int STARTF_FORCEONFEEDBACK = 0x00000040;
private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
private const int STD_INPUT_HANDLE = -10;
private static readonly IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1);
private static readonly HandleRef NullHandleRef = new HandleRef(null, IntPtr.Zero);
private static string LaunchProcessAsUser(string cmdLine, IntPtr token, IntPtr envBlock)
{
bool result = false;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
SECURITY_ATTRIBUTES saProcess = new SECURITY_ATTRIBUTES();
SECURITY_ATTRIBUTES saThread = new SECURITY_ATTRIBUTES();
saProcess.nLength = (uint)Marshal.SizeOf(saProcess);
saThread.nLength = (uint)Marshal.SizeOf(saThread);
IntPtr stdoutReadHandle = IntPtr.Zero;
SafeFileHandle safeHandle = null;
IntPtr stdoutWriteHandle = IntPtr.Zero;
IntPtr stdinHandle = GetStdHandle(STD_INPUT_HANDLE);
try
{
CreatePipe(out stdoutReadHandle, out stdoutWriteHandle, false);
SetHandleInformation(stdoutReadHandle, HANDLE_FLAGS.INHERIT, 0);
STARTUPINFO si = new STARTUPINFO();
si.cb = (uint)Marshal.SizeOf(si);
//if this member is NULL, the new process inherits the desktop
//and window station of its parent process. If this member is
//an empty string, the process does not inherit the desktop and
//window station of its parent process; instead, the system
//determines if a new desktop and window station need to be created.
//If the impersonated user already has a desktop, the system uses the
//existing desktop.
si.lpDesktop = #"WinSta0\Default"; //Modify as needed
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK;
si.wShowWindow = SW_SHOW;
si.hStdInput = stdinHandle;
si.hStdOutput = stdoutWriteHandle;
si.hStdOutput = stdoutWriteHandle;
//Set other si properties as required.
result = CreateProcessAsUser(
token,
null,
cmdLine,
ref saProcess,
ref saThread,
false,
CREATE_UNICODE_ENVIRONMENT,
envBlock,
null,
ref si,
out pi);
if (result == false)
{
int error = Marshal.GetLastWin32Error();
string message = String.Format("CreateProcessAsUser Error: {0}", error);
Debug.WriteLine(message);
}
var ret = WaitForSingleObject(pi.hProcess, 100000);
//Console.Write("WaitForSingleObject returned " + ret);
//ret==258 (0x102) - not signalled, ret==0 ok!
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(stdoutWriteHandle);
CloseHandle(stdinHandle);
safeHandle = new SafeFileHandle(stdoutReadHandle, true);
string outputData;
var encoding = Encoding.GetEncoding(GetConsoleOutputCP());
using (var fs = new FileStream(safeHandle, FileAccess.Read, 0x1000, true))
using (var reader = new StreamReader(fs, encoding))
{
outputData = reader.ReadToEnd();
}
return outputData;
}
finally
{
if (!safeHandle.IsClosed)
{
safeHandle.Close();
}
}
}
private static IntPtr GetPrimaryToken(int processId)
{
IntPtr token = IntPtr.Zero;
IntPtr primaryToken = IntPtr.Zero;
bool retVal = false;
Process p = null;
try
{
p = Process.GetProcessById(processId);
}
catch (ArgumentException ex)
{
string details = String.Format("ProcessID {0} Not Available, More: {1}", processId, ex.Message);
throw new ArgumentException(details);
}
//Gets impersonation token
retVal = OpenProcessToken(p.Handle, TOKEN_DUPLICATE, ref token);
if (retVal == true)
{
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.nLength = (uint)Marshal.SizeOf(sa);
//Convert the impersonation token into Primary token
retVal = DuplicateTokenEx(
token,
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY,
ref sa,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)TOKEN_TYPE.TokenPrimary,
ref primaryToken);
//Close the Token that was previously opened.
CloseHandle(token);
if (retVal == false)
{
string message = String.Format("DuplicateTokenEx Error: {0}", Marshal.GetLastWin32Error());
throw new Exception(message);
}
}
else
{
string message = String.Format("OpenProcessToken Error: {0}", Marshal.GetLastWin32Error());
Debug.WriteLine(message);
}
//We'll Close this token after it is used.
return primaryToken;
}
private static IntPtr GetEnvironmentBlock(IntPtr token)
{
IntPtr envBlock = IntPtr.Zero;
bool retVal = CreateEnvironmentBlock(ref envBlock, token, false);
if (retVal == false)
{
//Environment Block, things like common paths to My Documents etc.
//Will not be created if "false"
//It should not adversley affect CreateProcessAsUser.
string message = String.Format("CreateEnvironmentBlock Error: {0}", Marshal.GetLastWin32Error());
Debug.WriteLine(message);
}
return envBlock;
}
public static string Launch(string appCmdLine /*,int processId*/)
{
string ret = "";
//Either specify the processID explicitly
//Or try to get it from a process owned by the user.
//In this case assuming there is only one explorer.exe
Process[] ps = Process.GetProcessesByName("explorer");
int processId = -1;//=processId
if (ps.Length > 0)
{
processId = ps[0].Id;
}
if (processId > 1)
{
IntPtr token = GetPrimaryToken(processId);
if (token != IntPtr.Zero)
{
IntPtr envBlock = GetEnvironmentBlock(token);
ret = LaunchProcessAsUser(appCmdLine, token, envBlock);
if (envBlock != IntPtr.Zero)
DestroyEnvironmentBlock(envBlock);
CloseHandle(token);
}
}
return ret;
}
private static void CreatePipe(out IntPtr parentHandle, out IntPtr childHandle, bool parentInputs)
{
string pipename = #"\\.\pipe\" + Guid.NewGuid().ToString();
parentHandle = CreateNamedPipe(pipename, 0x40000003, 0, 0xff, 0x1000, 0x1000, 0, IntPtr.Zero);
if (parentHandle == INVALID_HANDLE_VALUE)
{
throw new Exception("Invalid Handle Exception.");
}
int childAcc = 0x40000000;
if (parentInputs)
{
childAcc = -2147483648;
}
childHandle = CreateFile(pipename, childAcc, 3, IntPtr.Zero, 3, 0x40000080, NullHandleRef);
if (childHandle == INVALID_HANDLE_VALUE)
{
throw new Exception("Invalid Handle Exception.");
}
}
}
}
NOTE: It is a sample code only, In real code I want to take a screenshot of the desktop from Window Service using another process, In the context of explorer.exe, because windows service cannot access to desktop directly and need another context to do it.
Same thread:https://social.msdn.microsoft.com/Forums/vstudio/en-US/19ff0bb0-924f-4f9c-9b71-ff4ee4ccaf50/standard-output-from-createprocessasuser-always-empty-in-c?forum=csharpgeneral#19ff0bb0-924f-4f9c-9b71-ff4ee4ccaf50
If you need more information please tell me, thanks!
I'm not the most familiar with the unmanaged cryptography library in the Windows API, but alas I am trying to generate a self-signed X509Certificate2 certificate.
Here is the complete code:
using System;
using System.Security;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Security.Cryptography.X509Certificates;
namespace Example
{
[StructLayout(LayoutKind.Sequential)]
public struct SystemTime
{
public short Year;
public short Month;
public short DayOfWeek;
public short Day;
public short Hour;
public short Minute;
public short Second;
public short Milliseconds;
}
public static class MarshalHelper
{
public static void ErrorCheck(bool nativeCallSucceeded)
{
if (!nativeCallSucceeded)
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
}
public static class DateTimeExtensions
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FileTimeToSystemTime(ref long fileTime, out SystemTime systemTime);
public static SystemTime ToSystemTime(this DateTime dateTime)
{
long fileTime = dateTime.ToFileTime();
SystemTime systemTime;
MarshalHelper.ErrorCheck(FileTimeToSystemTime(ref fileTime, out systemTime));
return systemTime;
}
}
class X509Certificate2Helper
{
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern bool CryptAcquireContextW(out IntPtr providerContext, string container, string provider, int providerType, int flags);
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool CryptReleaseContext(IntPtr providerContext, int flags);
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool CryptGenKey(IntPtr providerContext, int algorithmId, int flags, out IntPtr cryptKeyHandle);
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool CryptDestroyKey(IntPtr cryptKeyHandle);
[DllImport("crypt32.dll", SetLastError = true)]
static extern bool CertStrToNameW(int certificateEncodingType, IntPtr x500, int strType, IntPtr reserved, byte[] encoded, ref int encodedLength, out IntPtr errorString);
[DllImport("crypt32.dll", SetLastError = true)]
static extern IntPtr CertCreateSelfSignCertificate(IntPtr providerHandle, ref CryptoApiBlob subjectIssuerBlob, int flags, ref CryptKeyProviderInformation keyProviderInformation, IntPtr signatureAlgorithm, ref SystemTime startTime, ref SystemTime endTime, IntPtr extensions);
[DllImport("crypt32.dll", SetLastError = true)]
static extern bool CertFreeCertificateContext(IntPtr certificateContext);
[DllImport("crypt32.dll", SetLastError = true)]
static extern bool CertSetCertificateContextProperty(IntPtr certificateContext, int propertyId, int flags, ref CryptKeyProviderInformation data);
public static X509Certificate2 GenerateSelfSignedCertificate(String name = "CN = Example", DateTime? startTime = null, DateTime? endTime = null)
{
if (name == null)
name = String.Empty;
var startSystemTime = default(SystemTime);
if (startTime == null || (DateTime)startTime < DateTime.FromFileTimeUtc(0))
startTime = DateTime.FromFileTimeUtc(0);
var startSystemTime = ((DateTime)startTime).ToSystemTime();
if (endTime == null)
endTime = DateTime.MaxValue;
var endSystemTime = ((DateTime)endTime).ToSystemTime();
string containerName = Guid.NewGuid().ToString();
GCHandle dataHandle = new GCHandle();
IntPtr providerContext = IntPtr.Zero;
IntPtr cryptKey = IntPtr.Zero;
IntPtr certificateContext = IntPtr.Zero;
IntPtr algorithmPointer = IntPtr.Zero;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
MarshalHelper.ErrorCheck(CryptAcquireContextW(out providerContext, containerName, null, 1, 0x8));
MarshalHelper.ErrorCheck(CryptGenKey(providerContext, 1, 0x8000001, out cryptKey));
IntPtr errorStringPtr;
int nameDataLength = 0;
byte[] nameData;
dataHandle = GCHandle.Alloc(name, GCHandleType.Pinned);
if (!CertStrToNameW(0x00010001, dataHandle.AddrOfPinnedObject(), 3, IntPtr.Zero, null, ref nameDataLength, out errorStringPtr))
{
string error = Marshal.PtrToStringUni(errorStringPtr);
throw new ArgumentException(error);
}
nameData = new byte[nameDataLength];
if (!CertStrToNameW(0x00010001, dataHandle.AddrOfPinnedObject(), 3, IntPtr.Zero, nameData, ref nameDataLength, out errorStringPtr))
{
string error = Marshal.PtrToStringUni(errorStringPtr);
throw new ArgumentException(error);
}
dataHandle.Free();
dataHandle = GCHandle.Alloc(nameData, GCHandleType.Pinned);
CryptoApiBlob nameBlob = new CryptoApiBlob { cbData = nameData.Length, pbData = dataHandle.AddrOfPinnedObject() };
dataHandle.Free();
CryptKeyProviderInformation keyProvider = new CryptKeyProviderInformation { pwszContainerName = containerName, dwProvType = 1, dwKeySpec = 1 };
CryptAlgorithmIdentifier algorithm = new CryptAlgorithmIdentifier { pszObjId = "1.2.840.113549.1.1.13", Parameters = new CryptoApiBlob() };
algorithmPointer = Marshal.AllocHGlobal(Marshal.SizeOf(algorithm));
Marshal.StructureToPtr(algorithm, algorithmPointer, false);
certificateContext = CertCreateSelfSignCertificate(providerContext, ref nameBlob, 0, ref keyProvider, algorithmPointer, ref startSystemTime, ref endSystemTime, IntPtr.Zero);
MarshalHelper.ErrorCheck(certificateContext != IntPtr.Zero);
return new X509Certificate2(certificateContext);
}
finally
{
if (dataHandle.IsAllocated)
dataHandle.Free();
if (certificateContext != IntPtr.Zero)
CertFreeCertificateContext(certificateContext);
if (cryptKey != IntPtr.Zero)
CryptDestroyKey(cryptKey);
if (providerContext != IntPtr.Zero)
CryptReleaseContext(providerContext, 0);
if (algorithmPointer != IntPtr.Zero)
{
Marshal.DestroyStructure(algorithmPointer, typeof(CryptAlgorithmIdentifier));
Marshal.FreeHGlobal(algorithmPointer);
}
}
}
struct CryptoApiBlob
{
public Int32 cbData;
public IntPtr pbData;
}
struct CryptAlgorithmIdentifier {
public String pszObjId;
public CryptoApiBlob Parameters;
}
struct CryptKeyProviderInformation
{
public String pwszContainerName;
public String pwszProvName;
public Int32 dwProvType;
public Int32 dwFlags;
public Int32 cProvParam;
public IntPtr rgProvParam;
public Int32 dwKeySpec;
}
}
}
Here is how you can generate a new X509Certificate2 using it:
var certificate = X509Certificate2Helper.GenerateSelfSignedCertificate();
However, you can see that trying to get the private key through certificate.PrivateKey throws Keyset does not exist. I've tried to consult the documentation but I couldn't figure out why the certificate context doesn't have its private key set when its loaded as an X509Certificate2. Does anyone have any ideas? Are there problems with the implementation that cause the key to not be set? I mean, I'm a little bit confused here because I would expect a self-signed certificate to always carry its private key since its signed itself using it, or is this not the case?
The problem is with CryptKeyProviderInformation structure signature. It is missing CharSet (with either, Auto or Unicode) attribute, because container and provider names are expected to be unicode (after marshalling). Update structure definition as follows:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct CryptKeyProviderInformation
{
public String pwszContainerName;
public String pwszProvName;
public Int32 dwProvType;
public Int32 dwFlags;
public Int32 cProvParam;
public IntPtr rgProvParam;
public Int32 dwKeySpec;
}
and the key should be accessbile after that.
I wanted to run an GUI application via a service application in C#, so i have tried with System.Diagnostics.Process.Start() method as below:
if (KillTask("notepad") == false)
{
//ProcessStartInfo _ProcessStartInfo = new ProcessStartInfo(#"C:\WINDOWS\system32\notepad.exe");
//_ProcessStartInfo.UseShellExecute = false;
//_ProcessStartInfo.RedirectStandardError = true;
//_ProcessStartInfo.RedirectStandardInput = true;
//_ProcessStartInfo.RedirectStandardOutput = true;
//_ProcessStartInfo.CreateNoWindow = true;
//_ProcessStartInfo.ErrorDialog = false;
//_ProcessStartInfo.WindowStyle = ProcessWindowStyle.Maximized;
//System.Diagnostics.Process.Start(_ProcessStartInfo);
System.Diagnostics.Process.Start("notepad.exe");
}
The problem is that Notepad goes run but with no UI and you can see it in the task manager but no GUI Instance of Notepad was shown.
I've also tried with the ProcessStartInfo() class as you can see as remarked code, but the problem still exists.
Have you tried setting the username and password property of ProcessStartInfo to a user that is currently logged on? The problem is that the system user does not have a gui available.
Hello what happens is that the process runs in an environment without a session,
It is necessary to have a user session, start the process and send the GUI to the graphical desktop of this started session.
This is verified since when you start your Process you get an ID and it can be checked in TaskManager/Details/
Sort by PID and you will see that service started but in SYSTEM.
It is necessary to create a class to obtain the logged in User and send the GUI to that desktop
class 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
// Gets the user token from the currently active session
private static bool GetSessionUserToken(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 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 (!GetSessionUserToken(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;
}
}
Then you just call your process as follows
public static void run()
{
ProcessExtensions.StartProcessAsCurrentUser(#"calc.exe");
}
Next Recompile you Service // Install // Start //
More Info:
How to start a process from windows service into currently logged in user's session
Credits: #murrayju
https://github.com/murrayju/CreateProcessAsUser