When did SECURITY_ATTRIBUTES change and why? - c#

I have some code that uses P/Invoke to launch a process and capture the standard output. (The story of why we did this using P/Invoke instead of System.Diagnostics.Process is long and convoluted; suffice it to say it's a requirement.) It's been working in production under heavy load for nearly a year, and the tests that exercise it have always passed.
This morning though I ran the tests, and they failed. I can't be certain when I last ran the tests prior to this morning (5/15/2014), but I believe it was 4/24/2014. The tests passed then, but failed this morning. I was getting the "PInvokeStackImbalance" error message, so I did some research, and eventually realized the signature of one of the structs used by the extern method (CreatePipe in this instance) was incorrect. I changed it, and the tests started passing again.
I'm happy to have found the fix, but I'm concerned about deployment. Why did the signature of the struct change? I haven't upgraded my OS or anything - I was running Windows 7 x64 on 4/24, and I'm still running it now. (The deployment environment is Windows Server 2012.) I've installed (and uninstalled) a few apps since then, but they've been light-weight 3rd-party tools, not Microsoft or system components. I assume a Windows Update hotfix is responsible, but I can't figure out which one.
To be clear, in my own code, all I changed was this:
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public UInt32 nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
to this:
[StructLayout(LayoutKind.Sequential)]
internal class SECURITY_ATTRIBUTES
{
public int nLength = 12;
public IntPtr lpSecurityDescriptor = IntPtr.Zero;
public bool bInheritHandle;
}
I need to be sure that the change I made to get the code working on my machine isn't going to break the app when I deploy to production. Does anyone know how to identify what necessitated the change and how to determine whether the production environment does or does not require it?
EDIT:
Here is the code that opens the pipe for the standard output:
private PipeInfo CreatePipe()
{
PipeInfo pipeInfo = new PipeInfo();
SafeFileHandle safeFileHandle = null;
try
{
Native.SECURITY_ATTRIBUTES pipeAttributes = new Native.SECURITY_ATTRIBUTES();
pipeAttributes.bInheritHandle = true;
if (!Native.CreatePipe(out safeFileHandle, out pipeInfo.ChildHandle, pipeAttributes, 0) || safeFileHandle.IsInvalid || pipeInfo.ChildHandle.IsInvalid)
{
throw new Win32Exception();
}
if (!Native.DuplicateHandle(new HandleRef(this, Native.GetCurrentProcess()), safeFileHandle, new HandleRef(this, Native.GetCurrentProcess()), out pipeInfo.ParentHandle, 0, false, 2))
{
throw new Win32Exception();
}
}
finally
{
if (safeFileHandle != null && !safeFileHandle.IsInvalid)
{
safeFileHandle.Close();
}
}
return pipeInfo;
}
I can't exactly take credit for this code, I largely lifted it from the .NET Reference Source
Just to be clear on timeline:
May 2013 - write the CreatePipe code with the first version of SECURITY_ATTRIBUTES
June 2013 - deploy; code has been running successfully ever since
April 2014 - without any changes being made, code starts throwing stack imbalance error
May 2014 - I change to the second version of SECURITY_ATTRIBUTES and the error goes away

We encountered this problem on x64 and this post was the top result in our search. We were using the magic 12 for nLength just like your solution which we got from the C# source for process: https://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/Process.cs
[StructLayout(LayoutKind.Sequential)]
internal class SECURITY_ATTRIBUTES {
#if !SILVERLIGHT
// We don't support ACL's on Silverlight nor on CoreSystem builds in our API's.
// But, we need P/Invokes to occasionally take these as parameters. We can pass null.
public int nLength = 12;
public SafeLocalMemHandle lpSecurityDescriptor = new SafeLocalMemHandle(IntPtr.Zero, false);
public bool bInheritHandle = false;
#endif // !SILVERLIGHT
}
It turns out CreatePipe expects a pointer, from the docs:
lpPipeAttributes
A pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle can be inherited by child processes. If lpPipeAttributes is NULL, the handle cannot be inherited.
The solution is detailed in this stackoverflow post. It works for both x86 and x64. Our code is below based on that stack overflow post and the process source (with using DWORD = System.UInt32; at the top).
internal static class NativeMethods
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CreatePipe(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe,
IntPtr lpPipeAttributes, int nSize);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, SafeHandle hSourceHandle,
IntPtr hTargetProcess, out SafeFileHandle targetHandle, int dwDesiredAccess,
bool bInheritHandle, int dwOptions);
[StructLayout(LayoutKind.Sequential)]
public struct PIPE_SECURITY_ATTRIBUTES
{
public DWORD nLength;
public IntPtr lpSecurityDescriptor;
[MarshalAs(UnmanagedType.Bool)]
public bool bInheritHandle;
}
public static void CreatePipe(out SafeFileHandle parentHandle, out SafeFileHandle childHandle, bool parentInputs)
{
PIPE_SECURITY_ATTRIBUTES lpPipeAttributes = new PIPE_SECURITY_ATTRIBUTES();
lpPipeAttributes.nLength = (DWORD)Marshal.SizeOf(lpPipeAttributes);
lpPipeAttributes.bInheritHandle = true;
lpPipeAttributes.lpSecurityDescriptor = IntPtr.Zero;
IntPtr attr = Marshal.AllocHGlobal(Marshal.SizeOf(lpPipeAttributes));
Marshal.StructureToPtr(lpPipeAttributes, attr, true);
SafeFileHandle hWritePipe = null;
try
{
if (parentInputs)
CreatePipeWithSecurityAttributes(out childHandle, out hWritePipe, attr, 0);
else
CreatePipeWithSecurityAttributes(out hWritePipe, out childHandle, attr, 0);
if (!DuplicateHandle(GetCurrentProcess(), hWritePipe, GetCurrentProcess(), out parentHandle, 0, false, 2))
throw new Exception();
}
finally
{
if ((hWritePipe != null) && !hWritePipe.IsInvalid)
{
hWritePipe.Close();
}
}
}
public static void CreatePipeWithSecurityAttributes(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe,
IntPtr lpPipeAttributes, int nSize)
{
hReadPipe = null;
if ((!CreatePipe(out hReadPipe, out hWritePipe, lpPipeAttributes, nSize) || hReadPipe.IsInvalid) || hWritePipe.IsInvalid)
throw new Exception();
}
}

Related

Programmatically check Windows 10's case sensitive directory attribute

Since April 2018, Windows 10 has had the ability to get or set if a directory was marked as case sensitive using fsutil.exe.
Is there a way to programmatically query the case sensitivity of a directory without running fsutil.exe or hackishly creating files with different casing to see if they collide?
I haven't really found any way to test this through research. I've read this is an actual NTFS attribute yet it doesn't show up when getting the file's attributes. I also noticed FindFirstFile will return the casing of the correct file if two different casings exist. Other than this I have no idea where to go as there really isn't a lot of information on this. This stuff is still pretty new.
As others have mentioned, making something case-sensitive is not a good idea in Windows due to comparability issues. I'm aware of that and my goal is to scan and work with existing case sensitive directories in the file system.
Progress:
I've discovered that Windows' FindFirstFile and friends functions respects the case sensitivity of the directory even without using the FIND_FIRST_EX_CASE_SENSITIVE. It will not return a file with invalid casing. Now I'm trying to figure out if there's a good way to make use of this.
Here is my P/Invoke solution thanks to the help of #eryksun's comment.
Edit 2: Added SetDirectoryCaseSensitive()
Edit 3: Added IsDirectoryCaseSensitivitySupported()
I've implemented the the native method NtQueryInformationFile while using the FILE_INFORMATION_CLASS FileCaseSensitiveInformation to read the FILE_CASE_SENSITIVE_INFORMATION structure.
public static partial class NativeMethods {
public static readonly IntPtr INVALID_HANDLE = new IntPtr(-1);
public const FileAttributes FILE_FLAG_BACKUP_SEMANTICS = (FileAttributes) 0x02000000;
public enum NTSTATUS : uint {
SUCCESS = 0x00000000,
NOT_IMPLEMENTED = 0xC0000002,
INVALID_INFO_CLASS = 0xC0000003,
INVALID_PARAMETER = 0xC000000D,
NOT_SUPPORTED = 0xC00000BB,
DIRECTORY_NOT_EMPTY = 0xC0000101,
}
public enum FILE_INFORMATION_CLASS {
None = 0,
// Note: If you use the actual enum in here, remember to
// start the first field at 1. There is nothing at zero.
FileCaseSensitiveInformation = 71,
}
// It's called Flags in FileCaseSensitiveInformation so treat it as flags
[Flags]
public enum CASE_SENSITIVITY_FLAGS : uint {
CaseInsensitiveDirectory = 0x00000000,
CaseSensitiveDirectory = 0x00000001,
}
[StructLayout(LayoutKind.Sequential)]
public struct IO_STATUS_BLOCK {
[MarshalAs(UnmanagedType.U4)]
public NTSTATUS Status;
public ulong Information;
}
[StructLayout(LayoutKind.Sequential)]
public struct FILE_CASE_SENSITIVE_INFORMATION {
[MarshalAs(UnmanagedType.U4)]
public CASE_SENSITIVITY_FLAGS Flags;
}
// An override, specifically made for FileCaseSensitiveInformation, no IntPtr necessary.
[DllImport("ntdll.dll")]
[return: MarshalAs(UnmanagedType.U4)]
public static extern NTSTATUS NtQueryInformationFile(
IntPtr FileHandle,
ref IO_STATUS_BLOCK IoStatusBlock,
ref FILE_CASE_SENSITIVE_INFORMATION FileInformation,
int Length,
FILE_INFORMATION_CLASS FileInformationClass);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr CreateFile(
[MarshalAs(UnmanagedType.LPTStr)] string filename,
[MarshalAs(UnmanagedType.U4)] FileAccess access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
IntPtr templateFile);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(
IntPtr hObject);
public static bool IsDirectoryCaseSensitive(string directory, bool throwOnError = true) {
// Read access is NOT required
IntPtr hFile = CreateFile(directory, 0, FileShare.ReadWrite,
IntPtr.Zero, FileMode.Open,
FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero);
if (hFile == INVALID_HANDLE)
throw new Win32Exception();
try {
IO_STATUS_BLOCK iosb = new IO_STATUS_BLOCK();
FILE_CASE_SENSITIVE_INFORMATION caseSensitive = new FILE_CASE_SENSITIVE_INFORMATION();
NTSTATUS status = NtQueryInformationFile(hFile, ref iosb, ref caseSensitive,
Marshal.SizeOf<FILE_CASE_SENSITIVE_INFORMATION>(),
FILE_INFORMATION_CLASS.FileCaseSensitiveInformation);
switch (status) {
case NTSTATUS.SUCCESS:
return caseSensitive.Flags.HasFlag(CASE_SENSITIVITY_FLAGS.CaseSensitiveDirectory);
case NTSTATUS.NOT_IMPLEMENTED:
case NTSTATUS.NOT_SUPPORTED:
case NTSTATUS.INVALID_INFO_CLASS:
case NTSTATUS.INVALID_PARAMETER:
// Not supported, must be older version of windows.
// Directory case sensitivity is impossible.
return false;
default:
throw new Exception($"Unknown NTSTATUS: {(uint)status:X8}!");
}
}
finally {
CloseHandle(hFile);
}
}
}
Here is the implementation for setting the case sensitivity of a directory by implementing NTSetInformationFile. (Which has a parameter list that is identical to NTQueryInformationFile. Again, the problem was solved thanks to insight from #eryksun.
FILE_WRITE_ATTRIBUTES is a FileAccess flag that is not implemented in C#, so it needs to be defined and/or casted from the the value 0x100.
partial class NativeMethods {
public const FileAccess FILE_WRITE_ATTRIBUTES = (FileAccess) 0x00000100;
// An override, specifically made for FileCaseSensitiveInformation, no IntPtr necessary.
[DllImport("ntdll.dll")]
[return: MarshalAs(UnmanagedType.U4)]
public static extern NTSTATUS NtSetInformationFile(
IntPtr FileHandle,
ref IO_STATUS_BLOCK IoStatusBlock,
ref FILE_CASE_SENSITIVE_INFORMATION FileInformation,
int Length,
FILE_INFORMATION_CLASS FileInformationClass);
// Require's elevated priviledges
public static void SetDirectoryCaseSensitive(string directory, bool enable) {
// FILE_WRITE_ATTRIBUTES access is the only requirement
IntPtr hFile = CreateFile(directory, FILE_WRITE_ATTRIBUTES, FileShare.ReadWrite,
IntPtr.Zero, FileMode.Open,
FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero);
if (hFile == INVALID_HANDLE)
throw new Win32Exception();
try {
IO_STATUS_BLOCK iosb = new IO_STATUS_BLOCK();
FILE_CASE_SENSITIVE_INFORMATION caseSensitive = new FILE_CASE_SENSITIVE_INFORMATION();
if (enable)
caseSensitive.Flags |= CASE_SENSITIVITY_FLAGS.CaseSensitiveDirectory;
NTSTATUS status = NtSetInformationFile(hFile, ref iosb, ref caseSensitive,
Marshal.SizeOf<FILE_CASE_SENSITIVE_INFORMATION>(),
FILE_INFORMATION_CLASS.FileCaseSensitiveInformation);
switch (status) {
case NTSTATUS.SUCCESS:
return;
case NTSTATUS.DIRECTORY_NOT_EMPTY:
throw new IOException($"Directory \"{directory}\" contains matching " +
$"case-insensitive files!");
case NTSTATUS.NOT_IMPLEMENTED:
case NTSTATUS.NOT_SUPPORTED:
case NTSTATUS.INVALID_INFO_CLASS:
case NTSTATUS.INVALID_PARAMETER:
// Not supported, must be older version of windows.
// Directory case sensitivity is impossible.
throw new NotSupportedException("This version of Windows does not support directory case sensitivity!");
default:
throw new Exception($"Unknown NTSTATUS: {(uint)status:X8}!");
}
}
finally {
CloseHandle(hFile);
}
}
}
Finally I have added a method to calculate once if the version of Windows supports case sensitive directories. This just creates a folder with a constant GUID name in Temp and checks the NTSTATUS result (so it can check a folder it knows it has access to).
partial class NativeMethods {
// Use the same directory so it does not need to be recreated when restarting the program
private static readonly string TempDirectory =
Path.Combine(Path.GetTempPath(), "88DEB13C-E516-46C3-97CA-46A8D0DDD8B2");
private static bool? isSupported;
public static bool IsDirectoryCaseSensitivitySupported() {
if (isSupported.HasValue)
return isSupported.Value;
// Make sure the directory exists
if (!Directory.Exists(TempDirectory))
Directory.CreateDirectory(TempDirectory);
IntPtr hFile = CreateFile(TempDirectory, 0, FileShare.ReadWrite,
IntPtr.Zero, FileMode.Open,
FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero);
if (hFile == INVALID_HANDLE)
throw new Exception("Failed to open file while checking case sensitivity support!");
try {
IO_STATUS_BLOCK iosb = new IO_STATUS_BLOCK();
FILE_CASE_SENSITIVE_INFORMATION caseSensitive = new FILE_CASE_SENSITIVE_INFORMATION();
// Strangely enough, this doesn't fail on files
NTSTATUS result = NtQueryInformationFile(hFile, ref iosb, ref caseSensitive,
Marshal.SizeOf<FILE_CASE_SENSITIVE_INFORMATION>(),
FILE_INFORMATION_CLASS.FileCaseSensitiveInformation);
switch (result) {
case NTSTATUS.SUCCESS:
return (isSupported = true).Value;
case NTSTATUS.NOT_IMPLEMENTED:
case NTSTATUS.INVALID_INFO_CLASS:
case NTSTATUS.INVALID_PARAMETER:
case NTSTATUS.NOT_SUPPORTED:
// Not supported, must be older version of windows.
// Directory case sensitivity is impossible.
return (isSupported = false).Value;
default:
throw new Exception($"Unknown NTSTATUS {(uint)result:X8} while checking case sensitivity support!");
}
}
finally {
CloseHandle(hFile);
try {
// CHOOSE: If you delete the folder, future calls to this will not be any faster
// Directory.Delete(TempDirectory);
}
catch { }
}
}
}

Evaluate if drive is in use

I'd like to evaluate wether a drive is in use (if I'm not mistaken this means that some read/write stuff is happening with the drive) using C#. I wouldn't mind for a solution using bash scripts or similiar either, as I could use them in a C# application. I already found a question regarding bash scripts here, but couldn't solve my problem with the answers given.
I considered to use the DriveInfo class already, however it didn't seem to have anything useful for me. I wondered wether I could use the IsReady property from DriveInfo, because I guessed that it wouldn't be ready while it is read/written, but this attempt seems rather botchy to me.
However I still tried it:
private static bool IsDriveInUse ()
{
var drive = DriveInfo.GetDrives ().FirstOrDefault(info => info.Name.StartsWith(DRIVE_LETTER.ToString()));
return drive != null && !drive.IsReady;
}
But it didn't work (it returned false while I played music from my drive).
An optimal solution for me would be a function that tells me wether the drive was in use in a specific span of time (let's stick to the name IsDriveInUse). That means that if the time was for example 60 seconds, IsDriveInUse should return true if 5 seconds before the function call content from the drive was read and false if there was no read/write action in the passed 60 seconds.
EDIT To specify what exactly I mean by in use, I'll try to explain what I'm trying to do. I am writing a tool, which automatically spins down my hard drive, when it's been idle or when I press a keyboard shortcut. I managed to spin it down programmatically (even though either the windows integrated tool nor other tools I found were able to do that, but that's another problem). However, it now spins down the hard drive every minute, regardless of wether it's currently in use or not. That means, if I play music from my hard drive, it's still spinned down, just to spin up directly after it, which doesn't decrease noise development.
I hope this clarified the matter.
EDIT I now tried using the FSCTL_LOCK_VOLUME control code (I couldn't find a value for IOCTL_DISK_PERFORMANCE), but it still returned false for IsDriveInUse() while I was playing music. Furthermore it caused windows to directly spin the drive up again as I spinned it down (probably because the releasing made Windows think that something was using the drive). This is what I tried:
public class DriveManager
{
public const int FSCTL_LOCK_VOLUME = 0x00090018;
public const int FSCTL_UNLOCK_VOLUME = 0x0009001c;
[DllImport ("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CreateFile (
string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes,
uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
[return: MarshalAs (UnmanagedType.Bool)]
[DllImport ("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool DeviceIoControl (
[In] SafeFileHandle hDevice,
[In] int dwIoControlCode, [In] IntPtr lpInBuffer,
[In] int nInBufferSize, [Out] IntPtr lpOutBuffer,
[In] int nOutBufferSize, out int lpBytesReturned,
[In] IntPtr lpOverlapped);
public static SafeFileHandle CreateFileR (string device)
{
string str = device.EndsWith (#"\") ? device.Substring (0, device.Length - 1) : device;
return new SafeFileHandle (
CreateFile (#"\\.\" + str, WinntConst.GENERIC_READ, WinntConst.FILE_SHARE_READ, IntPtr.Zero,
WinntConst.OPEN_EXISTING, WinntConst.FILE_ATTRIBUTE_NORMAL, IntPtr.Zero), true);
}
internal class WinntConst
{
// Fields
internal static uint FILE_ATTRIBUTE_NORMAL = 0x80;
internal static uint FILE_SHARE_READ = 1;
internal static uint GENERIC_READ = 0x80000000;
internal static uint OPEN_EXISTING = 3;
}
public static bool IsDriveInUse (string deviceName)
{
var handle = CreateFileR (deviceName);
var buffer = Marshal.AllocHGlobal (sizeof (int));
try
{
return
DeviceIoControl (handle,
FSCTL_LOCK_VOLUME,
IntPtr.Zero,
0,
buffer,
sizeof(int),
out var bytesReturned,
IntPtr.Zero
);
}
finally
{
var sessionId = Marshal.ReadInt32 (buffer);
Marshal.FreeHGlobal (buffer);
handle.Close ();
}
}
And the implementation:
private static bool IsDriveInUse () => DriveManager.IsDriveInUse ($#"{DRIVE_LETTER}:\");
Maybe it helps to see the part in which I'm spinning the disc down as well (I used Smartmontools for this):
internal static class Program
{
private const string PROGRAM_PATH = #"External\smartctl.exe";
private const string ARGUMENTS_SHUTDOWN = #"-s standby,now {0}:";
private const char DRIVE_LETTER = 'd';
public static void Main (string [] args)
{
InitializeHotKey ();
Console.WriteLine ("Hotkey registered!");
while (true)
{
Thread.Sleep (60000);
if (!IsDriveInUse ())
ShutDownDrive (true);
}
}
private static bool IsDriveInUse () => DriveManager.IsDriveInUse ($#"{DRIVE_LETTER}:\");
private static void InitializeHotKey ()
{
HotKeyManager.RegisterHotKey (Keys.D, KeyModifiers.Alt | KeyModifiers.Control);
HotKeyManager.HotKeyPressed += HotKeyPressed;
}
private static void HotKeyPressed (object sender, HotKeyEventArgs hotKeyEventArgs) => ShutDownDrive (true);
private static void ShutDownDrive (bool withDialog = false)
{
Process process;
(process = new Process
{
StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = PROGRAM_PATH,
Arguments = string.Format (ARGUMENTS_SHUTDOWN, DRIVE_LETTER)
}
}).Start ();
process.WaitForExit ();
process.Close ();
if (withDialog)
Console.WriteLine ("Drive shut down!");
}
}
Perhaps you could use the Windows Performance Counter relevant to your drive ?
"Disk Read/sec" seems quite relevant for what youhave in mind.
In .Net, the counters are available via System.Diagnostics.PerformanceCounter
see there :
https://msdn.microsoft.com/en-us/library/system.diagnostics.performancecounter(v=vs.110).aspx

C#: How to tell if an EXE has an icon?

I'm looking for a way to tell whether or not an EXE file contains an application icon. From the answer here, I tried this:
bool hasIcon = Icon.ExtractAssociatedIcon(exe) != null;
But this seems to work even if the EXE has no icon. Is there a way to detect this in .NET?
edit: I'm OK with solutions involving P/Invoke.
You can get the IDI_APPLICATION icon through SystemIcons.Application property from SystemIcons class
if (Icon.ExtractAssociatedIcon(exe).Equals(SystemIcons.Application))
{
...
}
See MSDN for more details.
Try this. Define your pinvoke like this:
[DllImport("user32.dll")]
internal static extern IntPtr LoadImage(IntPtr hInst, IntPtr name, uint type, int cxDesired, int cyDesired, uint fuLoad);
[DllImport("kernel32.dll")]
static extern bool EnumResourceNames(IntPtr hModule, int dwID, EnumResNameProcDelegate lpEnumFunc, IntPtr lParam);
delegate bool EnumResNameProcDelegate(IntPtr hModule, IntPtr lpszType, IntPtr lpszName, IntPtr lParam);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr LoadLibraryEx(string name, IntPtr handle, uint dwFlags);
private const int LOAD_LIBRARY_AS_DATAFILE = 0x00000002;
private const int LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020;
private const int IMAGE_ICON = 1;
private const int RT_GROUP_ICON = 14;
Then you can write a function like this:
static bool HasIcon(string path)
{
// This loads the exe into the process address space, which is necessary
// for LoadImage / LoadIcon to work note, that LOAD_LIBRARY_AS_DATAFILE
// allows loading a 32-bit image into 64-bit process which is otherwise impossible
IntPtr moduleHandle = LoadLibraryEx(path, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE);
if (moduleHandle == IntPtr.Zero)
{
throw new ApplicationException("Cannot load executable");
}
IntPtr index = IntPtr.Zero;
bool hasIndex = false;
bool enumerated = EnumResourceNames(moduleHandle, RT_GROUP_ICON, (module, type, name, param) =>
{
index = name;
hasIndex = true;
// Only load first icon and bail out
return false;
}, IntPtr.Zero);
if (!enumerated || !hasIndex)
{
return false;
}
// Strictly speaking you do not need this you can return true now
// This is to demonstrate how to access the icon that was found on
// the previous step
IntPtr result = LoadImage(moduleHandle, index, IMAGE_ICON, 0, 0, 0);
if (result == IntPtr.Zero)
{
return false;
}
return true;
}
It has added bonus that if you want to, after LoadImage you can load the icon with
Icon icon = Icon.FromHandle(result);
and do whatever you want with that.
Important note: I have not done any clean up in the function, so you cannot use it as is, you'll leak handles/memory. Proper clean up is left as an exercise for the reader. Read the description of every of the winapi function used in MSDN and call corresponding clean up functions as needed.
An alternate way using shell32 api can be found here, although I don't know if it has the same problem you encountered.
Also, old, but still very relevant article: https://msdn.microsoft.com/en-us/library/ms997538.aspx

Process.MainModule --> "Access is denied" [duplicate]

This question already has an answer here:
Access Denied while using System.Diagnostics.Process
(1 answer)
Closed 2 years ago.
I want to handle this differently,
ie. determine if I have access or not.
Is it possible to see if you have access to the main module or not?
foreach (Process p in Process.GetProcesses())
{
try
{
//This throws error for some processes.
if (p.MainModule.FileName.ToLower().EndsWith(ExeName, StringComparison.CurrentCultureIgnoreCase))
{
//Do some stuff
}
}
catch (Exception)
{
//Acess denied
}
}
[Flags]
private enum ProcessAccessFlags : uint
{
QueryLimitedInformation = 0x00001000
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool QueryFullProcessImageName(
[In] IntPtr hProcess,
[In] int dwFlags,
[Out] StringBuilder lpExeName,
ref int lpdwSize);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(
ProcessAccessFlags processAccess,
bool bInheritHandle,
int processId);
String GetProcessFilename(Process p)
{
int capacity = 2000;
StringBuilder builder = new StringBuilder(capacity);
IntPtr ptr = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, p.Id);
if (!QueryFullProcessImageName(ptr, 0, builder, ref capacity))
{
return String.Empty;
}
return builder.ToString();
}
Use pinvoke with ProcessAccessFlags.QueryLimitedInformation. This will allow you to grab the filename of the process without having special admin privileges and works across x32 and x64 processes.
I see two possible causes of the exception:
It may be that your process is x86 and the process being queried is x64 or vice versa.
Every process has a so called ACL (Access control list) that describes who can interact with it, the processes you are having problems with have for security reasons an empty ACL so even as administrator you cannot mess with them. For example, there's a handfull of processes (audiodg, System, and Idle from the top of my head) that throw an exception due to the access rights.
Just use a try/catch to your loop to deal with those processes.

P/Invoking CreateToolhelp32Snapshot failing in Compact Framework

Hey, im doing a little app for my smart phone, using Windows Mobile 6. I'm trying to get all currently running processec, but method CreateToolhelp32Snapshot always returns -1. So now im stuck. I tried to get error with invoking GetLastError() method, but that method returns 0 value.
Here is a snippet of my code.
private const int TH32CS_SNAPPROCESS = 0x00000002;
[DllImport("toolhelp.dll")]
public static extern IntPtr CreateToolhelp32Snapshot(uint flags,
uint processid);
public static Process[] GetProcesses()
{
ArrayList procList = new ArrayList();
IntPtr handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if ((int)handle > 0)
{
try
{
PROCESSENTRY32 peCurr;
PROCESSENTRY32 pe32 = new PROCESSENTRY32();
// get byte array to pass to API call
byte[] peBytes = pe32.ToByteArray();
// get the first process
int retval = Process32First(handle, peBytes);
First, your handle check is wrong. It's common for the high bit to be on in a handle, causing it to look like a negative number when cast to a signed int. You should be checking that is isn't NULL (0) or INVALID_HANDLE_VALUE (-1 / 0xffffffff).
You shouldn't be "invoking GetLastError" but calling Marshal.GetLastWin32Error()
You've not set the SetLastError attribute in the P/Invoke declaration. In C# it defaults to false, in VB it defaults to true.
Where's your PROCESS32 implementation? The docs clearly state that the dwLength member must be set before the call and it's not clear here if that's happening.
As a side note, the Smart Device Framework's OpenNETCF.ToolHelp namespace has all of this implemented and working (in case you'd rather not reinvent the wheel).
Instead of
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
use
private const int TH32CS_SNAPNOHEAPS = 0x40000000;
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPNOHEAPS, 0);
By default CreateToolhelp32Snapshot will try to snapshot the heaps and that can cause an out of memory error.
Found this at https://social.msdn.microsoft.com/Forums/en-US/e91d845d-d51e-45ad-8acf-737e832c20d0/createtoolhelp32snapshot-windows-mobile-5?forum=vssmartdevicesnative and it solved my problem.
If you're not seeing valid "last error" information, perhaps you might need to add the "SetLastError" attribute on the API's DllImport attribute (MSDN reference with code examples). According to the documentation of this attribute, you should set SetLastError to...
...true to indicate that the callee will
call SetLastError; otherwise, false.
The default is false.
The runtime marshaler calls
GetLastError and caches the value
returned to prevent it from being
overwritten by other API calls. You
can retrieve the error code by calling
GetLastWin32Error
As for the API failure you're seeing, I don't spot anything obvious offhand; the code you have seems very similar to the sample code here.
This is the proper implementation based on the MSDN documentation
private const int INVALID_HANDLE_VALUE = -1;
[Flags]
private enum SnapshotFlags : uint
{
HeapList = 0x00000001,
Process = 0x00000002,
Thread = 0x00000004,
Module = 0x00000008,
Module32 = 0x00000010,
Inherit = 0x80000000,
All = 0x0000001F,
NoHeaps = 0x40000000
}
[DllImport("toolhelp.dll"]
private static extern IntPtr CreateToolhelp32Snapshot(SnapshotFlags dwFlags, int th32ProcessID);
[StructLayout(LayoutKind.Sequential)]
public struct PROCESSENTRY32
{
public uint dwSize;
public uint cntUsage;
public uint th32ProcessID;
public IntPtr th32DefaultHeapID;
public uint th32ModuleID;
public uint cntThreads;
public uint th32ParentProcessID;
public int pcPriClassBase;
public uint dwFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szExeFile;
};
IntPtr hSnap = CreateToolhelp32Snapshot(SnapshotFlags.Process, 0);
if (hSnap.ToInt64() != INVALID_HANDLE_VALUE)
{
PROCESSENTRY32 procEntry = new PROCESSENTRY32();
procEntry.dwSize = (uint)Marshal.SizeOf(typeof(PROCESSENTRY32));
if (Process32First(hSnap, ref procEntry))
{
do
{
//do whatever you want here
} while (Process32Next(hSnap, ref procEntry));
}
}
CloseHandle(hSnap);
Most importantly is this line, because you must set the size of the procEntry:
procEntry.dwSize = (uint)Marshal.SizeOf(typeof(PROCESSENTRY32));

Categories