Related
I am writing a module in C# which needs to retrieve the effective rights on a resource for a given Active Directory user account. I'm attempting to pinvoke the GetEffectiveRightsFromAcl C function to do this. The function is returning an exception:
System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
From my extremely limited knowledge of unmanaged programming, I'm lead to believe that maybe one of the pointers I'm passing into the function (or the TRUSTEE struct) isn't actually pointing to the place in memory that I think it does.
Here's my code:
class Program {
const Int32 NO_MULTIPLE_TRUSTEE = 0;
const Int32 TRUSTEE_IS_SID = 0;
const Int32 TRUSTEE_IS_USER = 1;
[DllImport("advapi32.dll", SetLastError = true)]
static extern UInt32 GetEffectiveRightsFromAcl(
IntPtr pAcl,
ref TRUSTEE pTrustee,
ref Int32 pAclRights);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
struct TRUSTEE {
public IntPtr pMultipleTrustee;
public Int32 MultipleTrusteeOperation;
public Int32 TrusteeForm;
public Int32 TrusteeType;
[MarshalAs(UnmanagedType.LPStr)]
public String ptstrName;
}
static void Main(string[] args) {
var SID = new WindowsIdentity("company\user1").user ?? throw new ArgumentException("User does not exist");
IntPtr fileACLHandle = getFileSecurityHandle("C:\temp\test.txt"); //Confirmed working via the pinvoked GetNamedSecurityInfo C function
var trustee = new TRUSTEE {
pMultipleTrustee = IntPtr.Zero,
MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE,
TrusteeForm = TRUSTEE_IS_SID,
TrusteeType = TRUSTEE_IS_USER,
ptstrName = SID.Value
};
Int32 pAclRights = 0;
UInt32 result = GetEffectiveRightsFromAcl(fileACLHandle, ref trustee, ref pAclRights);
if (result != 0) {
Int32 hResult = Marshal.GetLastWin32Error();
var ex = new Win32Exception(hResult);
Console.WriteLine(ex.ToString());
return;
}
Console.WriteLine($"Rights: {pAclRights}");
}
}
Thanks in advance for any help!
The problem was my lack of understanding about marshalling pointers between managed and unmanaged memory. I had a method, which I didn't post because I didn't think it was relevant, that was returning an IntPtr handle, however, I was destroying the handle in that same method!
static IntPtr getHandle(Byte[] bytes) {
IntPtr handle = Marshal.AllocHGlobal(bytes.Length);
Marshal.Copy(bytes, 0, handle, bytes.Length);
//Marshal.FreeHGlobal(handle); <-- Destroys the pointer! Don't do this yet.
return handle;
}
This explains the Attempted to read or write protected memory. message: I had freed up the memory that my handle was pointing to before I actually used it!
In terms of P/Invoke, my declaration was a bit off as well. Here is what worked:
const Int32 NO_MULTIPLE_TRUSTEE = 0;
const Int32 TRUSTEE_IS_NAME = 1;
const Int32 TRUSTEE_IS_USER = 1;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct TRUSTEE {
public IntPtr pMultipleTrustee;
public Int32 MultipleTrusteeOperation;
public Int32 TrusteeForm;
public Int32 TrusteeType;
[MarshalAs(UnmanagedType.LPTStr)]
public String ptstrName;
}
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern Int32 GetEffectiveRightsFromAcl(
IntPtr pAcl,
ref TRUSTEE pTrustee,
out Int32 pAclRights);
//-----And later on-----//
Int32 pAclRights = 0;
try {
var trustee = new TRUSTEE {
pMultipleTrustee = IntPtr.Zero,
MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE,
TrusteeForm = TRUSTEE_IS_NAME,
TrusteeType = TRUSTEE_IS_USER,
ptstrName = userName
};
Int32 hResult = GetEffectiveRightsFromAcl(keySecurityHandle, ref trustee, out pAclRights);
if (hResult != 0) {
throw new Win32Exception(Marshal.GetLastWin32Error());
}
} catch (Exception ex) {
//do something with the exception
} finally {
//Lastly, deallocate the unmanaged pointer
Marshal.FreeHGlobal(keySecurityHandle);
}
The final thing I should point out is that I was doing extra work to translate a user ID into a SID. It is far easier to pass a userID, which the GetEffectiveRightsFromAcl function is able to resolve in virtually any format.
There are quite a number of issues with your code.
Structure packing (in general, you should not specify it, defaults are consistent between .NET and Win32)
Ansi vs Unicode
Error handling (GetLastError is not used by these APIs)
Here's a version that seems to work:
var identity = WindowsIdentity.GetCurrent(); // get some identity
var status = GetNamedSecurityInfo(#"c:\temp\file.txt",
SE_OBJECT_TYPE.SE_FILE_OBJECT,
SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | SECURITY_INFORMATION.UNPROTECTED_DACL_SECURITY_INFORMATION,
IntPtr.Zero,
IntPtr.Zero,
out var fileACLHandle,
IntPtr.Zero,
IntPtr.Zero);
if (status != 0)
throw new Win32Exception(status);
var nameTrustee = new TRUSTEE_WITH_NAME
{
TrusteeForm = TRUSTEE_FORM.TRUSTEE_IS_NAME,
TrusteeType = TRUSTEE_TYPE.TRUSTEE_IS_USER,
ptstrName = identity.Name
};
status = GetEffectiveRightsFromAcl(fileACLHandle, ref nameTrustee, out var accessMask);
if (status != 0)
throw new Win32Exception(status);
Console.WriteLine($"Rights: {accessMask}");
var sid = new byte[identity.User.BinaryLength];
identity.User.GetBinaryForm(sid, 0);
var sidTrustee = new TRUSTEE_WITH_SID
{
TrusteeForm = TRUSTEE_FORM.TRUSTEE_IS_SID,
TrusteeType = TRUSTEE_TYPE.TRUSTEE_IS_USER,
pSid = Marshal.UnsafeAddrOfPinnedArrayElement(sid, 0)
};
status = GetEffectiveRightsFromAcl(fileACLHandle, ref sidTrustee, out accessMask);
if (status != 0)
throw new Win32Exception(status);
Console.WriteLine($"Rights: {accessMask}");
...
public enum SE_OBJECT_TYPE
{
SE_UNKNOWN_OBJECT_TYPE,
SE_FILE_OBJECT,
SE_SERVICE,
SE_PRINTER,
SE_REGISTRY_KEY,
SE_LMSHARE,
SE_KERNEL_OBJECT,
SE_WINDOW_OBJECT,
SE_DS_OBJECT,
SE_DS_OBJECT_ALL,
SE_PROVIDER_DEFINED_OBJECT,
SE_WMIGUID_OBJECT,
SE_REGISTRY_WOW64_32KEY
}
[Flags]
public enum SECURITY_INFORMATION
{
OWNER_SECURITY_INFORMATION = 0x00000001,
GROUP_SECURITY_INFORMATION = 0x00000002,
DACL_SECURITY_INFORMATION = 0x00000004,
SACL_SECURITY_INFORMATION = 0x00000008,
UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000,
UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000,
PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000,
PROTECTED_DACL_SECURITY_INFORMATION = unchecked((int)0x80000000)
}
public enum MULTIPLE_TRUSTEE_OPERATION
{
NO_MULTIPLE_TRUSTEE,
TRUSTEE_IS_IMPERSONATE
}
public enum TRUSTEE_FORM
{
TRUSTEE_IS_SID,
TRUSTEE_IS_NAME,
TRUSTEE_BAD_FORM,
TRUSTEE_IS_OBJECTS_AND_SID,
TRUSTEE_IS_OBJECTS_AND_NAME
}
public enum TRUSTEE_TYPE
{
TRUSTEE_IS_UNKNOWN,
TRUSTEE_IS_USER,
TRUSTEE_IS_GROUP,
TRUSTEE_IS_DOMAIN,
TRUSTEE_IS_ALIAS,
TRUSTEE_IS_WELL_KNOWN_GROUP,
TRUSTEE_IS_DELETED,
TRUSTEE_IS_INVALID,
TRUSTEE_IS_COMPUTER
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct TRUSTEE_WITH_NAME
{
public IntPtr pMultipleTrustee;
public MULTIPLE_TRUSTEE_OPERATION MultipleTrusteeOperation;
public TRUSTEE_FORM TrusteeForm;
public TRUSTEE_TYPE TrusteeType;
public string ptstrName;
}
[StructLayout(LayoutKind.Sequential)]
public struct TRUSTEE_WITH_SID
{
public IntPtr pMultipleTrustee;
public MULTIPLE_TRUSTEE_OPERATION MultipleTrusteeOperation;
public TRUSTEE_FORM TrusteeForm;
public TRUSTEE_TYPE TrusteeType;
public IntPtr pSid;
}
[DllImport("advapi32", CharSet = CharSet.Unicode)]
public static extern int GetNamedSecurityInfo(string pObjectName, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMATION SecurityInfo, IntPtr pSidOwner, IntPtr pSidGroup, out IntPtr pDacl, IntPtr pSacl, IntPtr pSecurityDescriptor);
[DllImport("advapi32", CharSet = CharSet.Unicode)]
public static extern int GetEffectiveRightsFromAcl(IntPtr pacl, ref TRUSTEE_WITH_NAME pTrustee, out int pAccessRights);
[DllImport("advapi32", CharSet = CharSet.Unicode)]
public static extern int GetEffectiveRightsFromAcl(IntPtr pacl, ref TRUSTEE_WITH_SID pTrustee, out int pAccessRights);
I wrote code that can write and read data from physical disk.
I open the physical disk using pInvoke CreateFile and use FileStream to perform read and write data.
When physical disk is online, every thing works great.
In case the physical disk is offline and I try to write to disk, I get an error
System.IO.IOException: 'The media is write protected.'
How can I detect if disk is offline without trying to write to disk.
Here is the code that create the FileStream and perform writes to disk
private const uint GENERIC_READ = 0x80000000;
private const uint GENERIC_WRITE = 0x40000000;
private const uint OPEN_EXISTING = 3;
private const uint FILE_FLAG_NO_BUFFERING = 0x20000000;
private const uint FILE_FLAG_WRITE_THROUGH = 0x80000000;
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess,
uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition,
uint dwFlagsAndAttributes, IntPtr hTemplateFile);
public void OpenFile()
{
m_handleValue = CreateFile(DevicePath, GENERIC_WRITE | GENERIC_READ,
0x3, IntPtr.Zero, OPEN_EXISTING,
FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH
, IntPtr.Zero);
m_fileStream = new FileStream(m_handleValue, FileAccess.ReadWrite, 512);
}
public void WriteData(int offset, byte[] data)
{
m_fileStream.Seek(offset, SeekOrigin.Begin);
m_fileStream.Write(data, 0, data.Length);
}
Using DeviceIoControl pInvoke we can review attribute DISK_ATTRIBUTE_OFFLINE
public static class RawDeviceInfoProvider
{
private const int IOCTL_DISK_GET_DISK_ATTRIBUTES = 0x000700F0;
private const uint DISK_ATTRIBUTE_OFFLINE = 0x1;
[StructLayout(LayoutKind.Sequential)]
public struct GetDiskAttributes
{
public uint Version;
public uint Reserved;
public ulong Attributes;
}
[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool DeviceIoControl(SafeHandle hDevice, uint dwIoControlCode, IntPtr lpInBuffer,
uint nInBufferSize, IntPtr lpOutBuffer, uint nOutBufferSize, out uint lpBytesReturned, IntPtr lpOverlapped);
public static bool GetOnlineStatus(SafeFileHandle hDevice)
{
uint dummy;
GetDiskAttributes attributes = new GetDiskAttributes();
IntPtr lpOutBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(attributes));
bool success = DeviceIoControl(hDevice, IOCTL_DISK_GET_DISK_ATTRIBUTES, IntPtr.Zero, 0,
lpOutBuffer, (uint)Marshal.SizeOf(typeof(GetDiskAttributes)), out dummy, IntPtr.Zero);
attributes = (GetDiskAttributes)Marshal.PtrToStructure(lpOutBuffer, typeof(GetDiskAttributes));
Marshal.FreeHGlobal(lpOutBuffer);
if (!success)
{
int errorCode = Marshal.GetLastWin32Error();
throw new IOException("Unable to retrieve disk attributes, Error: " + errorCode);
}
bool isOffline = (attributes.Attributes & DISK_ATTRIBUTE_OFFLINE) > 0;
return !isOffline;
}
}
I have a very old VB6 code that is used to generate the hash for the password. The code uses CryptAcquireContext function along with advapi32.dll to generate the Hash. There is so much code with variables having hex values etc. The code will take forever to migrate to ASP.NET.
We have lots of data encrypted using this Hash code and we don't have a way to decrypt it back to plain text.
I need to write similar code in ASP.NET C# that generates the same hash as the VB6 code does.
Example: Look at the picture below on how it generates HASH from plaintext:
Working C# Code in Windows forms only with exception that CryptAcquireContext returns false when the program is run second time:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security;
using System.Web;
using System.Security.Cryptography;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace CryptoGraphicHash
{
public partial class Form1 : Form
{
static uint CRYPT_NEWKEYSET = 0x8;
static uint CRYPT_MACHINE_KEYSET = 0x20;
static uint ALG_CLASS_HASH = 32768;
// Algorithm types
static uint ALG_TYPE_ANY = 0;
static uint PROV_RSA_FULL = 1;
static uint ALG_SID_SHA = 4;
static string MS_DEF_PROV = "Microsoft Base Cryptographic Provider v1.0";
static uint CALG_SHA = ALG_CLASS_HASH + ALG_TYPE_ANY + ALG_SID_SHA;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var test = GenerateHash(textBox1.Text);
textBox2.Text = test;
}
private string GenerateHash(string plaintext)
{
string sContainer = string.Empty;
string sProvider = MS_DEF_PROV;
IntPtr hProv = new IntPtr();
IntPtr hKey = new IntPtr(0);
IntPtr phHash = new IntPtr();
try
{
bool res = Crypt32.CryptAcquireContext(out hProv, sContainer, sProvider, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET);
if (!res)
{
bool res1 = Crypt32.CryptAcquireContext(out hProv, sContainer, sProvider, PROV_RSA_FULL, CRYPT_NEWKEYSET);
if (!res1)
{
MessageBox.Show("CryptAcquireContext is false for second time so exiting the hash.");
var error = Marshal.GetLastWin32Error();
Win32Exception test = new Win32Exception(error);
MessageBox.Show("Last Win32 error code: " + error);
MessageBox.Show("Last Win32 error msg: " + test.Message);
return string.Empty;
}
}
MessageBox.Show("hProv handle value is: " + hProv.ToString());
//Once we have received the context, next we create hash object
bool hashCreateResponse = Crypt32.CryptCreateHash(hProv, CALG_SHA, hKey, 0, ref phHash);
if (!hashCreateResponse)
{
MessageBox.Show("CryptCreateHash is false so exiting with last win32 error: " + Marshal.GetLastWin32Error());
return string.Empty;
}
//Hash the data
byte[] pbData = Encoding.ASCII.GetBytes(plaintext);
bool hashDataResponse = Crypt32.CryptHashData(phHash, pbData, (uint)plaintext.Length, 0);
if (!hashDataResponse)
{
MessageBox.Show("CryptHashData is false so exiting.");
return string.Empty;
}
uint paramLen = 0;
byte[] paramValue = new byte[0];
bool getHashParamResponse = Crypt32.CryptGetHashParam(phHash, 0x0002, paramValue, ref paramLen, 0);
if (234 == Marshal.GetLastWin32Error())
{
paramValue = new byte[paramLen];
bool getHashParamResponse1 = Crypt32.CryptGetHashParam(phHash, 0x0002, paramValue, ref paramLen, 0);
}
//destroy the key
Crypt32.CryptDestroyKey(hKey);
//Destroy the hash object
Crypt32.CryptDestroyHash(phHash);
//Release provider handle
Crypt32.CryptReleaseContext(hProv, 0);
var sb = new StringBuilder();
foreach(var item in paramValue)
{
sb.Append(Microsoft.VisualBasic.Strings.Chr(item));
}
return sb.ToString();
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
MessageBox.Show(ex.InnerException.StackTrace);
throw ex;
}
}
}
public class Crypt32
{
public enum HashParameters
{
HP_ALGID = 0x0001, // Hash algorithm
HP_HASHVAL = 0x2, // Hash value
HP_HASHSIZE = 0x0004 // Hash value size
}
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptAcquireContext(
out IntPtr phProv,
string pszContainer,
string pszProvider,
uint dwProvType,
uint dwFlags);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CryptCreateHash(IntPtr hProv, uint algId, IntPtr hKey, uint dwFlags, ref IntPtr phHash);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool CryptDestroyHash(IntPtr hHash);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool CryptDestroyKey(IntPtr phKey);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool CryptHashData(IntPtr hHash, byte[] pbData, uint dataLen, uint flags);
[DllImport("Advapi32.dll", EntryPoint = "CryptReleaseContext", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CryptReleaseContext(IntPtr hProv,Int32 dwFlags);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CryptGetHashParam(IntPtr hHash,
uint dwParam,
Byte[] pbData,
ref uint pdwDataLen,
uint dwFlags);
//public static extern bool CryptGetHashParam(IntPtr hHash, uint dwParam, [Out] byte[] pbData, [In, Out] uint pdwDataLen, uint dwFlags);
}
}
You might consider using Platform Invoke Services (PInvoke) to call advapi32.dll functions from .NET. This might speed up the migration process.
You can find signatures on http://pinvoke.net
So finally with help of Joe's suggestion, PInvoke documentation and some code tweaks found on stack over flow, I was able to create a successful working windows form application that has 2 textboxes and a button. Enter plaintext in first textbox and click the button to get hash value in the second textbox. The complete code is:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security;
using System.Web;
using System.Security.Cryptography;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace CryptoGraphicHash
{
public partial class Form1 : Form
{
static uint CRYPT_NEWKEYSET = 0x8;
static uint CRYPT_MACHINE_KEYSET = 0x20;
static uint ALG_CLASS_HASH = 32768;
// Algorithm types
static uint ALG_TYPE_ANY = 0;
static uint PROV_RSA_FULL = 1;
static uint ALG_SID_SHA = 4;
static string MS_DEF_PROV = "Microsoft Base Cryptographic Provider v1.0";
static uint CALG_SHA = ALG_CLASS_HASH + ALG_TYPE_ANY + ALG_SID_SHA;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var test = GenerateHash(textBox1.Text);
textBox2.Text = test;
}
private void HandleWin32Error()
{
var error = Marshal.GetLastWin32Error();
Win32Exception ex = new Win32Exception(error);
MessageBox.Show("Last Win32 error code: " + error);
MessageBox.Show("Last Win32 error msg: " + ex.Message);
}
private string GenerateHash(string plaintext)
{
string sContainer = string.Empty;
string sProvider = MS_DEF_PROV;
IntPtr hProv = new IntPtr();
IntPtr hKey = new IntPtr(0);
IntPtr phHash = new IntPtr();
try
{
bool res = Crypt32.CryptAcquireContext(out hProv, sContainer, sProvider, PROV_RSA_FULL, 0);
if (!res)
{
MessageBox.Show("CryptAcquireContext is false for first time so will try again with CRYPT_NEWKEYSET.");
HandleWin32Error();
bool res1 = Crypt32.CryptAcquireContext(out hProv, sContainer, sProvider, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET + CRYPT_NEWKEYSET);
if (!res1)
{
MessageBox.Show("CryptAcquireContext is false for second time so exiting the hash.");
HandleWin32Error();
return string.Empty;
}
}
MessageBox.Show("hProv handle value is: " + hProv.ToString());
//Once we have received the context, next we create hash object
bool hashCreateResponse = Crypt32.CryptCreateHash(hProv, CALG_SHA, hKey, 0, ref phHash);
if (!hashCreateResponse)
{
MessageBox.Show("CryptCreateHash is false so exiting with last win32 error: " + Marshal.GetLastWin32Error());
return string.Empty;
}
//Hash the data
byte[] pbData = Encoding.ASCII.GetBytes(plaintext);
bool hashDataResponse = Crypt32.CryptHashData(phHash, pbData, (uint)plaintext.Length, 0);
if (!hashDataResponse)
{
MessageBox.Show("CryptHashData is false so exiting.");
return string.Empty;
}
uint paramLen = 0;
byte[] paramValue = new byte[0];
bool getHashParamResponse = Crypt32.CryptGetHashParam(phHash, 0x0002, paramValue, ref paramLen, 0);
if (234 == Marshal.GetLastWin32Error())
{
paramValue = new byte[paramLen];
bool getHashParamResponse1 = Crypt32.CryptGetHashParam(phHash, 0x0002, paramValue, ref paramLen, 0);
}
//destroy the key
Crypt32.CryptDestroyKey(hKey);
//Destroy the hash object
Crypt32.CryptDestroyHash(phHash);
//Release provider handle
Crypt32.CryptReleaseContext(hProv, 0);
var sb = new StringBuilder();
foreach(var item in paramValue)
{
sb.Append(Microsoft.VisualBasic.Strings.Chr(item));
}
return sb.ToString();
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
MessageBox.Show(ex.InnerException.StackTrace);
throw ex;
}
}
}
public class Crypt32
{
public enum HashParameters
{
HP_ALGID = 0x0001, // Hash algorithm
HP_HASHVAL = 0x2, // Hash value
HP_HASHSIZE = 0x0004 // Hash value size
}
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptAcquireContext(
out IntPtr phProv,
string pszContainer,
string pszProvider,
uint dwProvType,
uint dwFlags);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CryptCreateHash(IntPtr hProv, uint algId, IntPtr hKey, uint dwFlags, ref IntPtr phHash);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool CryptDestroyHash(IntPtr hHash);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool CryptDestroyKey(IntPtr phKey);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool CryptHashData(IntPtr hHash, byte[] pbData, uint dataLen, uint flags);
[DllImport("Advapi32.dll", EntryPoint = "CryptReleaseContext", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CryptReleaseContext(IntPtr hProv,Int32 dwFlags);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CryptGetHashParam(IntPtr hHash,
uint dwParam,
Byte[] pbData,
ref uint pdwDataLen,
uint dwFlags);
//public static extern bool CryptGetHashParam(IntPtr hHash, uint dwParam, [Out] byte[] pbData, [In, Out] uint pdwDataLen, uint dwFlags);
}
}
I have the following problem:
From a service I need to start an application in a user session. No human user log on that machine, since it is a server. Launched application must have a session != 0.
Current "solution"
I used a scheduled task at machine startup, that task launch ( in session 0, of course ) an application launching a Remote Desktop logon on the same machine: this creates a user session > 0 and in the user startup the is the final application to launch. It works, but too tricky.
Is there some smartest way? It is critical that I can reuse a user session already on since there is potentially no user logged on.
MAJOR UPDATE
Well after a lot of research and partial successes, and also thanks to some SysAdmin inflexibility about creating an user for a specific pourpose, I decided to use OpenGL instead of WPF for render the 3d portion broken in Session 0.
Surprisingly it took less than expected. I think having this question as a reference could be useful to other who want try to render a Viewport3D from a service.
I'm not sure if this will work, but maybe this answer helps in your case.
Use the class from the answer I link i provided and the following method (with the appropriate values):
public static void EnableVideoDrivers(bool enable)
{
// every type of device has a hard-coded GUID, put here the one for
// video drivers
Guid videoGuid = new Guid("{device GUID}");
// get this from the properties dialog box of this device in Device Manager
string instancePath = #"Device Instance Path";
DeviceHelper.SetDeviceEnabled(videoGuid, instancePath, enable);
}
Here's a list of Popular Device Class GUIDs.
I'm not sure I understand correctly your needs, but maybe just starting process with given credentials and redirect input and output is what you need. Starting process with given credentials:
Process p = new Process();
p.StartInfo = new ProcessStartInfo(fileName, args);
p.StartInfo.UserName = userName;
p.StartInfo.Password = pass;
p.Start();
You may also need to redirect input and output of the application. That problem is well described on CodeProjecgt in this artice.
This is how I start a process for a particular usersession from a Local windows service.
It uses C#, with some DLL imports from kernel32.dll, wtsaspi.dll, userev.dll, and advapi32.dll.
For context, my code will search all user sessions. In my scenario, my service is running on a Windows Terminal server and wants to keep a particular app "alive" in each user's session. Meaning, if we check and its not running anymore, we restart it.
Here is the program logic (abbreviated), this is how you call the method that starts the user process:
foreach(var sesh in ProcessExtensions.GetSessions().Where(r => r.State == "Active").ToList())
{
var running = procs.Any(r => r.ProcessName == filename && r.SessionId == sesh.SessionId);
if (!running)
{
try
{
ProcessExtensions.StartProcessForSession(sesh.SessionId, (string)item, "/restart", System.IO.Path.GetDirectoryName((string)item), true);
}
catch (Exception ex)
{
Trace.TraceWarning("Error: {0}", ex);
}
}
}
Here is the implementation of ProcessExtensions where all of the good stuff is.
Disclaimer - I did not write this code, This is an example I found online and adjusted it to my needs. If you authored the original post. Apologies for the lack of footnote.
ProcessExtensions.cs
public static class ProcessExtensions
{
#region Win32 Constants
private const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;
private const int CREATE_NO_WINDOW = 0x08000000;
private const int CREATE_NEW_CONSOLE = 0x00000010;
private const uint INVALID_SESSION_ID = 0xFFFFFFFF;
private static readonly IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;
#endregion
#region DllImports
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
private static extern bool CreateProcessAsUser(
IntPtr hToken,
String lpApplicationName,
String lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandle,
uint dwCreationFlags,
IntPtr lpEnvironment,
String lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
private static extern bool DuplicateTokenEx(
IntPtr ExistingTokenHandle,
uint dwDesiredAccess,
IntPtr lpThreadAttributes,
int TokenType,
int ImpersonationLevel,
ref IntPtr DuplicateTokenHandle);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
[DllImport("userenv.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hSnapshot);
[DllImport("kernel32.dll")]
private static extern uint WTSGetActiveConsoleSessionId();
[DllImport("Wtsapi32.dll")]
private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken);
[DllImport("wtsapi32.dll", SetLastError = true)]
private static extern int WTSEnumerateSessions(
IntPtr hServer,
int Reserved,
int Version,
ref IntPtr ppSessionInfo,
ref int pCount);
#endregion
#region Win32 Structs
private enum SW
{
SW_HIDE = 0,
SW_SHOWNORMAL = 1,
SW_NORMAL = 1,
SW_SHOWMINIMIZED = 2,
SW_SHOWMAXIMIZED = 3,
SW_MAXIMIZE = 3,
SW_SHOWNOACTIVATE = 4,
SW_SHOW = 5,
SW_MINIMIZE = 6,
SW_SHOWMINNOACTIVE = 7,
SW_SHOWNA = 8,
SW_RESTORE = 9,
SW_SHOWDEFAULT = 10,
SW_MAX = 10
}
private enum WTS_CONNECTSTATE_CLASS
{
WTSActive,
WTSConnected,
WTSConnectQuery,
WTSShadow,
WTSDisconnected,
WTSIdle,
WTSListen,
WTSReset,
WTSDown,
WTSInit
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
private enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3,
}
[StructLayout(LayoutKind.Sequential)]
private struct STARTUPINFO
{
public int cb;
public String lpReserved;
public String lpDesktop;
public String lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
private enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation = 2
}
[StructLayout(LayoutKind.Sequential)]
private struct WTS_SESSION_INFO
{
public readonly UInt32 SessionID;
[MarshalAs(UnmanagedType.LPStr)]
public readonly String pWinStationName;
public readonly WTS_CONNECTSTATE_CLASS State;
}
#endregion
public static IEnumerable<UserSessionData> GetSessions()
{
//var bResult = false;
var hImpersonationToken = IntPtr.Zero;
//var activeSessionId = INVALID_SESSION_ID;
var pSessionInfo = IntPtr.Zero;
var sessionCount = 0;
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0)
{
var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
var current = pSessionInfo;
for (var i = 0; i < sessionCount; i++)
{
var si = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO));
current += arrayElementSize;
var data = new UserSessionData
{
SessionId = (int)si.SessionID,
State = si.State.ToString().Substring(3),
Name = si.pWinStationName
};
yield return data;
}
}
}
private static bool GetUserTokenForSession(int sessionId, ref IntPtr phUserToken)
{
var bResult = false;
var hImpersonationToken = IntPtr.Zero;
var pSessionInfo = IntPtr.Zero;
if (WTSQueryUserToken((uint)sessionId, ref hImpersonationToken) != 0)
{
// Convert the impersonation token to a primary token
bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary,
ref phUserToken);
CloseHandle(hImpersonationToken);
}
return bResult;
}
private static bool GetCurrentUserSessionToken(ref IntPtr phUserToken)
{
var bResult = false;
var hImpersonationToken = IntPtr.Zero;
var activeSessionId = INVALID_SESSION_ID;
var pSessionInfo = IntPtr.Zero;
var sessionCount = 0;
// Get a handle to the user access token for the current active session.
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0)
{
var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
var current = pSessionInfo;
for (var i = 0; i < sessionCount; i++)
{
var si = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO));
current += arrayElementSize;
if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive)
{
activeSessionId = si.SessionID;
}
}
}
// If enumerating did not work, fall back to the old method
if (activeSessionId == INVALID_SESSION_ID)
{
activeSessionId = WTSGetActiveConsoleSessionId();
}
if (WTSQueryUserToken(activeSessionId, ref hImpersonationToken) != 0)
{
// Convert the impersonation token to a primary token
bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary,
ref phUserToken);
CloseHandle(hImpersonationToken);
}
return bResult;
}
public static bool StartProcessForSession(int sessionId, string appPath, string cmdLine = null, string workDir = null, bool visible = true)
{
var hUserToken = IntPtr.Zero;
var startInfo = new STARTUPINFO();
var procInfo = new PROCESS_INFORMATION();
var pEnv = IntPtr.Zero;
int iResultOfCreateProcessAsUser;
startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO));
try
{
if (!GetUserTokenForSession(sessionId, ref hUserToken))
{
throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed.");
}
uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW);
startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE);
startInfo.lpDesktop = "winsta0\\default";
if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false))
{
throw new Exception("StartProcessInSession: CreateEnvironmentBlock failed.");
}
if (!CreateProcessAsUser(hUserToken,
appPath, // Application Name
cmdLine, // Command Line
IntPtr.Zero,
IntPtr.Zero,
false,
dwCreationFlags,
pEnv,
workDir, // Working directory
ref startInfo,
out procInfo))
{
iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed. Error Code -" + iResultOfCreateProcessAsUser);
}
iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
}
finally
{
CloseHandle(hUserToken);
if (pEnv != IntPtr.Zero)
{
DestroyEnvironmentBlock(pEnv);
}
CloseHandle(procInfo.hThread);
CloseHandle(procInfo.hProcess);
}
return true;
}
public static bool StartProcessAsCurrentUser(string appPath, string cmdLine = null, string workDir = null, bool visible = true)
{
var hUserToken = IntPtr.Zero;
var startInfo = new STARTUPINFO();
var procInfo = new PROCESS_INFORMATION();
var pEnv = IntPtr.Zero;
int iResultOfCreateProcessAsUser;
startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO));
try
{
if (!GetCurrentUserSessionToken(ref hUserToken))
{
throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed.");
}
uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW);
startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE);
startInfo.lpDesktop = "winsta0\\default";
if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false))
{
throw new Exception("StartProcessAsCurrentUser: CreateEnvironmentBlock failed.");
}
if (!CreateProcessAsUser(hUserToken,
appPath, // Application Name
cmdLine, // Command Line
IntPtr.Zero,
IntPtr.Zero,
false,
dwCreationFlags,
pEnv,
workDir, // Working directory
ref startInfo,
out procInfo))
{
iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed. Error Code -" + iResultOfCreateProcessAsUser);
}
iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
}
finally
{
CloseHandle(hUserToken);
if (pEnv != IntPtr.Zero)
{
DestroyEnvironmentBlock(pEnv);
}
CloseHandle(procInfo.hThread);
CloseHandle(procInfo.hProcess);
}
return true;
}
}
Do I need to enable Interactive desktp for it to work and what is the correct code to start an EXE or cmd window? I'm still unable to start the service even when I had enable it to interact with desktop.
I would be using an chat engine so it is easier to manage as a windows service.
What wrong with my code?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceProcess;
using System.Diagnostics;
using System.ComponentModel;
using System.Threading;
namespace MyNewService
{
class Program : ServiceBase
{
static void Main(string[] args)
{
}
public Program()
{
this.ServiceName = "Chatter";
}
protected override void OnStart(string[] args)
{
base.OnStart(args);
//TODO: place your start code here
ThreadStart starter = new ThreadStart(bw_DoWork);
Thread t = new Thread(starter);
t.Start();
}
private void bw_DoWork()
{
Process p = new Process();
p.StartInfo = new ProcessStartInfo(#"C:\Windows\system32\cmd.exe");
p.Start();
p.WaitForExit();
base.Stop();
}
protected override void OnStop()
{
base.OnStop();
//TODO: clean up any variables and stop any threads
}
}
}
I have gone through all the pain of doing this.
Under windows 7/Vista/2008 it is not possible to load any interactive process from a service - without calling a number of Win API. = BLACK MAGIC
Have a look here and here.
The code below does the trick, use it with your own risk:
public static class ProcessAsCurrentUser
{
/// <summary>
/// Connection state of a session.
/// </summary>
public enum ConnectionState
{
/// <summary>
/// A user is logged on to the session.
/// </summary>
Active,
/// <summary>
/// A client is connected to the session.
/// </summary>
Connected,
/// <summary>
/// The session is in the process of connecting to a client.
/// </summary>
ConnectQuery,
/// <summary>
/// This session is shadowing another session.
/// </summary>
Shadowing,
/// <summary>
/// The session is active, but the client has disconnected from it.
/// </summary>
Disconnected,
/// <summary>
/// The session is waiting for a client to connect.
/// </summary>
Idle,
/// <summary>
/// The session is listening for connections.
/// </summary>
Listening,
/// <summary>
/// The session is being reset.
/// </summary>
Reset,
/// <summary>
/// The session is down due to an error.
/// </summary>
Down,
/// <summary>
/// The session is initializing.
/// </summary>
Initializing
}
[StructLayout(LayoutKind.Sequential)]
class SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
enum LOGON_TYPE
{
LOGON32_LOGON_INTERACTIVE = 2,
LOGON32_LOGON_NETWORK,
LOGON32_LOGON_BATCH,
LOGON32_LOGON_SERVICE,
LOGON32_LOGON_UNLOCK = 7,
LOGON32_LOGON_NETWORK_CLEARTEXT,
LOGON32_LOGON_NEW_CREDENTIALS
}
enum LOGON_PROVIDER
{
LOGON32_PROVIDER_DEFAULT,
LOGON32_PROVIDER_WINNT35,
LOGON32_PROVIDER_WINNT40,
LOGON32_PROVIDER_WINNT50
}
[Flags]
enum CreateProcessFlags : uint
{
CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
CREATE_NEW_CONSOLE = 0x00000010,
CREATE_NEW_PROCESS_GROUP = 0x00000200,
CREATE_NO_WINDOW = 0x08000000,
CREATE_PROTECTED_PROCESS = 0x00040000,
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
CREATE_SEPARATE_WOW_VDM = 0x00000800,
CREATE_SHARED_WOW_VDM = 0x00001000,
CREATE_SUSPENDED = 0x00000004,
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
DEBUG_ONLY_THIS_PROCESS = 0x00000002,
DEBUG_PROCESS = 0x00000001,
DETACHED_PROCESS = 0x00000008,
EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
INHERIT_PARENT_AFFINITY = 0x00010000
}
[StructLayout(LayoutKind.Sequential)]
public struct WTS_SESSION_INFO
{
public int SessionID;
[MarshalAs(UnmanagedType.LPTStr)]
public string WinStationName;
public ConnectionState State;
}
[DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern Int32 WTSEnumerateSessions(IntPtr hServer, int reserved, int version,
ref IntPtr sessionInfo, ref int count);
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUserW", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
UInt32 dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("wtsapi32.dll")]
public static extern void WTSFreeMemory(IntPtr memory);
[DllImport("kernel32.dll")]
private static extern UInt32 WTSGetActiveConsoleSessionId();
[DllImport("wtsapi32.dll", SetLastError = true)]
static extern int WTSQueryUserToken(UInt32 sessionId, out IntPtr Token);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateTokenEx(
IntPtr hExistingToken,
uint dwDesiredAccess,
IntPtr lpTokenAttributes,
int ImpersonationLevel,
int TokenType,
out IntPtr phNewToken);
private const int TokenImpersonation = 2;
private const int SecurityIdentification = 1;
private const int MAXIMUM_ALLOWED = 0x2000000;
private const int TOKEN_DUPLICATE = 0x2;
private const int TOKEN_QUERY = 0x00000008;
/// <summary>
/// Launches a process for the current logged on user if there are any.
/// If none, return false as well as in case of
///
/// ##### !!! BEWARE !!! #### ------------------------------------------
/// This code will only work when running in a windows service (where it is really needed)
/// so in case you need to test it, it needs to run in the service. Reason
/// is a security privileg which only services have (SE_??? something, cant remember)!
/// </summary>
/// <param name="processExe"></param>
/// <returns></returns>
public static bool CreateProcessAsCurrentUser(string processExe)
{
IntPtr duplicate = new IntPtr();
STARTUPINFO info = new STARTUPINFO();
PROCESS_INFORMATION procInfo = new PROCESS_INFORMATION();
Debug.WriteLine(string.Format("CreateProcessAsCurrentUser. processExe: " + processExe));
IntPtr p = GetCurrentUserToken();
bool result = DuplicateTokenEx(p, MAXIMUM_ALLOWED | TOKEN_QUERY | TOKEN_DUPLICATE, IntPtr.Zero, SecurityIdentification, SecurityIdentification, out duplicate);
Debug.WriteLine(string.Format("DuplicateTokenEx result: {0}", result));
Debug.WriteLine(string.Format("duplicate: {0}", duplicate));
if (result)
{
result = CreateProcessAsUser(duplicate, processExe, null,
IntPtr.Zero, IntPtr.Zero, false, (UInt32)CreateProcessFlags.CREATE_NEW_CONSOLE, IntPtr.Zero, null,
ref info, out procInfo);
Debug.WriteLine(string.Format("CreateProcessAsUser result: {0}", result));
}
if (p.ToInt32() != 0)
{
Marshal.Release(p);
Debug.WriteLine(string.Format("Released handle p: {0}", p));
}
if (duplicate.ToInt32() != 0)
{
Marshal.Release(duplicate);
Debug.WriteLine(string.Format("Released handle duplicate: {0}", duplicate));
}
return result;
}
public static int GetCurrentSessionId()
{
uint sessionId = WTSGetActiveConsoleSessionId();
Debug.WriteLine(string.Format("sessionId: {0}", sessionId));
if (sessionId == 0xFFFFFFFF)
return -1;
else
return (int)sessionId;
}
public static bool IsUserLoggedOn()
{
List<WTS_SESSION_INFO> wtsSessionInfos = ListSessions();
Debug.WriteLine(string.Format("Number of sessions: {0}", wtsSessionInfos.Count));
return wtsSessionInfos.Where(x => x.State == ConnectionState.Active).Count() > 0;
}
private static IntPtr GetCurrentUserToken()
{
List<WTS_SESSION_INFO> wtsSessionInfos = ListSessions();
int sessionId = wtsSessionInfos.Where(x => x.State == ConnectionState.Active).FirstOrDefault().SessionID;
//int sessionId = GetCurrentSessionId();
Debug.WriteLine(string.Format("sessionId: {0}", sessionId));
if (sessionId == int.MaxValue)
{
return IntPtr.Zero;
}
else
{
IntPtr p = new IntPtr();
int result = WTSQueryUserToken((UInt32)sessionId, out p);
Debug.WriteLine(string.Format("WTSQueryUserToken result: {0}", result));
Debug.WriteLine(string.Format("WTSQueryUserToken p: {0}", p));
return p;
}
}
public static List<WTS_SESSION_INFO> ListSessions()
{
IntPtr server = IntPtr.Zero;
List<WTS_SESSION_INFO> ret = new List<WTS_SESSION_INFO>();
try
{
IntPtr ppSessionInfo = IntPtr.Zero;
Int32 count = 0;
Int32 retval = WTSEnumerateSessions(IntPtr.Zero, 0, 1, ref ppSessionInfo, ref count);
Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
Int64 current = (int)ppSessionInfo;
if (retval != 0)
{
for (int i = 0; i < count; i++)
{
WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTS_SESSION_INFO));
current += dataSize;
ret.Add(si);
}
WTSFreeMemory(ppSessionInfo);
}
}
catch (Exception exception)
{
Debug.WriteLine(exception.ToString());
}
return ret;
}
}
When running as a service you won't be able to launch anything that needs to interact with the desktop OR will spawn it's own windows.
As Aliostad said, you need to call Win API calls to CreateProcessAsUser and emulate the user in order with this to work. This involves emulating the logged in user and using their credentials to "lift" your process into process isolation level 1 (which gives you access to the windowing system and things like the GPU).
I am doing this in an app I wrote and it does work but I agree with Aliostad there is a bit of Black magic going on and it generally sucks
Having said all that, you can spawn worker processes from within a service as along as they don't require things that are in process isolation level 1 (Windowing, GPU etc..)
cmd.exe by default will try to create a window, this is why your example is failing. You could set the following ProcessStartInfo properties to get it work.
CreateNoWindow
WindowStyle
I wrote an application watchdog service which simply restarts an application (in my case a Console Window App).
I found a very good Hands-On Lab Tutorial (in C++) which I tried at it worked for Session 0 Isolation at: http://msdn.microsoft.com/en-us/Windows7TrainingCourse_Win7Session0Isolation
I converted that C++ Sample into C#. After a few tests it worked. As long as I stay logged on and don't log out and log on again that code works perfect. I have to do a little to catch the Session Logout/Login. But for simple log-in and Work Condition in Windows the watchdog works as expected.
Here is that required PInvoke code:
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public int cb;
public String lpReserved;
public String lpDesktop;
public String lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
public enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public extern static bool CloseHandle(IntPtr handle);
[DllImport("kernel32.dll")]
public static extern uint WTSGetActiveConsoleSessionId();
[DllImport("wtsapi32.dll", SetLastError = true)]
public static extern bool WTSQueryUserToken(UInt32 sessionId, out IntPtr Token);
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public extern static bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, String lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment,
String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateTokenEx(
IntPtr hExistingToken,
uint dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpTokenAttributes,
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
TOKEN_TYPE TokenType,
out IntPtr phNewToken);
Here is the encapsuled method :
private void CreateUserProcess()
{
bool ret;
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
uint dwSessionID = WTSGetActiveConsoleSessionId();
this.EventLog.WriteEntry("WTSGetActiveConsoleSessionId: " + dwSessionID, EventLogEntryType.FailureAudit);
IntPtr Token = new IntPtr();
ret = WTSQueryUserToken((UInt32)dwSessionID, out Token);
if (ret == false)
{
this.EventLog.WriteEntry("WTSQueryUserToken failed with " + Marshal.GetLastWin32Error(), EventLogEntryType.FailureAudit);
}
const uint MAXIMUM_ALLOWED = 0x02000000;
IntPtr DupedToken = IntPtr.Zero;
ret = DuplicateTokenEx(Token,
MAXIMUM_ALLOWED,
ref sa,
SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
TOKEN_TYPE.TokenPrimary,
out DupedToken);
if (ret == false)
{
this.EventLog.WriteEntry("DuplicateTokenEx failed with " + Marshal.GetLastWin32Error(), EventLogEntryType.FailureAudit);
}
else
{
this.EventLog.WriteEntry("DuplicateTokenEx SUCCESS", EventLogEntryType.SuccessAudit);
}
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
//si.lpDesktop = "";
string commandLinePath;
// commandLinePath example: "c:\myapp.exe c:\myconfig.xml" . cmdLineArgs can be ommited
commandLinePath = AppPath + " " + CmdLineArgs;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
//CreateProcessAsUser(hDuplicatedToken, NULL, lpszClientPath, NULL, NULL, FALSE,
// 0,
// NULL, NULL, &si, &pi)
ret = CreateProcessAsUser(DupedToken, null, commandLinePath, ref sa, ref sa, false, 0, (IntPtr)0, null, ref si, out pi);
if (ret == false)
{
this.EventLog.WriteEntry("CreateProcessAsUser failed with " + Marshal.GetLastWin32Error(), EventLogEntryType.FailureAudit);
}
else
{
this.EventLog.WriteEntry("CreateProcessAsUser SUCCESS. The child PID is" + pi.dwProcessId, EventLogEntryType.SuccessAudit);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
ret = CloseHandle(DupedToken);
if (ret == false)
{
this.EventLog.WriteEntry("CloseHandle LastError: " + Marshal.GetLastWin32Error(), EventLogEntryType.Error);
}
else
{
this.EventLog.WriteEntry("CloseHandle SUCCESS", EventLogEntryType.Information);
}
}
I hope it is useful !
The function below will launch an executable as active user from a windows service.
//Function to run a process as active user from windows service
void ImpersonateActiveUserAndRun(WCHAR* path, WCHAR* args)
{
DWORD session_id = -1;
DWORD session_count = 0;
WTS_SESSION_INFOA *pSession = NULL;
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSession, &session_count))
{
//log success
}
else
{
//log error
return;
}
for (int i = 0; i < session_count; i++)
{
session_id = pSession[i].SessionId;
WTS_CONNECTSTATE_CLASS wts_connect_state = WTSDisconnected;
WTS_CONNECTSTATE_CLASS* ptr_wts_connect_state = NULL;
DWORD bytes_returned = 0;
if (::WTSQuerySessionInformation(
WTS_CURRENT_SERVER_HANDLE,
session_id,
WTSConnectState,
reinterpret_cast<LPTSTR*>(&ptr_wts_connect_state),
&bytes_returned))
{
wts_connect_state = *ptr_wts_connect_state;
::WTSFreeMemory(ptr_wts_connect_state);
if (wts_connect_state != WTSActive) continue;
}
else
{
//log error
continue;
}
HANDLE hImpersonationToken;
if (!WTSQueryUserToken(session_id, &hImpersonationToken))
{
//log error
continue;
}
//Get real token from impersonation token
DWORD neededSize1 = 0;
HANDLE *realToken = new HANDLE;
if (GetTokenInformation(hImpersonationToken, (::TOKEN_INFORMATION_CLASS) TokenLinkedToken, realToken, sizeof(HANDLE), &neededSize1))
{
CloseHandle(hImpersonationToken);
hImpersonationToken = *realToken;
}
else
{
//log error
continue;
}
HANDLE hUserToken;
if (!DuplicateTokenEx(hImpersonationToken,
//0,
//MAXIMUM_ALLOWED,
TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS | MAXIMUM_ALLOWED,
NULL,
SecurityImpersonation,
TokenPrimary,
&hUserToken))
{
//log error
continue;
}
// Get user name of this process
//LPTSTR pUserName = NULL;
WCHAR* pUserName;
DWORD user_name_len = 0;
if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session_id, WTSUserName, &pUserName, &user_name_len))
{
//log username contained in pUserName WCHAR string
}
//Free memory
if (pUserName) WTSFreeMemory(pUserName);
ImpersonateLoggedOnUser(hUserToken);
STARTUPINFOW StartupInfo;
GetStartupInfoW(&StartupInfo);
StartupInfo.cb = sizeof(STARTUPINFOW);
//StartupInfo.lpDesktop = "winsta0\\default";
PROCESS_INFORMATION processInfo;
SECURITY_ATTRIBUTES Security1;
Security1.nLength = sizeof SECURITY_ATTRIBUTES;
SECURITY_ATTRIBUTES Security2;
Security2.nLength = sizeof SECURITY_ATTRIBUTES;
void* lpEnvironment = NULL;
// Get all necessary environment variables of logged in user
// to pass them to the new process
BOOL resultEnv = CreateEnvironmentBlock(&lpEnvironment, hUserToken, FALSE);
if (!resultEnv)
{
//log error
continue;
}
WCHAR PP[1024]; //path and parameters
ZeroMemory(PP, 1024 * sizeof WCHAR);
wcscpy(PP, path);
wcscat(PP, L" ");
wcscat(PP, args);
// Start the process on behalf of the current user
BOOL result = CreateProcessAsUserW(hUserToken,
NULL,
PP,
//&Security1,
//&Security2,
NULL,
NULL,
FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,
//lpEnvironment,
NULL,
//"C:\\ProgramData\\some_dir",
NULL,
&StartupInfo,
&processInfo);
if (!result)
{
//log error
}
else
{
//log success
}
DestroyEnvironmentBlock(lpEnvironment);
CloseHandle(hImpersonationToken);
CloseHandle(hUserToken);
CloseHandle(realToken);
RevertToSelf();
}
WTSFreeMemory(pSession);
}