Related
How would you write this type of struct in c#?
struct _JOBOBJECT_BASIC_PROCESS_ID_LIST {
DWORD NumberOfAssignedProcesses;
DWORD NumberOfProcessIdsInList;
ULONG_PTR ProcessIdList[1];
}
sins there is no set size for the ProcessIdList array, what do you do? Do you just write it like this:
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_PROCESS_ID_LIST
{
int NumberOfAssignedProcesses;
int NumberOfProcessIdsInList;
IntPtr ProcessIdList; //Must point to a allocated array, thanks jdweng for letting me know.
}
or do you just assign a size which is big enough, e.g.:
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_PROCESS_ID_LIST
{
int NumberOfAssignedProcesses;
int NumberOfProcessIdsInList;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_PATH)]
UIntPtr[] ProcessIdList; //Works just fine, but is limited to the SizeConst.
}
This sort of structure is usually declared (there are others like this one in WLan APIs for example) :
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct JOBOBJECT_BASIC_PROCESS_ID_LIST
{
public int NumberOfAssignedProcesses;
public int NumberOfProcessIdsInList;
public IntPtr[] ProcessIdList;
public JOBOBJECT_BASIC_PROCESS_ID_LIST(IntPtr pList)
{
int nIntSize = Marshal.SizeOf<int>(); // 4
NumberOfAssignedProcesses = Marshal.ReadInt32(pList, 0);
NumberOfProcessIdsInList = Marshal.ReadInt32(pList, nIntSize);
ProcessIdList = new IntPtr[NumberOfProcessIdsInList];
for (int i = 0; i < NumberOfProcessIdsInList; i++)
{
IntPtr pItemList = IntPtr.Zero;
if (Marshal.SizeOf<IntPtr>() == 4)
pItemList = new IntPtr(pList.ToInt32() + (i * Marshal.SizeOf<IntPtr>()) + (nIntSize * 2));
else
pItemList = new IntPtr(pList.ToInt64() + (i * Marshal.SizeOf<IntPtr>()) + (nIntSize * 2));
IntPtr nPID = new IntPtr();
nPID = Marshal.ReadIntPtr(pItemList, 0);
ProcessIdList[i] = nPID;
}
}
}
A test with 5 Notepad launched and assigned to a job with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
then QueryInformationJobObject to enumerate the PIDs by using this structure =>
private IntPtr hJob = IntPtr.Zero;
bool bRet = false;
hJob = CreateJobObject(IntPtr.Zero, "Test Job Object");
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jbeli = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
jbeli.BasicLimitInformation.LimitFlags |= (JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK | JOB_OBJECT_LIMIT_BREAKAWAY_OK);
int nLength = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
IntPtr pJobInfo = Marshal.AllocHGlobal(nLength);
Marshal.StructureToPtr(jbeli, pJobInfo, false);
SetInformationJobObject(hJob, JOBOBJECTINFOCLASS.JobObjectExtendedLimitInformation, pJobInfo, (uint)nLength);
Marshal.FreeHGlobal(pJobInfo);
int nNbProcesses = 5;
for (int i = 0; i < nNbProcesses; i++)
{
using (Process exeProcess = new Process())
{
exeProcess.StartInfo.FileName = "notepad";
exeProcess.Start();
exeProcess.WaitForInputIdle();
IntPtr hProcess = exeProcess.Handle;
bRet = AssignProcessToJobObject(hJob, hProcess);
}
}
JOBOBJECT_BASIC_PROCESS_ID_LIST jobpil = new JOBOBJECT_BASIC_PROCESS_ID_LIST();
jobpil.NumberOfAssignedProcesses = nNbProcesses;
int nSize = Marshal.SizeOf<JOBOBJECT_BASIC_PROCESS_ID_LIST>() + (nNbProcesses - 1) * Marshal.SizeOf<IntPtr>();
IntPtr pJobpil = Marshal.AllocHGlobal(nSize);
Marshal.StructureToPtr(jobpil, pJobpil, false);
int nReturnLength = 0;
bRet = QueryInformationJobObject(hJob, JOBOBJECTINFOCLASS.JobObjectBasicProcessIdList, pJobpil, nSize, out nReturnLength);
if (bRet)
{
var processidlist = new JOBOBJECT_BASIC_PROCESS_ID_LIST(pJobpil);
foreach (var pid in processidlist.ProcessIdList)
{
Console.WriteLine("PID: {0}", pid.ToString());
}
}
else
{
int nErr = Marshal.GetLastWin32Error();
Win32Exception win32Exception = new Win32Exception(nErr);
this.Activate();
MessageBox.Show("Error: " + win32Exception.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
Marshal.FreeHGlobal(pJobpil);
// CloseHandle can be added in Form1_FormClosed :
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
CloseHandle(hJob);
}
Declarations =>
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string lpName);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool SetInformationJobObject(IntPtr hJob, JOBOBJECTINFOCLASS JobObjectInfoClass, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool AssignProcessToJobObject(IntPtr hJob, IntPtr hProcess);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool QueryInformationJobObject(IntPtr hJob, JOBOBJECTINFOCLASS JobObjectInformationClass, [Out, MarshalAs(UnmanagedType.SysUInt)] IntPtr lpJobObjectInformation, int cbJobObjectInformationLength, out int lpReturnLength);
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public ulong PerProcessUserTimeLimit;
public ulong PerJobUserTimeLimit;
public int LimitFlags;
public IntPtr MinimumWorkingSetSize;
public IntPtr MaximumWorkingSetSize;
public int ActiveProcessLimit;
public IntPtr Affinity;
public int PriorityClass;
public int SchedulingClass;
}
[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
public ulong ReadOperationCount;
public ulong WriteOperationCount;
public ulong OtherOperationCount;
public ulong ReadTransferCount;
public ulong WriteTransferCount;
public ulong OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public IntPtr ProcessMemoryLimit;
public IntPtr JobMemoryLimit;
public IntPtr PeakProcessMemoryUsed;
public IntPtr PeakJobMemoryUsed;
}
//
// Basic Limits
//
public const int JOB_OBJECT_LIMIT_WORKINGSET = 0x00000001;
public const int JOB_OBJECT_LIMIT_PROCESS_TIME = 0x00000002;
public const int JOB_OBJECT_LIMIT_JOB_TIME = 0x00000004;
public const int JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 0x00000008;
public const int JOB_OBJECT_LIMIT_AFFINITY = 0x00000010;
public const int JOB_OBJECT_LIMIT_PRIORITY_CLASS = 0x00000020;
public const int JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME = 0x00000040;
public const int JOB_OBJECT_LIMIT_SCHEDULING_CLASS = 0x00000080;
//
// Extended Limits
//
public const int JOB_OBJECT_LIMIT_PROCESS_MEMORY = 0x00000100;
public const int JOB_OBJECT_LIMIT_JOB_MEMORY = 0x00000200;
public const int JOB_OBJECT_LIMIT_JOB_MEMORY_HIGH = JOB_OBJECT_LIMIT_JOB_MEMORY;
public const int JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION = 0x00000400;
public const int JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800;
public const int JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK = 0x00001000;
public const int JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000;
public const int JOB_OBJECT_LIMIT_SUBSET_AFFINITY = 0x00004000;
public const int JOB_OBJECT_LIMIT_JOB_MEMORY_LOW = 0x00008000;
public enum JOBOBJECTINFOCLASS
{
JobObjectBasicAccountingInformation = 1,
JobObjectBasicLimitInformation,
JobObjectBasicProcessIdList,
JobObjectBasicUIRestrictions,
JobObjectSecurityLimitInformation, // deprecated
JobObjectEndOfJobTimeInformation,
JobObjectAssociateCompletionPortInformation,
JobObjectBasicAndIoAccountingInformation,
JobObjectExtendedLimitInformation,
JobObjectJobSetInformation,
JobObjectGroupInformation,
JobObjectNotificationLimitInformation,
JobObjectLimitViolationInformation,
JobObjectGroupInformationEx,
JobObjectCpuRateControlInformation,
JobObjectCompletionFilter,
JobObjectCompletionCounter,
JobObjectReserved1Information = 18,
JobObjectReserved2Information,
JobObjectReserved3Information,
JobObjectReserved4Information,
JobObjectReserved5Information,
JobObjectReserved6Information,
JobObjectReserved7Information,
JobObjectReserved8Information,
JobObjectReserved9Information,
JobObjectReserved10Information,
JobObjectReserved11Information,
JobObjectReserved12Information,
JobObjectReserved13Information,
JobObjectReserved14Information = 31,
JobObjectNetRateControlInformation,
JobObjectNotificationLimitInformation2,
JobObjectLimitViolationInformation2,
JobObjectCreateSilo,
JobObjectSiloBasicInformation,
JobObjectReserved15Information = 37,
JobObjectReserved16Information,
JobObjectReserved17Information,
JobObjectReserved18Information,
JobObjectReserved19Information = 41,
JobObjectReserved20Information,
MaxJobObjectInfoClass
}
I think any of the ways you mentioned should work.
In addition, there is a matching feature in c#: Define an array with the fixed keyword:
struct JOBOBJECT_BASIC_PROCESS_ID_LIST
{
int NumberOfAssignedProcesses;
int NumberOfProcessIdsInList;
fixed IntPtr ProcessIdList[1];
}
See documentation:
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/unsafe-code-pointers/fixed-size-buffers
Also no bounds check, so you should be able to read behind the end of the struckt easily:
Note
Except for memory created by using stackalloc, the C# compiler and the common language runtime (CLR) do not perform any security buffer overrun checks. As with all unsafe code, use caution.
I want to find which files is currently opened by Excel, Word of PDF process.
In x64dbg i can see info about process and can see needed file, but C# and WMI looks like do not allow to get such information.
The handle.exe is not very good solution, I do not want to use it and parse data.
So is there any way to do it using C# and WMI, if not, then what Win32 API I can use to find Handles associated with process.
The ntdll.dll ->NtQueryInformationProcess it is allows me to get address of process but how to use it to read Handles?
Thanks to all, I have found a solution.NtQueryObject hang when FileTypePipe
So there is a lot of solutions in the internet but most of them have problem with hanging when getting name for FileTypePipe :)
public class ProcessUtility
{
/// <summary>
/// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle_table_entry.htm?ts=0,242
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct SYSTEM_HANDLE_INFORMATION
{ // Information Class 16
public ushort ProcessID;
public ushort CreatorBackTrackIndex;
public byte ObjectType;
public byte HandleAttribute;
public ushort Handle;
public IntPtr Object_Pointer;
public IntPtr AccessMask;
}
private enum OBJECT_INFORMATION_CLASS : int
{
ObjectBasicInformation = 0,
ObjectNameInformation = 1,
ObjectTypeInformation = 2,
ObjectAllTypesInformation = 3,
ObjectHandleInformation = 4
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct OBJECT_NAME_INFORMATION
{ // Information Class 1
public UNICODE_STRING Name;
}
[StructLayout(LayoutKind.Sequential)]
private struct UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
public IntPtr Buffer;
}
[Flags]
private enum PROCESS_ACCESS_FLAGS : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VMOperation = 0x00000008,
VMRead = 0x00000010,
VMWrite = 0x00000020,
DupHandle = 0x00000040,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
Synchronize = 0x00100000
}
private enum FileType : uint
{
FileTypeChar = 0x0002,
FileTypeDisk = 0x0001,
FileTypePipe = 0x0003,
FileTypeRemote = 0x8000,
FileTypeUnknown = 0x0000,
}
[DllImport("ntdll.dll")]
private static extern uint NtQuerySystemInformation(int SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength, ref int returnLength);
[DllImport("kernel32.dll")]
private static extern IntPtr OpenProcess(PROCESS_ACCESS_FLAGS dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions);
[DllImport("kernel32.dll")]
private static extern IntPtr GetCurrentProcess();
[DllImport("ntdll.dll")]
private static extern int NtQueryObject(IntPtr ObjectHandle, int ObjectInformationClass, IntPtr ObjectInformation, int ObjectInformationLength, ref int returnLength);
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax);
[DllImport("kernel32.dll")]
private static extern bool GetHandleInformation(IntPtr hObject, out uint lpdwFlags);
[DllImport("kernel32.dll")]
private static extern FileType GetFileType(IntPtr hFile);
private const int MAX_PATH = 260;
private const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
private const int DUPLICATE_SAME_ACCESS = 0x2;
private const uint FILE_SEQUENTIAL_ONLY = 0x00000004;
private const int CNST_SYSTEM_HANDLE_INFORMATION = 0x10;
private const int OBJECT_TYPE_FILE = 0x24;
public static List<string> FindFilesByExtension(List<Process> target_processes, List<string> target_extensions)
{
List<string> aFilePaths = new List<string>();
if (target_extensions == null || target_extensions.Count == 0)
{
throw new Exception("Exceptions not defined");
}
foreach (Process process in target_processes)
{
List<string> aProcessFiles = GetPrcessFiles(target_processes);
foreach (string file_path in aProcessFiles)
{
if (target_extensions.Contains(Path.GetExtension(file_path.ToLower()))
&& !Path.GetFileName(file_path).StartsWith("~"))
{
aFilePaths.Add(file_path);
}
}
}
return aFilePaths;
}
public static List<string> GetPrcessFiles(List<Process> target_processes)
{
List<string> aFiles = new List<string>();
foreach (Process process in target_processes)
{
List<SYSTEM_HANDLE_INFORMATION> aHandles = GetFileHandles(process).ToList();
foreach (SYSTEM_HANDLE_INFORMATION handle_info in aHandles)
{
string file_path = GetFilePath(handle_info, process);
if (!string.IsNullOrEmpty(file_path))
{
aFiles.Add(file_path);
}
}
}
return aFiles;
}
private static IEnumerable<SYSTEM_HANDLE_INFORMATION> GetFileHandles(Process process)
{
List<SYSTEM_HANDLE_INFORMATION> aHandles = new List<SYSTEM_HANDLE_INFORMATION>();
int handle_info_size = Marshal.SizeOf(new SYSTEM_HANDLE_INFORMATION()) * 20000;
IntPtr ptrHandleData = IntPtr.Zero;
try
{
ptrHandleData = Marshal.AllocHGlobal(handle_info_size);
int nLength = 0;
while (NtQuerySystemInformation(CNST_SYSTEM_HANDLE_INFORMATION, ptrHandleData, handle_info_size, ref nLength) == STATUS_INFO_LENGTH_MISMATCH)
{
handle_info_size = nLength;
Marshal.FreeHGlobal(ptrHandleData);
ptrHandleData = Marshal.AllocHGlobal(nLength);
}
long handle_count = Marshal.ReadIntPtr(ptrHandleData).ToInt64();
IntPtr ptrHandleItem = ptrHandleData + Marshal.SizeOf(ptrHandleData);
for (long lIndex = 0; lIndex < handle_count; lIndex++)
{
SYSTEM_HANDLE_INFORMATION oSystemHandleInfo = Marshal.PtrToStructure<SYSTEM_HANDLE_INFORMATION>(ptrHandleItem);
ptrHandleItem += Marshal.SizeOf(new SYSTEM_HANDLE_INFORMATION());
if (oSystemHandleInfo.ProcessID != process.Id || oSystemHandleInfo.ObjectType != OBJECT_TYPE_FILE)
{ continue; }
aHandles.Add(oSystemHandleInfo);
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
Marshal.FreeHGlobal(ptrHandleData);
}
return aHandles;
}
private static string GetFilePath(SYSTEM_HANDLE_INFORMATION systemHandleInformation, Process process)
{
IntPtr ipHandle = IntPtr.Zero;
IntPtr openProcessHandle = IntPtr.Zero;
IntPtr hObjectName = IntPtr.Zero;
try
{
PROCESS_ACCESS_FLAGS flags = PROCESS_ACCESS_FLAGS.DupHandle | PROCESS_ACCESS_FLAGS.VMRead;
openProcessHandle = OpenProcess(flags, false, process.Id);
if (!DuplicateHandle(openProcessHandle, new IntPtr(systemHandleInformation.Handle), GetCurrentProcess(), out ipHandle, 0, false, DUPLICATE_SAME_ACCESS))
{
return null;
}
if (GetFileType(ipHandle) != FileType.FileTypeDisk)
{ return null; }
int nLength = 0;
hObjectName = Marshal.AllocHGlobal(256 * 1024);
while ((uint)(NtQueryObject(ipHandle, (int)OBJECT_INFORMATION_CLASS.ObjectNameInformation, hObjectName, nLength, ref nLength)) == STATUS_INFO_LENGTH_MISMATCH)
{
Marshal.FreeHGlobal(hObjectName);
if (nLength == 0)
{
Console.WriteLine("Length returned at zero!");
return null;
}
hObjectName = Marshal.AllocHGlobal(nLength);
}
OBJECT_NAME_INFORMATION objObjectName = Marshal.PtrToStructure<OBJECT_NAME_INFORMATION>(hObjectName);
if (objObjectName.Name.Buffer != IntPtr.Zero)
{
string strObjectName = Marshal.PtrToStringUni(objObjectName.Name.Buffer);
return GetRegularFileNameFromDevice(strObjectName);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Marshal.FreeHGlobal(hObjectName);
CloseHandle(ipHandle);
CloseHandle(openProcessHandle);
}
return null;
}
private static string GetRegularFileNameFromDevice(string strRawName)
{
string strFileName = strRawName;
foreach (string strDrivePath in Environment.GetLogicalDrives())
{
var sbTargetPath = new StringBuilder(MAX_PATH);
if (QueryDosDevice(strDrivePath.Substring(0, 2), sbTargetPath, MAX_PATH) == 0)
{
return strRawName;
}
string strTargetPath = sbTargetPath.ToString();
if (strFileName.StartsWith(strTargetPath))
{
strFileName = strFileName.Replace(strTargetPath, strDrivePath.Substring(0, 2));
break;
}
}
return strFileName;
}
}
I have the following problem:
From a service I need to start an application in a user session. No human user log on that machine, since it is a server. Launched application must have a session != 0.
Current "solution"
I used a scheduled task at machine startup, that task launch ( in session 0, of course ) an application launching a Remote Desktop logon on the same machine: this creates a user session > 0 and in the user startup the is the final application to launch. It works, but too tricky.
Is there some smartest way? It is critical that I can reuse a user session already on since there is potentially no user logged on.
MAJOR UPDATE
Well after a lot of research and partial successes, and also thanks to some SysAdmin inflexibility about creating an user for a specific pourpose, I decided to use OpenGL instead of WPF for render the 3d portion broken in Session 0.
Surprisingly it took less than expected. I think having this question as a reference could be useful to other who want try to render a Viewport3D from a service.
I'm not sure if this will work, but maybe this answer helps in your case.
Use the class from the answer I link i provided and the following method (with the appropriate values):
public static void EnableVideoDrivers(bool enable)
{
// every type of device has a hard-coded GUID, put here the one for
// video drivers
Guid videoGuid = new Guid("{device GUID}");
// get this from the properties dialog box of this device in Device Manager
string instancePath = #"Device Instance Path";
DeviceHelper.SetDeviceEnabled(videoGuid, instancePath, enable);
}
Here's a list of Popular Device Class GUIDs.
I'm not sure I understand correctly your needs, but maybe just starting process with given credentials and redirect input and output is what you need. Starting process with given credentials:
Process p = new Process();
p.StartInfo = new ProcessStartInfo(fileName, args);
p.StartInfo.UserName = userName;
p.StartInfo.Password = pass;
p.Start();
You may also need to redirect input and output of the application. That problem is well described on CodeProjecgt in this artice.
This is how I start a process for a particular usersession from a Local windows service.
It uses C#, with some DLL imports from kernel32.dll, wtsaspi.dll, userev.dll, and advapi32.dll.
For context, my code will search all user sessions. In my scenario, my service is running on a Windows Terminal server and wants to keep a particular app "alive" in each user's session. Meaning, if we check and its not running anymore, we restart it.
Here is the program logic (abbreviated), this is how you call the method that starts the user process:
foreach(var sesh in ProcessExtensions.GetSessions().Where(r => r.State == "Active").ToList())
{
var running = procs.Any(r => r.ProcessName == filename && r.SessionId == sesh.SessionId);
if (!running)
{
try
{
ProcessExtensions.StartProcessForSession(sesh.SessionId, (string)item, "/restart", System.IO.Path.GetDirectoryName((string)item), true);
}
catch (Exception ex)
{
Trace.TraceWarning("Error: {0}", ex);
}
}
}
Here is the implementation of ProcessExtensions where all of the good stuff is.
Disclaimer - I did not write this code, This is an example I found online and adjusted it to my needs. If you authored the original post. Apologies for the lack of footnote.
ProcessExtensions.cs
public static class ProcessExtensions
{
#region Win32 Constants
private const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;
private const int CREATE_NO_WINDOW = 0x08000000;
private const int CREATE_NEW_CONSOLE = 0x00000010;
private const uint INVALID_SESSION_ID = 0xFFFFFFFF;
private static readonly IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;
#endregion
#region DllImports
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
private static extern bool CreateProcessAsUser(
IntPtr hToken,
String lpApplicationName,
String lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandle,
uint dwCreationFlags,
IntPtr lpEnvironment,
String lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
private static extern bool DuplicateTokenEx(
IntPtr ExistingTokenHandle,
uint dwDesiredAccess,
IntPtr lpThreadAttributes,
int TokenType,
int ImpersonationLevel,
ref IntPtr DuplicateTokenHandle);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
[DllImport("userenv.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hSnapshot);
[DllImport("kernel32.dll")]
private static extern uint WTSGetActiveConsoleSessionId();
[DllImport("Wtsapi32.dll")]
private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken);
[DllImport("wtsapi32.dll", SetLastError = true)]
private static extern int WTSEnumerateSessions(
IntPtr hServer,
int Reserved,
int Version,
ref IntPtr ppSessionInfo,
ref int pCount);
#endregion
#region Win32 Structs
private enum SW
{
SW_HIDE = 0,
SW_SHOWNORMAL = 1,
SW_NORMAL = 1,
SW_SHOWMINIMIZED = 2,
SW_SHOWMAXIMIZED = 3,
SW_MAXIMIZE = 3,
SW_SHOWNOACTIVATE = 4,
SW_SHOW = 5,
SW_MINIMIZE = 6,
SW_SHOWMINNOACTIVE = 7,
SW_SHOWNA = 8,
SW_RESTORE = 9,
SW_SHOWDEFAULT = 10,
SW_MAX = 10
}
private enum WTS_CONNECTSTATE_CLASS
{
WTSActive,
WTSConnected,
WTSConnectQuery,
WTSShadow,
WTSDisconnected,
WTSIdle,
WTSListen,
WTSReset,
WTSDown,
WTSInit
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
private enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3,
}
[StructLayout(LayoutKind.Sequential)]
private struct STARTUPINFO
{
public int cb;
public String lpReserved;
public String lpDesktop;
public String lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
private enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation = 2
}
[StructLayout(LayoutKind.Sequential)]
private struct WTS_SESSION_INFO
{
public readonly UInt32 SessionID;
[MarshalAs(UnmanagedType.LPStr)]
public readonly String pWinStationName;
public readonly WTS_CONNECTSTATE_CLASS State;
}
#endregion
public static IEnumerable<UserSessionData> GetSessions()
{
//var bResult = false;
var hImpersonationToken = IntPtr.Zero;
//var activeSessionId = INVALID_SESSION_ID;
var pSessionInfo = IntPtr.Zero;
var sessionCount = 0;
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0)
{
var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
var current = pSessionInfo;
for (var i = 0; i < sessionCount; i++)
{
var si = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO));
current += arrayElementSize;
var data = new UserSessionData
{
SessionId = (int)si.SessionID,
State = si.State.ToString().Substring(3),
Name = si.pWinStationName
};
yield return data;
}
}
}
private static bool GetUserTokenForSession(int sessionId, ref IntPtr phUserToken)
{
var bResult = false;
var hImpersonationToken = IntPtr.Zero;
var pSessionInfo = IntPtr.Zero;
if (WTSQueryUserToken((uint)sessionId, ref hImpersonationToken) != 0)
{
// Convert the impersonation token to a primary token
bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary,
ref phUserToken);
CloseHandle(hImpersonationToken);
}
return bResult;
}
private static bool GetCurrentUserSessionToken(ref IntPtr phUserToken)
{
var bResult = false;
var hImpersonationToken = IntPtr.Zero;
var activeSessionId = INVALID_SESSION_ID;
var pSessionInfo = IntPtr.Zero;
var sessionCount = 0;
// Get a handle to the user access token for the current active session.
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0)
{
var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
var current = pSessionInfo;
for (var i = 0; i < sessionCount; i++)
{
var si = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO));
current += arrayElementSize;
if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive)
{
activeSessionId = si.SessionID;
}
}
}
// If enumerating did not work, fall back to the old method
if (activeSessionId == INVALID_SESSION_ID)
{
activeSessionId = WTSGetActiveConsoleSessionId();
}
if (WTSQueryUserToken(activeSessionId, ref hImpersonationToken) != 0)
{
// Convert the impersonation token to a primary token
bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary,
ref phUserToken);
CloseHandle(hImpersonationToken);
}
return bResult;
}
public static bool StartProcessForSession(int sessionId, string appPath, string cmdLine = null, string workDir = null, bool visible = true)
{
var hUserToken = IntPtr.Zero;
var startInfo = new STARTUPINFO();
var procInfo = new PROCESS_INFORMATION();
var pEnv = IntPtr.Zero;
int iResultOfCreateProcessAsUser;
startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO));
try
{
if (!GetUserTokenForSession(sessionId, ref hUserToken))
{
throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed.");
}
uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW);
startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE);
startInfo.lpDesktop = "winsta0\\default";
if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false))
{
throw new Exception("StartProcessInSession: CreateEnvironmentBlock failed.");
}
if (!CreateProcessAsUser(hUserToken,
appPath, // Application Name
cmdLine, // Command Line
IntPtr.Zero,
IntPtr.Zero,
false,
dwCreationFlags,
pEnv,
workDir, // Working directory
ref startInfo,
out procInfo))
{
iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed. Error Code -" + iResultOfCreateProcessAsUser);
}
iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
}
finally
{
CloseHandle(hUserToken);
if (pEnv != IntPtr.Zero)
{
DestroyEnvironmentBlock(pEnv);
}
CloseHandle(procInfo.hThread);
CloseHandle(procInfo.hProcess);
}
return true;
}
public static bool StartProcessAsCurrentUser(string appPath, string cmdLine = null, string workDir = null, bool visible = true)
{
var hUserToken = IntPtr.Zero;
var startInfo = new STARTUPINFO();
var procInfo = new PROCESS_INFORMATION();
var pEnv = IntPtr.Zero;
int iResultOfCreateProcessAsUser;
startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO));
try
{
if (!GetCurrentUserSessionToken(ref hUserToken))
{
throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed.");
}
uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW);
startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE);
startInfo.lpDesktop = "winsta0\\default";
if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false))
{
throw new Exception("StartProcessAsCurrentUser: CreateEnvironmentBlock failed.");
}
if (!CreateProcessAsUser(hUserToken,
appPath, // Application Name
cmdLine, // Command Line
IntPtr.Zero,
IntPtr.Zero,
false,
dwCreationFlags,
pEnv,
workDir, // Working directory
ref startInfo,
out procInfo))
{
iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed. Error Code -" + iResultOfCreateProcessAsUser);
}
iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
}
finally
{
CloseHandle(hUserToken);
if (pEnv != IntPtr.Zero)
{
DestroyEnvironmentBlock(pEnv);
}
CloseHandle(procInfo.hThread);
CloseHandle(procInfo.hProcess);
}
return true;
}
}
I need to access a remote LDAP server behind a firewall (using C#/.NET) for user authentication.
The firewall at the remote site is set to allow a specific IP address, but it is not the primary IP Address on the server i.e. by default, the connection to the remote LDAP server would use the primary IP.
How do you force LDAP to use a secondary IP Address in .NET?
I am specifically using the System.DirectoryServices.DirectoryEntry and System.DirectoryServices.AccountManagement.PrincipalContext classes, but there isn't obvious way I could see to control the local end point.
This is how I would bind to a local IP address using a TcpClient:
using System.Net;
using System.Net.Sockets;
IPEndPoint localEndpoint = ...get relevant local ip address that needs to connect
TcpClient tcp = new TcpClient( localEndpoint );
...do stuff with tcp client
NB: The primary IP address of the server cannot be changed in this instance.
PS: While I use the word "bind" here to mean binding to a local end point, LDAP uses the word "bind" for connecting/authenticating to the directory.
You'll have to PInvoke the ldap_* functions in wldap32.dll. It looks like the LDAP_OPT_SOCKET_BIND_ADDRESSES option in Session Options will let you control which local endpoint to use. The System.DirectoryServices.Protocols is the managed version of this API, but I don't see a corresponding property in LdapSessionOptions.
This works for me:
class LDAPConnection : IDisposable
{
public static bool IsValidCredentials(string domain, string localAddress,
string usernameDomain, string username, SecureString password)
{
try
{
using (LDAPConnection ldapConnection =
new LDAPConnection(domain, LDAP_PORT, localAddress))
{
ldapConnection.Bind(usernameDomain, username, password);
return true;
}
}
catch
{
return false;
}
}
protected IntPtr _ld;
protected List<IntPtr> _stringPointers;
public LDAPConnection(string hostname, uint port, params string[] localAddresses)
{
_stringPointers = new List<IntPtr>();
_ld = LdapInit(hostname, port);
LdapSetOption(_ld, LDAP_OPT_VERSION, LDAP_VERSION3);
if (localAddresses != null && localAddresses.Length > 0)
{
string addr = string.Join(" ", localAddresses);
IntPtr pStr = LdapSetOption(_ld, LDAP_OPT_SOCKET_BIND_ADDRESSES, addr);
_stringPointers.Add(pStr);
}
}
public void Bind(string domain, string username, SecureString password)
{
LdapBind(_ld, domain, username, password);
}
public void Dispose()
{
if (_ld != NULL) ldap_unbind_s(_ld);
foreach (IntPtr pString in _stringPointers)
{
Marshal.FreeHGlobal(pString);
}
}
[DllImport("wldap32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern uint LdapGetLastError();
[DllImport("wldap32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
protected static extern IntPtr ldap_init(string HostName, uint PortNumber);
//caller must call ldap_unbind or ldap_unbind_s on the return value
public static IntPtr LdapInit(string hostname, uint port)
{
IntPtr ld = ldap_init(hostname, port);
if (ld == NULL)
{
throw new Exception("LDAP Error: " + LdapGetLastError());
}
return ld;
}
[DllImport("wldap32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern uint ldap_unbind_s(IntPtr ld);
[DllImport("wldap32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern uint ldap_set_option(IntPtr ld, uint option, ref IntPtr invalue);
[DllImport("wldap32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern uint ldap_set_option(IntPtr ld, uint option, ref uint invalue);
//caller must free IntPtr after calling ldap_unbind_s
public static IntPtr LdapSetOption(IntPtr ld, uint option, string invalue)
{
IntPtr pString = Marshal.StringToHGlobalUni(invalue);
bool exception = true;
try
{
uint errorCode = ldap_set_option(ld, option, ref pString);
if (errorCode != LDAP_SUCCESS)
{
throw new Exception("LDAP Error: " + errorCode);
}
exception = false;
return pString;
}
finally
{
if (exception && pString != NULL)
{
Marshal.FreeHGlobal(pString);
}
}
}
public static void LdapSetOption(IntPtr ld, uint option, uint invalue)
{
uint errorCode = ldap_set_option(ld, option, ref invalue);
if (errorCode != LDAP_SUCCESS)
{
throw new Exception("LDAP Error: " + errorCode);
}
}
[DllImport("wldap32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
protected static extern uint ldap_bind_s(IntPtr ld, IntPtr dn, IntPtr cred, uint method);
public static void LdapBind(IntPtr ld, string domain,
string username, SecureString password)
{
IntPtr cred = SEC_WINNT_AUTH_IDENTITY.GetUnicode(username, password, domain);
try
{
uint errorCode = ldap_bind_s(ld, NULL, cred, LDAP_AUTH_NEGOTIATE);
if (errorCode != LDAP_SUCCESS)
{
throw new Exception("LDAP Error: " + errorCode);
}
}
finally
{
if (cred != NULL) SEC_WINNT_AUTH_IDENTITY.Free(cred);
}
}
public const uint LDAP_PORT = 389;
public const uint LDAP_VERSION3 = 3;
public const uint LDAP_SUCCESS = 0;
public const uint LDAP_OPT_VERSION = 0x11;
public const uint LDAP_OPT_SOCKET_BIND_ADDRESSES = 0x44;
public const uint LDAP_AUTH_NEGOTIATE = 0x486;
public static readonly IntPtr NULL = IntPtr.Zero;
public const uint SEC_WINNT_AUTH_IDENTITY_ANSI = 1;
public const uint SEC_WINNT_AUTH_IDENTITY_UNICODE = 2;
[StructLayout(LayoutKind.Sequential)]
public struct SEC_WINNT_AUTH_IDENTITY
{
public IntPtr User;
public int UserLength;
public IntPtr Domain;
public int DomainLength;
public IntPtr Password;
public int PasswordLength;
public uint Flags;
public static IntPtr GetUnicode(string username,
SecureString password, string domain)
{
SEC_WINNT_AUTH_IDENTITY swai = new SEC_WINNT_AUTH_IDENTITY();
bool exception = true;
try
{
swai.User = Marshal.StringToHGlobalUni(username);
swai.UserLength = username.Length;
swai.Domain = Marshal.StringToHGlobalUni(domain);
swai.DomainLength = domain.Length;
swai.Password = Marshal.SecureStringToGlobalAllocUnicode(password);
swai.PasswordLength = password.Length;
swai.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
IntPtr pSwai = Marshal.AllocHGlobal(Marshal.SizeOf(swai));
try
{
Marshal.StructureToPtr(swai, pSwai, false);
exception = false;
return pSwai;
}
finally
{
if (exception && pSwai != NULL)
{
Marshal.FreeHGlobal(pSwai);
}
}
}
finally
{
if (exception)
{
if (swai.User != NULL) Marshal.FreeHGlobal(swai.User);
if (swai.Domain != NULL) Marshal.FreeHGlobal(swai.Domain);
if (swai.Password != NULL)
{
Marshal.ZeroFreeGlobalAllocUnicode(swai.Password);
}
}
}
}
public static void Free(IntPtr pSwai)
{
SEC_WINNT_AUTH_IDENTITY swai =
(SEC_WINNT_AUTH_IDENTITY)Marshal.PtrToStructure(
pSwai, typeof(SEC_WINNT_AUTH_IDENTITY));
if (swai.Flags == SEC_WINNT_AUTH_IDENTITY_ANSI)
{
Marshal.ZeroFreeGlobalAllocAnsi(swai.Password);
}
else
{
Marshal.ZeroFreeGlobalAllocUnicode(swai.Password);
}
Marshal.FreeHGlobal(swai.Domain);
Marshal.FreeHGlobal(swai.User);
Marshal.FreeHGlobal(pSwai);
}
}
Example usage:
LDAPConnection.IsValidCredentials("leaf.domain.com", "10.0.0.1", "leaf",
"myusername", password);
Is there a built in (to the .net framework) class or function to resolve a SRV entry to the corresponding records?
IE: _dmsc._tcp.network.local to an array of information (host, port, weight, priority)
Alternative solution:
P/Invoke to the DnsQuery function and pass DNS_TYPE_SRV.
An example can be found in this excellent post by Ruslan. I reproduce the code below in case the link breaks, but emphasize it is taken directly from his blog.
public class nDnsQuery
{
public nDnsQuery()
{
}
[DllImport("dnsapi", EntryPoint = "DnsQuery_W", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
private static extern int DnsQuery([MarshalAs(UnmanagedType.VBByRefStr)]ref string pszName, QueryTypes wType, QueryOptions options, int aipServers, ref IntPtr ppQueryResults, int pReserved);
[DllImport("dnsapi", CharSet = CharSet.Auto, SetLastError = true)]
private static extern void DnsRecordListFree(IntPtr pRecordList, int FreeType);
public static string[] GetSRVRecords(string needle)
{
IntPtr ptr1 = IntPtr.Zero;
IntPtr ptr2 = IntPtr.Zero;
SRVRecord recSRV;
if (Environment.OSVersion.Platform != PlatformID.Win32NT)
{
throw new NotSupportedException();
}
ArrayList list1 = new ArrayList();
try
{
int num1 = nDnsQuery.DnsQuery(ref needle, QueryTypes.DNS_TYPE_SRV, QueryOptions.DNS_QUERY_BYPASS_CACHE, 0, ref ptr1, 0);
if (num1 != 0)
{
if (num1 == 9003)
{
list1.Add("DNS record does not exist");
}
else
{
throw new Win32Exception(num1);
}
}
for (ptr2 = ptr1; !ptr2.Equals(IntPtr.Zero); ptr2 = recSRV.pNext)
{
recSRV = (SRVRecord)Marshal.PtrToStructure(ptr2, typeof(SRVRecord));
if (recSRV.wType == (short)QueryTypes.DNS_TYPE_SRV)
{
string text1 = Marshal.PtrToStringAuto(recSRV.pNameTarget);
text1 += ":" + recSRV.wPort;
list1.Add(text1);
}
}
}
finally
{
nDnsQuery.DnsRecordListFree(ptr1, 0);
}
return (string[])list1.ToArray(typeof(string));
}
private enum QueryOptions
{
DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE = 1,
DNS_QUERY_BYPASS_CACHE = 8,
DNS_QUERY_DONT_RESET_TTL_VALUES = 0x100000,
DNS_QUERY_NO_HOSTS_FILE = 0x40,
DNS_QUERY_NO_LOCAL_NAME = 0x20,
DNS_QUERY_NO_NETBT = 0x80,
DNS_QUERY_NO_RECURSION = 4,
DNS_QUERY_NO_WIRE_QUERY = 0x10,
DNS_QUERY_RESERVED = -16777216,
DNS_QUERY_RETURN_MESSAGE = 0x200,
DNS_QUERY_STANDARD = 0,
DNS_QUERY_TREAT_AS_FQDN = 0x1000,
DNS_QUERY_USE_TCP_ONLY = 2,
DNS_QUERY_WIRE_ONLY = 0x100
}
private enum QueryTypes
{
DNS_TYPE_A = 0x0001,
DNS_TYPE_MX = 0x000f,
DNS_TYPE_SRV = 0x0021
}
[StructLayout(LayoutKind.Sequential)]
private struct SRVRecord
{
public IntPtr pNext;
public string pName;
public short wType;
public short wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
public IntPtr pNameTarget;
public short wPriority;
public short wWeight;
public short wPort;
public short Pad;
}
}
Open source, BSD licensed library here: http://dndns.codeplex.com/
This looks like another great candidate and is Apache 2.0 licensed. Latest build was Thu Dec 17, 2015 at 3:00 AM