How to Start a Process Unelevated - c#

My app runs as requestedExecutionLevel set to highestAvailable.
How do I run a process unelevated?
I tried the following but it didn't work:
Process.Start(new ProcessStartInfo {FileName = "foo.exe", Verb = "open"})
I have tried the following trust levels to start my process using Win32 API but none of them work correctly:
0
1260: This program is blocked by group policy. For more information, contact your system administrator.
0x1000
The application was unable to start correctly (0xc0000142). Click OK to close the application.
0x10000
Process starts then hangs
0x20000
All options are not available
0x40000
Runs as admin
If I run tskill foo from my elevated app, it restarts foo with correct privileges.
What I need is a solution in which I don't have to specify the trust level. The process should start with the correct trust level automatically just like the tskill tool restarts foo.exe in the correct trust level. The user selects and runs foo.exe and so it can be anything.
If I can get the trust level of a process somehow, I can do this easily since foo.exe runs when my app can capture its trust level.

The Win32 Security Management functions provide the capability to create a restricted token with normal user rights; with the token, you can call CreateProcessAsUser to run the process with that token. Below is a proof of concept that runs cmd.exe as a normal user, regardless of whether the process is run in an elevated context.
// Initialize variables.
IntPtr hSaferLevel, hToken;
STARTUPINFO si = default(STARTUPINFO);
SECURITY_ATTRIBUTES processAttributes = default(SECURITY_ATTRIBUTES);
SECURITY_ATTRIBUTES threadAttributes = default(SECURITY_ATTRIBUTES);
PROCESS_INFORMATION pi;
si.cb = Marshal.SizeOf(si);
// The process to start (for demonstration, cmd.exe)
string ProcessName = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.System),
"cmd.exe");
// Create the restricted token info
if (!SaferCreateLevel(
SaferScopes.User,
SaferLevels.NormalUser, // Program will execute as a normal user
1, // required
out hSaferLevel,
IntPtr.Zero))
throw new Win32Exception(Marshal.GetLastWin32Error());
// From the level create a token
if (!SaferComputeTokenFromLevel(
hSaferLevel,
IntPtr.Zero,
out hToken,
SaferComputeTokenFlags.None,
IntPtr.Zero))
throw new Win32Exception(Marshal.GetLastWin32Error());
// Run the process with the restricted token
if (!CreateProcessAsUser(
hToken,
ProcessName,
null, ref processAttributes, ref threadAttributes,
true, 0, IntPtr.Zero, null,
ref si, out pi))
throw new Win32Exception(Marshal.GetLastWin32Error());
// Cleanup
if (!CloseHandle(pi.hProcess))
throw new Win32Exception(Marshal.GetLastWin32Error());
if (!CloseHandle(pi.hThread))
throw new Win32Exception(Marshal.GetLastWin32Error());
if (!SaferCloseLevel(hSaferLevel))
throw new Win32Exception(Marshal.GetLastWin32Error());
This approach makes use the following Win32 functions:
SaferIdentifyLevel to indicate the identity level (limited, normal, or elevated). Setting the levelId to SAFER_LEVELID_NORMALUSER (0x20000) provides the normal user level.
SaferComputeTokenFromLevel creates a token for the provided level. Passing NULL to the InAccessToken parameter uses the identity of the current thread.
CreateProcessAsUser creates the process with the provided token. Since the session is already interactive, most of the parameters can be kept at default values. (The third parameter, lpCommandLine can be provided as a string to specify the command line.)
CloseHandle (Kernel32) and SaferCloseLevel to free allocated memory.
Finally, the P/Invoke code is below (copied mostly from pinvoke.net):
[Flags]
public enum SaferLevels : uint
{
Disallowed = 0,
Untrusted = 0x1000,
Constrained = 0x10000,
NormalUser = 0x20000,
FullyTrusted = 0x40000
}
[Flags]
public enum SaferComputeTokenFlags : uint
{
None = 0x0,
NullIfEqual = 0x1,
CompareOnly = 0x2,
MakeIntert = 0x4,
WantFlags = 0x8
}
[Flags]
public enum SaferScopes : uint
{
Machine = 1,
User = 2
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferComputeTokenFromLevel(IntPtr LevelHandle, IntPtr InAccessToken, out IntPtr OutAccessToken, int dwFlags, IntPtr lpReserved);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferCreateLevel(
SaferScopes dwScopeId,
SaferLevels dwLevelId,
int OpenFlags,
out IntPtr pLevelHandle,
IntPtr lpReserved);
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferCloseLevel(
IntPtr pLevelHandle);
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferComputeTokenFromLevel(
IntPtr levelHandle,
IntPtr inAccessToken,
out IntPtr outAccessToken,
SaferComputeTokenFlags dwFlags,
IntPtr lpReserved
);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);

I had best results by cloning Explorer's token as follows:
var shellWnd = WinAPI.GetShellWindow();
if (shellWnd == IntPtr.Zero)
throw new Exception("Could not find shell window");
uint shellProcessId;
WinAPI.GetWindowThreadProcessId(shellWnd, out shellProcessId);
var hShellProcess = WinAPI.OpenProcess(0x00000400 /* QueryInformation */, false, shellProcessId);
var hShellToken = IntPtr.Zero;
if (!WinAPI.OpenProcessToken(hShellProcess, 2 /* TOKEN_DUPLICATE */, out hShellToken))
throw new Win32Exception();
uint tokenAccess = 8 /*TOKEN_QUERY*/ | 1 /*TOKEN_ASSIGN_PRIMARY*/ | 2 /*TOKEN_DUPLICATE*/ | 0x80 /*TOKEN_ADJUST_DEFAULT*/ | 0x100 /*TOKEN_ADJUST_SESSIONID*/;
var hToken = IntPtr.Zero;
WinAPI.DuplicateTokenEx(hShellToken, tokenAccess, IntPtr.Zero, 2 /* SecurityImpersonation */, 1 /* TokenPrimary */, out hToken);
var pi = new WinAPI.PROCESS_INFORMATION();
var si = new WinAPI.STARTUPINFO();
si.cb = Marshal.SizeOf(si);
if (!WinAPI.CreateProcessWithTokenW(hToken, 0, null, cmdArgs, 0, IntPtr.Zero, null, ref si, out pi))
throw new Win32Exception();
Alternative approach
Originally I went with drf's excellent answer, but expanded it somewhat. If the above (clone Explorer's token) is not to your liking, keep reading but see a gotcha at the very end.
When using drf's method as described, the process is started without administrative access, but it still has a high integrity level. A typical un-elevated process has a medium integrity level.
Try this: use Process Hacker to see the properties of the process started this way; you will see that PH considers the process to be elevated even though it doesn't have administrative access. Add an Integrity column and you'll see it's "High".
The fix is reasonably simple: after using SaferComputeTokenFromLevel, we need to change the token integrity level to Medium. The code to do this might look something like this (converted from MSDN sample):
// Get the Medium Integrity SID
if (!WinAPI.ConvertStringSidToSid("S-1-16-8192", out pMediumIntegritySid))
throw new Win32Exception();
// Construct a structure describing the token integrity level
var TIL = new TOKEN_MANDATORY_LABEL();
TIL.Label.Attributes = 0x00000020 /* SE_GROUP_INTEGRITY */;
TIL.Label.Sid = pMediumIntegritySid;
pTIL = Marshal.AllocHGlobal(Marshal.SizeOf<TOKEN_MANDATORY_LABEL>());
Marshal.StructureToPtr(TIL, pTIL, false);
// Modify the token
if (!WinAPI.SetTokenInformation(hToken, 25 /* TokenIntegrityLevel */, pTIL,
(uint) Marshal.SizeOf<TOKEN_MANDATORY_LABEL>()
+ WinAPI.GetLengthSid(pMediumIntegritySid)))
throw new Win32Exception();
Alas, this still doesn't really solve the problem completely. The process won't have administrative access; it won't have a high integrity, but it will still have a token that's marked as "elevated".
Whether this is a problem for you or not I don't know, but it may have been why I ended up cloning Explorer's token in the end, as described at the start of this answer.
Here is my full source code (modified drf's answer), in all its P/Invoke glory:
var hSaferLevel = IntPtr.Zero;
var hToken = IntPtr.Zero;
var pMediumIntegritySid = IntPtr.Zero;
var pTIL = IntPtr.Zero;
var pi = new WinAPI.PROCESS_INFORMATION();
try
{
var si = new WinAPI.STARTUPINFO();
si.cb = Marshal.SizeOf(si);
var processAttributes = new WinAPI.SECURITY_ATTRIBUTES();
var threadAttributes = new WinAPI.SECURITY_ATTRIBUTES();
var args = CommandRunner.ArgsToCommandLine(Args);
if (!WinAPI.SaferCreateLevel(WinAPI.SaferScopes.User, WinAPI.SaferLevels.NormalUser, 1, out hSaferLevel, IntPtr.Zero))
throw new Win32Exception();
if (!WinAPI.SaferComputeTokenFromLevel(hSaferLevel, IntPtr.Zero, out hToken, WinAPI.SaferComputeTokenFlags.None, IntPtr.Zero))
throw new Win32Exception();
if (!WinAPI.ConvertStringSidToSid("S-1-16-8192", out pMediumIntegritySid))
throw new Win32Exception();
var TIL = new TOKEN_MANDATORY_LABEL();
TIL.Label.Attributes = 0x00000020 /* SE_GROUP_INTEGRITY */;
TIL.Label.Sid = pMediumIntegritySid;
pTIL = Marshal.AllocHGlobal(Marshal.SizeOf<TOKEN_MANDATORY_LABEL>());
Marshal.StructureToPtr(TIL, pTIL, false);
if (!WinAPI.SetTokenInformation(hToken, 25 /* TokenIntegrityLevel */, pTIL, (uint) Marshal.SizeOf<TOKEN_MANDATORY_LABEL>() + WinAPI.GetLengthSid(pMediumIntegritySid)))
throw new Win32Exception();
if (!WinAPI.CreateProcessAsUser(hToken, null, commandLine, ref processAttributes, ref threadAttributes, true, 0, IntPtr.Zero, null, ref si, out pi))
throw new Win32Exception();
}
finally
{
if (hToken != IntPtr.Zero && !WinAPI.CloseHandle(hToken))
throw new Win32Exception();
if (pMediumIntegritySid != IntPtr.Zero && WinAPI.LocalFree(pMediumIntegritySid) != IntPtr.Zero)
throw new Win32Exception();
if (pTIL != IntPtr.Zero)
Marshal.FreeHGlobal(pTIL);
if (pi.hProcess != IntPtr.Zero && !WinAPI.CloseHandle(pi.hProcess))
throw new Win32Exception();
if (pi.hThread != IntPtr.Zero && !WinAPI.CloseHandle(pi.hThread))
throw new Win32Exception();
}
And here are the P/Invoke definitions you'll need in addition to those listed in drf's answer:
[DllImport("advapi32.dll", SetLastError = true)]
public static extern Boolean SetTokenInformation(IntPtr TokenHandle, int TokenInformationClass,
IntPtr TokenInformation, UInt32 TokenInformationLength);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("advapi32.dll")]
public static extern uint GetLengthSid(IntPtr pSid);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool ConvertStringSidToSid(
string StringSid,
out IntPtr ptrSid);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LocalFree(IntPtr hMem);

Raymond Chen addressed this in his blog:
How can I launch an unelevated process from my elevated process and vice versa?
Searching in GitHub for a C# version of this code, I found the following implementation in Microsoft's Node.js tools for Visual Studio repository: SystemUtilities.cs (see the ExecuteProcessUnElevated function).
Just in case the file disappears, here's the file's contents:
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
namespace Microsoft.NodejsTools.SharedProject
{
/// <summary>
/// Utility for accessing window IShell* interfaces in order to use them to launch a process unelevated
/// </summary>
internal class SystemUtility
{
/// <summary>
/// We are elevated and should launch the process unelevated. We can't create the
/// process directly without it becoming elevated. So to workaround this, we have
/// explorer do the process creation (explorer is typically running unelevated).
/// </summary>
internal static void ExecuteProcessUnElevated(string process, string args, string currentDirectory = "")
{
var shellWindows = (IShellWindows)new CShellWindows();
// Get the desktop window
object loc = CSIDL_Desktop;
object unused = new object();
int hwnd;
var serviceProvider = (IServiceProvider)shellWindows.FindWindowSW(ref loc, ref unused, SWC_DESKTOP, out hwnd, SWFO_NEEDDISPATCH);
// Get the shell browser
var serviceGuid = SID_STopLevelBrowser;
var interfaceGuid = typeof(IShellBrowser).GUID;
var shellBrowser = (IShellBrowser)serviceProvider.QueryService(ref serviceGuid, ref interfaceGuid);
// Get the shell dispatch
var dispatch = typeof(IDispatch).GUID;
var folderView = (IShellFolderViewDual)shellBrowser.QueryActiveShellView().GetItemObject(SVGIO_BACKGROUND, ref dispatch);
var shellDispatch = (IShellDispatch2)folderView.Application;
// Use the dispatch (which is unelevated) to launch the process for us
shellDispatch.ShellExecute(process, args, currentDirectory, string.Empty, SW_SHOWNORMAL);
}
/// <summary>
/// Interop definitions
/// </summary>
private const int CSIDL_Desktop = 0;
private const int SWC_DESKTOP = 8;
private const int SWFO_NEEDDISPATCH = 1;
private const int SW_SHOWNORMAL = 1;
private const int SVGIO_BACKGROUND = 0;
private readonly static Guid SID_STopLevelBrowser = new Guid("4C96BE40-915C-11CF-99D3-00AA004AE837");
[ComImport]
[Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39")]
[ClassInterfaceAttribute(ClassInterfaceType.None)]
private class CShellWindows
{
}
[ComImport]
[Guid("85CB6900-4D95-11CF-960C-0080C7F4EE85")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
private interface IShellWindows
{
[return: MarshalAs(UnmanagedType.IDispatch)]
object FindWindowSW([MarshalAs(UnmanagedType.Struct)] ref object pvarloc, [MarshalAs(UnmanagedType.Struct)] ref object pvarlocRoot, int swClass, out int pHWND, int swfwOptions);
}
[ComImport]
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IServiceProvider
{
[return: MarshalAs(UnmanagedType.Interface)]
object QueryService(ref Guid guidService, ref Guid riid);
}
[ComImport]
[Guid("000214E2-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellBrowser
{
void VTableGap01(); // GetWindow
void VTableGap02(); // ContextSensitiveHelp
void VTableGap03(); // InsertMenusSB
void VTableGap04(); // SetMenuSB
void VTableGap05(); // RemoveMenusSB
void VTableGap06(); // SetStatusTextSB
void VTableGap07(); // EnableModelessSB
void VTableGap08(); // TranslateAcceleratorSB
void VTableGap09(); // BrowseObject
void VTableGap10(); // GetViewStateStream
void VTableGap11(); // GetControlWindow
void VTableGap12(); // SendControlMsg
IShellView QueryActiveShellView();
}
[ComImport]
[Guid("000214E3-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellView
{
void VTableGap01(); // GetWindow
void VTableGap02(); // ContextSensitiveHelp
void VTableGap03(); // TranslateAcceleratorA
void VTableGap04(); // EnableModeless
void VTableGap05(); // UIActivate
void VTableGap06(); // Refresh
void VTableGap07(); // CreateViewWindow
void VTableGap08(); // DestroyViewWindow
void VTableGap09(); // GetCurrentInfo
void VTableGap10(); // AddPropertySheetPages
void VTableGap11(); // SaveViewState
void VTableGap12(); // SelectItem
[return: MarshalAs(UnmanagedType.Interface)]
object GetItemObject(UInt32 aspectOfView, ref Guid riid);
}
[ComImport]
[Guid("00020400-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
private interface IDispatch
{
}
[ComImport]
[Guid("E7A1AF80-4D96-11CF-960C-0080C7F4EE85")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
private interface IShellFolderViewDual
{
object Application { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
}
[ComImport]
[Guid("A4C6892C-3BA9-11D2-9DEA-00C04FB16162")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IShellDispatch2
{
void ShellExecute([MarshalAs(UnmanagedType.BStr)] string File, [MarshalAs(UnmanagedType.Struct)] object vArgs, [MarshalAs(UnmanagedType.Struct)] object vDir, [MarshalAs(UnmanagedType.Struct)] object vOperation, [MarshalAs(UnmanagedType.Struct)] object vShow);
}
}
}

The easiest solution would be starting the process using explorer.exe. This will start any process unelevated. You can just start explorer.exe using
System.Diagnostics.Process.Start();
The file name will be "C:\Windows\explorer.exe" and the arguments will be the executable you want to start unelevated, surrounded by quotes.
Example:
If I wanted to start F:\folder\example.exe unelevated I would do this:
using System.Diagnostics;
namespace example
{
class exampleClass
{
ProcessStartInfo exampleStartInfo = new ProcessStartInfo();
exampleStartInfo.FileName = "C:\\Windows\\explorer.exe";
exampleStartInfo.Arguments = "\"F:\\folder\\example.exe\"";
Process.Start(exampleStartInfo);
}
}
This might not work on older versions of windows, but it at least works on my laptop, so it certainly does on windows 10.

Related

MemoryMappedFile low-integrity .Net 5

I'm using a MemoryMappedFile in .NET 5 and encounter a problem that seemed to have been solved long ago.
I use it for inter-process communication so it must be located in ring 0. For that to achieve I use the name prefix Global:
var mmf = MemoryMappedFile.CreateOrOpen(#"Global\test", 1024, MemoryMappedFileAccess.ReadWrite);
This works fine for privileged processes.
For non-privileged ones I want to use
mmf = MemoryMappedFile.OpenExisting(#"Global\test", MemoryMappedFileRights.ReadWrite);
But that fails with an "UnauthorizedAccessException".
The solution that worked for .NET Framework up to 4.8 was to explicitly set MemoryMappedFileSecurity as described e.g. here: Gaining access to a MemoryMappedFile from low-integrity process
Unfortunately the MemoryMappedFileSecurity does no longer seem to exist and MemoryMappedFile.OpenExisting (or CreateNew) do no have an overload for that or similar either.
Interim solution
.Net5 mostlikely won't get that missing feature. See https://github.com/dotnet/runtime/issues/941
As a workaround I use the P/Invoke approach. In case someone has the same problem here is a working solution using null DACL to grant all access
private void CreateFile()
{
try
{
using(var secAttribs = CreateSecAttribs())
{
memBuffer = NativeMethods.CreateFileMapping(
UIntPtr.MaxValue,
secAttribs,
(uint)FileMapProtection.PAGE_READWRITE,
0,
(uint)MemOffset.TotalSize,
BufferName);
if (memBuffer == IntPtr.Zero)
{
uint lasterror = NativeMethods.GetLastError();
throw new Win32Exception((int)lasterror, string.Format(CultureInfo.InvariantCulture, "Error creating shared memory. Errorcode is {0}", lasterror));
}
IntPtr accessor = NativeMethods.MapViewOfFile(memBuffer, (uint)ViewAccess.FILE_MAP_ALL_ACCESS, 0, 0, (int)MemOffset.TotalSize);
if (accessor == IntPtr.Zero)
{
uint lasterror = NativeMethods.GetLastError();
throw new Win32Exception((int)lasterror, string.Format(CultureInfo.InvariantCulture, "Error creating shared memory view. Errorcode is {0}", lasterror));
}
//Do sth with accessor using System.Runtime.InteropServices.Marshal
}
}
catch (Exception)
{
memBuffer = IntPtr.Zero;
throw;
}
}
private static NativeMethods.SECURITY_ATTRIBUTES CreateSecAttribs()
{
//Create the descriptor with a null DACL --> Everything is granted.
RawSecurityDescriptor sec = new RawSecurityDescriptor(ControlFlags.DiscretionaryAclPresent, null, null, null, null);
return new NativeMethods.SECURITY_ATTRIBUTES(sec);
}
For reference:
internal static class NativeMethods
{
#region Structures
[StructLayout(LayoutKind.Sequential)]
internal class SECURITY_ATTRIBUTES : IDisposable
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
public SECURITY_ATTRIBUTES()
{
nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));
lpSecurityDescriptor = IntPtr.Zero;
bInheritHandle = 1;
}
public SECURITY_ATTRIBUTES(RawSecurityDescriptor sec) :
this()
{
byte[] binDACL = new byte[sec.BinaryLength];
sec.GetBinaryForm(binDACL, 0);
lpSecurityDescriptor = Marshal.AllocHGlobal(sec.BinaryLength);
Marshal.Copy(binDACL, 0, lpSecurityDescriptor, sec.BinaryLength);
}
~SECURITY_ATTRIBUTES()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
}
if (lpSecurityDescriptor != IntPtr.Zero)
{
Marshal.FreeHGlobal(lpSecurityDescriptor);
lpSecurityDescriptor = IntPtr.Zero;
}
}
}
#endregion
#region General imports
[DllImport("kernel32", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int CloseHandle(IntPtr hHandle);
[DllImport("kernel32", EntryPoint = "GetLastError", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern uint GetLastError();
#endregion
#region Memory Mapped Files imports
[DllImport("kernel32.dll", EntryPoint = "CreateFileMapping", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateFileMapping(UIntPtr hFile, SECURITY_ATTRIBUTES lpAttributes, uint flProtect, uint dwMaximumSizeHigh, uint dwMaximumSizeLow, string lpName);
[DllImport("kernel32.dll", EntryPoint = "MapViewOfFile", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, uint dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, uint /* UIntPtr */ dwNumberOfBytesToMap);
[DllImport("kernel32.dll", EntryPoint = "UnmapViewOfFile", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.VariantBool)]
internal static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
#endregion
}
Update
Still no official integration until .Net 7
Adam Sitnik was so friendly to manually recreate MemoryMappedFileSecurity and Wrappers for .Net7.
See https://gist.github.com/adamsitnik/6370b05b9e80bc14b62ac6efe5d1e2e2#file-mmfs-cs-L41-L184
You might need to add CreateOrOpen() based on .Net4 or remove the ERROR_ALREADY_EXIST error case in CreateCore()
Managed Solution
This works when first called by a windows service and then by a user program (and of course vv).
var everyoneRule = new AccessRule<MemoryMappedFileRights>(
new SecurityIdentifier(WellKnownSidType.WorldSid, null),
MemoryMappedFileRights.ReadWrite,
AccessControlType.Allow);
MemoryMappedFileSecurity mmfSec = new MemoryMappedFileSecurity();
mmfSec.AddAccessRule(everyoneRule);
mmf = MemoryMappedFileFactory.Create(#"Global\Test", 1024, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None, mmfSec, HandleInheritability.None);

open external .exe over LAN without the run message c#

Hi Im trying to open a external .exe in c#.
If i try to open the .exe file in my computer like "C:\programs\test.exe" it works well. but if I try to open it in another machine I have to map the drive
S:\programs\test.exe , is ok but it prompts a message if I wanna run this .exe
I want to avoid this message.
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "S:\\dev_express\\calendario_indar.exe";
psi.Arguments = "arguments"
var p = Process.Start(psi);
I had similar problems about running executable files. I found a class from MSDN and I always use it. But I'm not sure if it hides that window too. But give it a try. After adding this class to your Project you can launch your application like that:
NativeMethods.LaunchProcess("S:\\dev_express\\calendario_indar.exe");
Class is here:
class NativeMethods
{
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public System.UInt32 dwProcessId;
public System.UInt32 dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public System.UInt32 nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public System.UInt32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public System.UInt32 dwX;
public System.UInt32 dwY;
public System.UInt32 dwXSize;
public System.UInt32 dwYSize;
public System.UInt32 dwXCountChars;
public System.UInt32 dwYCountChars;
public System.UInt32 dwFillAttribute;
public System.UInt32 dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROFILEINFO
{
public int dwSize;
public int dwFlags;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpUserName;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpProfilePath;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpDefaultPath;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpServerName;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpPolicyPath;
public IntPtr hProfile;
}
internal enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3
}
internal enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation = 2
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
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, ref PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess, ref SECURITY_ATTRIBUTES lpTokenAttributes, SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, TOKEN_TYPE TokenType, ref IntPtr phNewToken);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr ProcessHandle, int 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);
private const short SW_SHOW = 1;
private const short SW_SHOWMAXIMIZED = 7;
private const int TOKEN_QUERY = 8;
private const int TOKEN_DUPLICATE = 2;
private const int TOKEN_ASSIGN_PRIMARY = 1;
private const int GENERIC_ALL_ACCESS = 268435456;
private const int STARTF_USESHOWWINDOW = 1;
private const int STARTF_FORCEONFEEDBACK = 64;
private const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;
private const string gs_EXPLORER = "explorer";
public static void LaunchProcess(string Ps_CmdLine)
{
IntPtr li_Token = default(IntPtr);
IntPtr li_EnvBlock = default(IntPtr);
Process[] lObj_Processes = Process.GetProcessesByName(gs_EXPLORER);
// Get explorer.exe id
// If process not found
if (lObj_Processes.Length == 0)
{
// Exit routine
return;
}
// Get primary token for the user currently logged in
li_Token = GetPrimaryToken(lObj_Processes[0].Id);
// If token is nothing
if (li_Token.Equals(IntPtr.Zero))
{
// Exit routine
return;
}
// Get environment block
li_EnvBlock = GetEnvironmentBlock(li_Token);
// Launch the process using the environment block and primary token
LaunchProcessAsUser(Ps_CmdLine, li_Token, li_EnvBlock);
// If no valid enviroment block found
if (li_EnvBlock.Equals(IntPtr.Zero))
{
// Exit routine
return;
}
// Destroy environment block. Free environment variables created by the
// CreateEnvironmentBlock function.
DestroyEnvironmentBlock(li_EnvBlock);
}
private static IntPtr GetPrimaryToken(int Pi_ProcessId)
{
IntPtr li_Token = IntPtr.Zero;
IntPtr li_PrimaryToken = IntPtr.Zero;
bool lb_ReturnValue = false;
Process lObj_Process = Process.GetProcessById(Pi_ProcessId);
SECURITY_ATTRIBUTES lObj_SecurityAttributes = default(SECURITY_ATTRIBUTES);
// Get process by id
// Open a handle to the access token associated with a process. The access token
// is a runtime object that represents a user account.
lb_ReturnValue = OpenProcessToken(lObj_Process.Handle, TOKEN_DUPLICATE, ref li_Token);
// If successfull in opening handle to token associated with process
if (lb_ReturnValue)
{
// Create security attributes to pass to the DuplicateTokenEx function
lObj_SecurityAttributes = new SECURITY_ATTRIBUTES();
lObj_SecurityAttributes.nLength = Convert.ToUInt32(Marshal.SizeOf(lObj_SecurityAttributes));
// Create a new access token that duplicates an existing token. This function
// can create either a primary token or an impersonation token.
lb_ReturnValue = DuplicateTokenEx(li_Token, Convert.ToUInt32(TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY), ref lObj_SecurityAttributes, SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, TOKEN_TYPE.TokenPrimary, ref li_PrimaryToken);
// If un-successful in duplication of the token
if (!lb_ReturnValue)
{
// Throw exception
throw new Exception(string.Format("DuplicateTokenEx Error: {0}", Marshal.GetLastWin32Error()));
}
}
else
{
// If un-successful in opening handle for token then throw exception
throw new Exception(string.Format("OpenProcessToken Error: {0}", Marshal.GetLastWin32Error()));
}
// Return primary token
return li_PrimaryToken;
}
private static IntPtr GetEnvironmentBlock(IntPtr Pi_Token)
{
IntPtr li_EnvBlock = IntPtr.Zero;
bool lb_ReturnValue = CreateEnvironmentBlock(ref li_EnvBlock, Pi_Token, false);
// Retrieve the environment variables for the specified user.
// This block can then be passed to the CreateProcessAsUser function.
// If not successful in creation of environment block then
if (!lb_ReturnValue)
{
// Throw exception
throw new Exception(string.Format("CreateEnvironmentBlock Error: {0}", Marshal.GetLastWin32Error()));
}
// Return the retrieved environment block
return li_EnvBlock;
}
private static void LaunchProcessAsUser(string Ps_CmdLine, IntPtr Pi_Token, IntPtr Pi_EnvBlock)
{
bool lb_Result = false;
PROCESS_INFORMATION lObj_ProcessInformation = default(PROCESS_INFORMATION);
SECURITY_ATTRIBUTES lObj_ProcessAttributes = default(SECURITY_ATTRIBUTES);
SECURITY_ATTRIBUTES lObj_ThreadAttributes = default(SECURITY_ATTRIBUTES);
STARTUPINFO lObj_StartupInfo = default(STARTUPINFO);
// Information about the newly created process and its primary thread.
lObj_ProcessInformation = new PROCESS_INFORMATION();
// Create security attributes to pass to the CreateProcessAsUser function
lObj_ProcessAttributes = new SECURITY_ATTRIBUTES();
lObj_ProcessAttributes.nLength = Convert.ToUInt32(Marshal.SizeOf(lObj_ProcessAttributes));
lObj_ThreadAttributes = new SECURITY_ATTRIBUTES();
lObj_ThreadAttributes.nLength = Convert.ToUInt32(Marshal.SizeOf(lObj_ThreadAttributes));
// To specify the window station, desktop, standard handles, and appearance of the
// main window for the new process.
lObj_StartupInfo = new STARTUPINFO();
lObj_StartupInfo.cb = Convert.ToUInt32(Marshal.SizeOf(lObj_StartupInfo));
lObj_StartupInfo.lpDesktop = null;
lObj_StartupInfo.dwFlags = Convert.ToUInt32(STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK);
lObj_StartupInfo.wShowWindow = SW_SHOW;
// Creates a new process and its primary thread. The new process runs in the
// security context of the user represented by the specified token.
lb_Result = CreateProcessAsUser(Pi_Token, null, Ps_CmdLine, ref lObj_ProcessAttributes, ref lObj_ThreadAttributes, true, CREATE_UNICODE_ENVIRONMENT, Pi_EnvBlock, null, ref lObj_StartupInfo, ref lObj_ProcessInformation);
// If create process return false then
if (!lb_Result)
{
// Throw exception
throw new Exception(string.Format("CreateProcessAsUser Error: {0}", Marshal.GetLastWin32Error()));
}
}
}

launch process from Session 0 Isolation

On Windows 8.1 I have a service that starts PowerShell scripts.
The service runs as “nt authority\system” in Session 0 Isolation.
Any process that I spawn from PowerShell runs as “nt authority\system” in Session 0 Isolation.
I need to run a script that is under a user account out of session 0 and not the system account.
I have tried this
Start-Process "$PsHome\PowerShell.exe" -Credential $pp -ArgumentList $script -wait
and PsExec specifying which session I want with "-I 1" argument.
& PsExec.exe "Install.bat" -i 1 -accepteula -u "domain\user" -p "awesomePassword" -w "startdir" -h
I have tried setting "Allow service to interact with desktop".
I keep getting Access is denied errors when I try and start the process either from PowerShell or from the c# service.
Here is an example exception when I try to escape using c# on the service.
System.ComponentModel.Win32Exception (0x80004005): Access is denied
at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo)
How do I escape from session 0?
I can re-write the c# code to start a process under a different user.
or I can re-write the called PowerShell script to start another process as a user.
No matter what I try, I can't seem to break out of session 0.
Using the example I found at code project I got a partial solution. The example in the link above will launch a process as the user who is running the "winlogon" process. In order to launch a process as the user who is logged in I just changed the process to look for "explorer" instead.
Here is a snippet of the original code
// obtain the process id of the winlogon process that is
// running within the currently active session
Process[] processes = Process.GetProcessesByName("winlogon");
I just change the process to look for explorer.
Process[] processes = Process.GetProcessesByName("explorer");
Now the process launches as domain/me in Session 3 as a user not admin.
There has to be issues with this approach, such as Remote Desktop, but for what I want this will ultimately do.
Here is the final code for completeness in case the original link evaporates.
Here is how to launch it
// the name of the application to launch
String applicationName = "cmd.exe";
// launch the application
ApplicationLoader.PROCESS_INFORMATION procInfo;
ApplicationLoader.StartProcessAndBypassUAC(applicationName, out procInfo);
Here is the code
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
namespace SuperAwesomeNameSpaceOfJustice
{
/// <summary>
/// Class that allows running applications with full admin rights. In
/// addition the application launched will bypass the Vista UAC prompt.
/// </summary>
public class ApplicationLoader
{
#region Structures
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public int cb;
public String lpReserved;
public String lpDesktop;
public String lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
#endregion
#region Enumerations
enum TOKEN_TYPE : int
{
TokenPrimary = 1,
TokenImpersonation = 2
}
enum SECURITY_IMPERSONATION_LEVEL : int
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3,
}
#endregion
#region Constants
public const int TOKEN_DUPLICATE = 0x0002;
public const uint MAXIMUM_ALLOWED = 0x2000000;
public const int CREATE_NEW_CONSOLE = 0x00000010;
public const int IDLE_PRIORITY_CLASS = 0x40;
public const int NORMAL_PRIORITY_CLASS = 0x20;
public const int HIGH_PRIORITY_CLASS = 0x80;
public const int REALTIME_PRIORITY_CLASS = 0x100;
#endregion
#region Win32 API Imports
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hSnapshot);
[DllImport("kernel32.dll")]
static extern uint WTSGetActiveConsoleSessionId();
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public extern static bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, String lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment,
String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll")]
static extern bool ProcessIdToSessionId(uint dwProcessId, ref uint pSessionId);
// Fixed invalid declaration from Code Projects code
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
public extern static bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes, int ImpersonationLevel,
int TokenType, ref IntPtr DuplicateTokenHandle);
[DllImport("kernel32.dll")]
static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("advapi32", SetLastError = true), SuppressUnmanagedCodeSecurity]
static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, ref IntPtr TokenHandle);
#endregion
/// <summary>
/// Launches the given application with full admin rights, and in addition bypasses the Vista UAC prompt
/// </summary>
/// <param name="applicationName">The name of the application to launch</param>
/// <param name="procInfo">Process information regarding the launched application that gets returned to the caller</param>
/// <returns></returns>
public static bool StartProcessAndBypassUAC(String applicationName, string startingDir, out PROCESS_INFORMATION procInfo)
{
uint winlogonPid = 0;
IntPtr hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;
procInfo = new PROCESS_INFORMATION();
// obtain the currently active session id; every logged on user in the system has a unique session id
uint dwSessionId = WTSGetActiveConsoleSessionId();
// obtain the process id of the winlogon process that is running within the currently active session
// -- chaged by ty
// Process[] processes = Process.GetProcessesByName("winlogon");
Process[] processes = Process.GetProcessesByName("explorer");
foreach (Process p in processes)
{
if ((uint)p.SessionId == dwSessionId)
{
winlogonPid = (uint)p.Id;
}
}
// obtain a handle to the winlogon process
hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);
// obtain a handle to the access token of the winlogon process
if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
{
CloseHandle(hProcess);
return false;
}
// Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser
// I would prefer to not have to use a security attribute variable and to just
// simply pass null and inherit (by default) the security attributes
// of the existing token. However, in C# structures are value types and therefore
// cannot be assigned the null value.
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
// copy the access token of the winlogon process; the newly created token will be a primary token
if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
{
CloseHandle(hProcess);
CloseHandle(hPToken);
return false;
}
// By default CreateProcessAsUser creates a process on a non-interactive window station, meaning
// the window station has a desktop that is invisible and the process is incapable of receiving
// user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user
// interaction with the new process.
STARTUPINFO si = new STARTUPINFO();
si.cb = (int)Marshal.SizeOf(si);
si.lpDesktop = #"winsta0\default"; // interactive window station parameter; basically this indicates that the process created can display a GUI on the desktop
// flags that specify the priority and creation method of the process
int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
// create a new process in the current user's logon session
bool result = CreateProcessAsUser(hUserTokenDup, // client's access token
null, // file to execute
applicationName, // command line
ref sa, // pointer to process SECURITY_ATTRIBUTES
ref sa, // pointer to thread SECURITY_ATTRIBUTES
false, // handles are not inheritable
dwCreationFlags, // creation flags
IntPtr.Zero, // pointer to new environment block
startingDir, // name of current directory
ref si, // pointer to STARTUPINFO structure
out procInfo // receives information about new process
);
// invalidate the handles
CloseHandle(hProcess);
CloseHandle(hPToken);
CloseHandle(hUserTokenDup);
return result; // return the result
}
}
}

C# Windows Service Creates Process but doesn't executes it

So I have checked many many sites, researched for days now. And I have not found or come up with one of my own solution for this problem.
I know, apparently since Windows Vista, a Windows Service since its created in Session 0 its not able to interact with whats considered GUI executables like console apps and other softwares that are part of other Sessions that are not Session 0.
According to Microsoft, a Service that does this would be a potential 'Virus'. Which I understand the reasoning for their thinking. But this is the only solution for our problems.
//This is how I am calling the process.
public void startVM(string vmname) {
string cmdline = startvm --type headless VM2000";
ProcessStartInfo startInfo = new ProcessStartInfo("cmd.exe");
startInfo.WindowStyle = ProcessWindowStyle.Minimized;
startInfo.Arguments = string.Format(#"c:\vms\vboxmanage startvm {0}",vmname);
Process.Start(startInfo);
}
So this is what happens:
I create a Windows Service, this service on startup will start a Process. In this case "cmd.exe". I have checked many times and I am certain that the process is actually created. But the arguments, the actual commands, that I want that cmd.exe to execute...they are being ignored. They just never happen. I tested the code elsewhere, as a library, as a windows form application it is working like clockwork. But yet, as a Service it won't work.
I have tried solutions like enabling to interact with Desktop. Even from Registry Key. I have tried even calling different executables, and it happens the same thing: it creates the process, but it doesn't execute the commands or arguments.
I have read many have had this problem... however no solution have been found by all these sites that I have seen this problem for. Even users from StackOverflow.
//Located in the service class inheriting from ServiceBase
protected override void OnStart(string[] args)
{
//System.Diagnostics.Debugger.Launch();
IVBoxCom vBox = new VBoxCom();
//This method calls the method you see above.
vBox.StartVM("WIN2K");
}
This is the Service Installer Class:
ServiceInstaller installer = new ServiceInstaller();
installer.ServiceName = "Steven-VBoxService"; //This has to be the exact Name of the Service that has ServiceBase Class
installer.DisplayName = "Steven-VBoxService";
installer.StartType = ServiceStartMode.Manual;
base.Installers.Add(installer);
//Creates an Executable that convokes the Service previously installed.
//Note: In theory, I can create 10 Services, and run them in a single Service Process
ServiceProcessInstaller installer2 = new ServiceProcessInstaller();
installer2.Account = ServiceAccount.LocalSystem; //Windows service.
//installer2.Password = "sh9852"; //Why would I used these options?
//installer2.Username = #"FITZMALL\hernandezs";
installer2.Password = null;
installer2.Username = null;
base.Installers.Add(installer2);
I have noticed that when I want to start the service, it gets stuck at "Starting", then it just stops.
But the Process the cmd.exe or the VBoxManage.exe get created but never actually do anything at all.
So the only alternative to this is to trick the OS. And make an instance of the Process from the Kernel but changing who was the creator. Let me elaborate.
Since Windows Vista and greater...Microsoft thought that having the Windows Service as a Service that can interactive with User GUI was a bad idea(and I agree at some point) because it may be potentially a virus that will run everytime at startup. So they created something called a Session 0. All your services are in this Session so that they are not able to interact with your user(or Session 1 +) GUI. Meaning the Windows Service has no access to cmd.exe, VBoxManage.exe, any other app that has GUI interaction.
So... the solution to the problem is tricking the OS, creating the Process from the Kernel with Platform Invokes(Win 32 API) which is not that common for a day to day developer in C#.
When creating the Process from the KernelDLL you have access to change who the User or the Creator is. In this case instead of having the Session 0 creating the Process, I changed it to the current Session ID, or current User. This made it possible for my Windows Service Work like I wanted.
For this idea to work you have to read a lot about KernelDll, advapi32.dll, mostly their methods and enum declarations since its not something you can just reference into your project. Those two need to be P/Invoke in order to use them.
The Following Class that I created makes it possible for you to create a process as the current user and not as Session 0. Hence solving my original problem.
//Just use the Class Method no need to instantiate it:
ApplicationLoader.CreateProcessAsUser(string filename, string args)
[SuppressUnmanagedCodeSecurity]
class ApplicationLoader
{
/// <summary>
/// No Need to create the class.
/// </summary>
private ApplicationLoader() { }
enum TOKEN_INFORMATION_CLASS
{
TokenUser = 1,
TokenGroups,
TokenPrivileges,
TokenOwner,
TokenPrimaryGroup,
TokenDefaultDacl,
TokenSource,
TokenType,
TokenImpersonationLevel,
TokenStatistics,
TokenRestrictedSids,
TokenSessionId,
TokenGroupsAndPrivileges,
TokenSessionReference,
TokenSandBoxInert,
TokenAuditPolicy,
TokenOrigin,
TokenElevationType,
TokenLinkedToken,
TokenElevation,
TokenHasRestrictions,
TokenAccessInformation,
TokenVirtualizationAllowed,
TokenVirtualizationEnabled,
TokenIntegrityLevel,
TokenUIAccess,
TokenMandatoryPolicy,
TokenLogonSid,
MaxTokenInfoClass
}
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public Int32 dwProcessID;
public Int32 dwThreadID;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public Int32 Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
public enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
public const int GENERIC_ALL_ACCESS = 0x10000000;
public const int CREATE_NO_WINDOW = 0x08000000;
[DllImport("advapi32.dll", EntryPoint = "ImpersonateLoggedOnUser", SetLastError = true,
CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr ImpersonateLoggedOnUser(IntPtr hToken);
[
DllImport("kernel32.dll",
EntryPoint = "CloseHandle", SetLastError = true,
CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)
]
public static extern bool CloseHandle(IntPtr handle);
[
DllImport("advapi32.dll",
EntryPoint = "CreateProcessAsUser", SetLastError = true,
CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)
]
public static extern bool
CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandle, Int32 dwCreationFlags, IntPtr lpEnvrionment,
string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo,
ref PROCESS_INFORMATION lpProcessInformation);
[
DllImport("advapi32.dll",
EntryPoint = "DuplicateTokenEx")
]
public static extern bool
DuplicateTokenEx(IntPtr hExistingToken, Int32 dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
Int32 ImpersonationLevel, Int32 dwTokenType,
ref IntPtr phNewToken);
[DllImport("Kernel32.dll", SetLastError = true)]
//[return: MarshalAs(UnmanagedType.U4)]
public static extern IntPtr WTSGetActiveConsoleSessionId();
[DllImport("advapi32.dll")]
public static extern IntPtr SetTokenInformation(IntPtr TokenHandle, IntPtr TokenInformationClass, IntPtr TokenInformation, IntPtr TokenInformationLength);
[DllImport("wtsapi32.dll", SetLastError = true)]
public static extern bool WTSQueryUserToken(uint sessionId, out IntPtr Token);
private static int getCurrentUserSessionID()
{
uint dwSessionId = (uint)WTSGetActiveConsoleSessionId();
//Gets the ID of the User logged in with WinLogOn
Process[] processes = Process.GetProcessesByName("winlogon");
foreach (Process p in processes)
{
if ((uint)p.SessionId == dwSessionId)
{
//this is the process controlled by the same sessionID
return p.SessionId;
}
}
return -1;
}
/// <summary>
/// Actually calls and creates the application.
/// </summary>
/// <param name="filename"></param>
/// <param name="args"></param>
/// <returns></returns>
public static Process CreateProcessAsUser(string filename, string args)
{
//var replaces IntPtr
var hToken = WindowsIdentity.GetCurrent().Token; //gets Security Token of Current User.
var hDupedToken = IntPtr.Zero;
var pi = new PROCESS_INFORMATION();
var sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
try
{
if (!DuplicateTokenEx(
hToken,
GENERIC_ALL_ACCESS,
ref sa,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)TOKEN_TYPE.TokenPrimary,
ref hDupedToken
))
throw new Win32Exception(Marshal.GetLastWin32Error());
var si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "";
var path = Path.GetFullPath(filename);
var dir = Path.GetDirectoryName(path);
//Testing
uint curSessionid = (uint)ApplicationLoader.getCurrentUserSessionID();
if (!WTSQueryUserToken(curSessionid,out hDupedToken))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Revert to self to create the entire process; not doing this might
// require that the currently impersonated user has "Replace a process
// level token" rights - we only want our service account to need
// that right.
using (var ctx = WindowsIdentity.Impersonate(IntPtr.Zero))
{
if (!CreateProcessAsUser(
hDupedToken,
path,
string.Format("\"{0}\" {1}", filename.Replace("\"", "\"\""), args),
ref sa, ref sa,
false, CREATE_NO_WINDOW, IntPtr.Zero,
dir, ref si, ref pi
))
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return Process.GetProcessById(pi.dwProcessID);
}
finally
{
if (pi.hProcess != IntPtr.Zero)
CloseHandle(pi.hProcess);
if (pi.hThread != IntPtr.Zero)
CloseHandle(pi.hThread);
if (hDupedToken != IntPtr.Zero)
CloseHandle(hDupedToken);
}
}
}
Modify the class at your will. Just be careful not to touch a lot of the initial enum declarations or the external methods if you have no clue how those work yet.
The problem with your original code (as shown in the question) is very simple: you left out the /c argument to cmd.exe to tell it to run your command.
In other words, you were trying to do this:
cmd c:\vms\vboxmanage startvm {0}
whereas what you needed to do was this:
cmd /c c:\vms\vboxmanage startvm {0}
or this:
c:\vms\vboxmanage startvm {0}
Now, that said, there are some applications that don't like running in a service context. Note that this isn't because they display a GUI but for any one of several other reasons. (For example, some applications only work if Explorer is running on the same desktop.)
It's possible that vboxmanage is such an application, but it's more likely that your original code would have worked perfectly if you hadn't forgotten the /c.

What's the best way to watchdog a desktop application?

I need some way to monitor a desktop application and restart it if it dies.
Initially I assumed the best way would be to monitor/restart the process from a Windows service, until I found out that since Vista Windows services should not interact with the desktop
I've seen several questions dealing with this issue, but every answer I've seen involved some kind of hack that is discouraged by Microsoft and will likely stop working in future OS updates.
So, a Windows service is probably not an option anymore. I could probably just create a different desktop/console application to do this, but that kind of defeats its purpose.
Which would be the most elegant way to achieve this, in your opinion?
EDIT: This is neither malware nor virus. The app that needs monitoring is a media player that will run on an embedded system, and even though I'm trying to cover all possible crash scenarios, I can't risk having it crash over an unexpected error (s**t happens). This watchdog would be just a safeguard in case everything else goes wrong. Also, since the player would be showing 3rd party flash content, an added plus would be for example to monitor for resource usage, and restart the player if say, some crappy flash movie starts leaking memory.
EDIT 2: I forgot to mention, the application I would like to monitor/restart has absolutely no need to run on either the LocalSystem account nor with any administrative privileges at all. Actually, I'd prefer it to run using the currently logged user credentials.
I finally implemented a the solution suggested by #A_nto2 and it achieved exactly what I was looking for: I now have a Windows Service that monitors a list of processes and whenever they are down, they are launched again automatically using the active user's credentials and session, so the GUI is visible.
However, since the links he posted shown VC++ code, I'm sharing my C# implementation for anyone dealing with the same issue:
public static class ProcessExtensions
{
public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
[StructLayout(LayoutKind.Sequential)]
public class SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
public enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
[Flags]
public enum CREATE_PROCESS_FLAGS : uint
{
NONE = 0x00000000,
DEBUG_PROCESS = 0x00000001,
DEBUG_ONLY_THIS_PROCESS = 0x00000002,
CREATE_SUSPENDED = 0x00000004,
DETACHED_PROCESS = 0x00000008,
CREATE_NEW_CONSOLE = 0x00000010,
NORMAL_PRIORITY_CLASS = 0x00000020,
IDLE_PRIORITY_CLASS = 0x00000040,
HIGH_PRIORITY_CLASS = 0x00000080,
REALTIME_PRIORITY_CLASS = 0x00000100,
CREATE_NEW_PROCESS_GROUP = 0x00000200,
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
CREATE_SEPARATE_WOW_VDM = 0x00000800,
CREATE_SHARED_WOW_VDM = 0x00001000,
CREATE_FORCEDOS = 0x00002000,
BELOW_NORMAL_PRIORITY_CLASS = 0x00004000,
ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000,
INHERIT_PARENT_AFFINITY = 0x00010000,
INHERIT_CALLER_PRIORITY = 0x00020000,
CREATE_PROTECTED_PROCESS = 0x00040000,
EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
PROCESS_MODE_BACKGROUND_BEGIN = 0x00100000,
PROCESS_MODE_BACKGROUND_END = 0x00200000,
CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
CREATE_NO_WINDOW = 0x08000000,
PROFILE_USER = 0x10000000,
PROFILE_KERNEL = 0x20000000,
PROFILE_SERVER = 0x40000000,
CREATE_IGNORE_SYSTEM_DEFAULT = 0x80000000,
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
public class Kernel32
{
[DllImport("kernel32.dll", EntryPoint = "WTSGetActiveConsoleSessionId")]
public static extern uint WTSGetActiveConsoleSessionId();
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);
}
public class WtsApi32
{
[DllImport("Wtsapi32.dll", EntryPoint = "WTSQueryUserToken")]
public static extern bool WTSQueryUserToken(UInt32 sessionId, out IntPtr phToken);
}
public class AdvApi32
{
public const uint MAXIMUM_ALLOWED = 0x2000000;
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateTokenEx
(
IntPtr hExistingToken,
uint dwDesiredAccess,
SECURITY_ATTRIBUTES lpTokenAttributes,
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
TOKEN_TYPE TokenType,
out IntPtr phNewToken
);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CreateProcessAsUser
(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
SECURITY_ATTRIBUTES lpProcessAttributes,
SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles,
CREATE_PROCESS_FLAGS dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation
);
}
public class UserEnv
{
[DllImport("userenv.dll", SetLastError = true)]
public static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
[DllImport("userenv.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);
}
public static void StartAsActiveUser(this Process process)
{
// Sanity check.
if (process.StartInfo == null)
{
throw new InvalidOperationException("The StartInfo property must be defined");
}
if (string.IsNullOrEmpty(process.StartInfo.FileName))
{
throw new InvalidOperationException("The StartInfo.FileName property must be defined");
}
// Retrieve the active session ID and its related user token.
var sessionId = Kernel32.WTSGetActiveConsoleSessionId();
var userTokenPtr = new IntPtr();
if (!WtsApi32.WTSQueryUserToken(sessionId, out userTokenPtr))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Duplicate the user token so that it can be used to create a process.
var duplicateUserTokenPtr = new IntPtr();
if (!AdvApi32.DuplicateTokenEx(userTokenPtr, AdvApi32.MAXIMUM_ALLOWED, null, SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, TOKEN_TYPE.TokenPrimary, out duplicateUserTokenPtr))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Create an environment block for the interactive process.
var environmentPtr = new IntPtr();
if (!UserEnv.CreateEnvironmentBlock(out environmentPtr, duplicateUserTokenPtr, false))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Create the process under the target user’s context.
var processFlags = CREATE_PROCESS_FLAGS.NORMAL_PRIORITY_CLASS | CREATE_PROCESS_FLAGS.CREATE_NEW_CONSOLE | CREATE_PROCESS_FLAGS.CREATE_UNICODE_ENVIRONMENT;
var processInfo = new PROCESS_INFORMATION();
var startupInfo = new STARTUPINFO();
startupInfo.cb = Marshal.SizeOf(startupInfo);
if (!AdvApi32.CreateProcessAsUser
(
duplicateUserTokenPtr,
process.StartInfo.FileName,
process.StartInfo.Arguments,
null,
null,
false,
processFlags,
environmentPtr,
null,
ref startupInfo,
out processInfo
))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Free used resources.
Kernel32.CloseHandle(processInfo.hProcess);
Kernel32.CloseHandle(processInfo.hThread);
if (userTokenPtr != null)
{
Kernel32.CloseHandle(userTokenPtr);
}
if (duplicateUserTokenPtr != null)
{
Kernel32.CloseHandle(duplicateUserTokenPtr);
}
if (environmentPtr != null)
{
UserEnv.DestroyEnvironmentBlock(environmentPtr);
}
}
}
And here's how the code is invoked:
var process = new Process();
process.StartInfo = new ProcessStartInfo { FileName = #"C:\path-to\target.exe", Arguments = "-arg1 -arg2" };
process.StartAsActiveUser();
Hope it helps!
Initially I assumed the best way would be to monitor/restart the process from a Windows service...
Sure you can!
I did it some times ago.
You can start learning how watching this:
http://msdn.microsoft.com/en-us/windows7trainingcourse_win7session0isolation_topic2#_Toc243675529
and this:
http://www.codeproject.com/Articles/18367/Launch-your-application-in-Vista-under-the-local-s
In substance, you have to run programs as SYSTEM, but with the SessionID of the current user.
If you're feeling lazy, I suppose there could be some good little Services which make the thing you're looking for. Try searching on www.codeproject.com.
The watchdog process could make use of System.Diagnostics.Process to launch the application, use the WaitForExitMethod() and check the ExitCode property.
In response to the complaints over the question, I have had to use such a method when working with a legacy call center application over which I had no source control access.
EDIT:
For the host application you could use a .NET application of output type "Windows Application"
and simply not have a form at all. For example:
namespace WindowsFormsApplication1
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
var info = new ProcessStartInfo(#"calc.exe");
var process = Process.Start(info);
process.WaitForExit();
MessageBox.Show("Hello World!");
}
}
}
Found this lib written up on Code Project:
https://www.codeproject.com/Tips/1054098/Simple-Csharp-Watchdog
It was posted 3 years after the latest answer here, so adding it for record's sake.
-- Addendum:
Installed it in our app, and it works pretty well. Needed slight tweaking to support our use case, but the code is pretty solid and straight forward

Categories