Programmatically check Windows 10's case sensitive directory attribute - c#

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 { }
}
}
}

Related

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

How to update existing share folder attributes?

I want to change "Enable access based enumeration", "allowing cache of share" and "encrypt data access" attributes on share folder using COM Api's or WMI's.
I was previously using Win32_Share to create share but this does not have properties to assign these attributes.
but then I came to know about 'MSFT_SmbShare' class but I can see only CreateShare method. I was to enable/disable these flags on exiting share but not able to find any UpdateShare method.
MSFT_SmbShare class
Please suggest a way to toggle these flags on a share by any of COM API's or WMI's.
To change these settings, you can use the NetShareSetInfo function available in the Windows SDK with the level 1005.
All flags are defined here, but note the documentation does not exhibit the SHI1005_FLAGS_ENCRYPT_DATA (0x08000) which is indeed in the corresponding Windows header file LMERR.H.
Here is a sample that demonstrate how to use it in a C# console app:
class Program
{
static void Main(string[] args)
{
// get flags of "myshare" share
var flags = NetShareUtilities.Get1005Flags(null, "myshare");
// add the "Require encryption" flag
flags |= SHI1005_FLAGS.SHI1005_FLAGS_ENCRYPT_DATA;
// save flags (you'll need to have admin rights for this)
NetShareUtilities.Set1005Flags(null, "myshare", flags);
}
}
Here is the NetShareUtilities class that uses P/Invoke to get to the Windows API:
public static class NetShareUtilities
{
[DllImport("netapi32.dll")]
private extern static int NetShareSetInfo([MarshalAs(UnmanagedType.LPWStr)] string servername, [MarshalAs(UnmanagedType.LPWStr)] string netname, int level, ref SHI1005_FLAGS buf, IntPtr parm_err);
[DllImport("netapi32.dll")]
private extern static int NetShareGetInfo([MarshalAs(UnmanagedType.LPWStr)] string servername, [MarshalAs(UnmanagedType.LPWStr)] string netname, int level, out IntPtr bufptr);
[DllImport("netapi32.dll")]
private static extern IntPtr NetApiBufferFree(IntPtr Buffer);
public static SHI1005_FLAGS Get1005Flags(string serverName, string name)
{
IntPtr ptr;
int err = NetShareGetInfo(serverName, name, 1005, out ptr);
if (err != 0)
throw new Win32Exception(err);
var flags = (SHI1005_FLAGS)Marshal.ReadInt32(ptr);
NetApiBufferFree(ptr);
return flags;
}
public static void Set1005Flags(string serverName, string name, SHI1005_FLAGS flags)
{
// note: you need to have enough rights to call this
int err = NetShareSetInfo(serverName, name, 1005, ref flags, IntPtr.Zero);
if (err != 0)
throw new Win32Exception(err);
}
}
[Flags]
public enum SHI1005_FLAGS
{
// note: all values are taken from LMERR.H
SHI1005_FLAGS_DFS = 0x0001,
SHI1005_FLAGS_DFS_ROOT = 0x0002,
// these 3 ones are not strict flags, you'll need to use a mask as specified in the official documentation
CSC_CACHE_AUTO_REINT = 0x0010,
CSC_CACHE_VDO = 0x0020,
CSC_CACHE_NONE = 0x0030,
SHI1005_FLAGS_RESTRICT_EXCLUSIVE_OPENS = 0x00100,
SHI1005_FLAGS_FORCE_SHARED_DELETE = 0x00200,
SHI1005_FLAGS_ALLOW_NAMESPACE_CACHING = 0x00400,
SHI1005_FLAGS_ACCESS_BASED_DIRECTORY_ENUM = 0x00800,
SHI1005_FLAGS_FORCE_LEVELII_OPLOCK = 0x01000,
SHI1005_FLAGS_ENABLE_HASH = 0x02000,
SHI1005_FLAGS_ENABLE_CA = 0x04000,
SHI1005_FLAGS_ENCRYPT_DATA = 0x08000,
SHI1005_FLAGS_RESERVED = 0x10000,
}
These values all seem to be properties on the MSFT_SmbShare class; specifically, FolderEnumerationMode, CachingMode and EncryptData.
After updating the values, use the Put_ method to update.

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

get unicode directory name in c#?

i am trying to get all directories from my usb drive using following code
DirectoryInfo di = new DirectoryInfo("H:\\");
DirectoryInfo[] allDir = di.GetDirectories();
this code works perfectly for directories with ascii names. but one directory has a name of
" "(unicode value U+00A0). GetDirectories() cannot get that directory. is there a way to get directory wiht unicode name?
Alright, so what you're fighting is the .NET framework uses this signature of FindFirstFile:
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
internal static SafeFindHandle FindFirstFile(string fileName, [In, Out] Win32Native.WIN32_FIND_DATA data);
To then add insult to injury, you're fighting the language:
Automatically marshal strings appropriately for the target operating system. The default is Unicode on Windows NT, Windows 2000, Windows XP, and the Windows Server 2003 family; the default is Ansi on Windows 98 and Windows Me. Although the common language runtime default is Auto, languages may override this default. For example, by default C# marks all methods and types as Ansi.
(from the CharSet enumeration documentation emphasis added)
The issue at hand is the CharSet parameter on the DllImport. This means that you're left with one approach; leverage the P/Invoke on your own. You'll need quite a few things. First you'll need the data structure that is returned:
[BestFitMapping(false)]
[Serializable]
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal class WIN32_FIND_DATA
{
internal int dwFileAttributes;
internal uint ftCreationTime_dwLowDateTime;
internal uint ftCreationTime_dwHighDateTime;
internal uint ftLastAccessTime_dwLowDateTime;
internal uint ftLastAccessTime_dwHighDateTime;
internal uint ftLastWriteTime_dwLowDateTime;
internal uint ftLastWriteTime_dwHighDateTime;
internal int nFileSizeHigh;
internal int nFileSizeLow;
internal int dwReserved0;
internal int dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
internal string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
internal string cAlternateFileName;
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public WIN32_FIND_DATA()
{
}
}
Next you'll need the right import for FindFirstFile:
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)]
internal static SafeFindHandle FindFirstFile(string fileName, [In, Out] Win32Native.WIN32_FIND_DATA data);
Next you'll need GetLastWin32Error to check for errors; that can be accessed via Marshal.GetLastWin32Error in the InteropServices namespace.
Next you're going to need to iterate, so you'll need FindNextFile:
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)]
internal static bool FindNextFile(SafeFindHandle hndFindFile, [MarshalAs(UnmanagedType.LPStruct), In, Out] Win32Native.WIN32_FIND_DATA lpFindFileData);
Armed with these you can build your own iterator. However, for your benefit here is what Microsoft does for "init":
[SecurityCritical]
private void CommonInit()
{
string fileName = Path.InternalCombine(this.searchData.fullPath, this.searchCriteria);
Win32Native.WIN32_FIND_DATA wiN32FindData = new Win32Native.WIN32_FIND_DATA();
this._hnd = Win32Native.FindFirstFile(fileName, wiN32FindData);
if (this._hnd.IsInvalid)
{
int lastWin32Error = Marshal.GetLastWin32Error();
switch (lastWin32Error)
{
case 2:
case 18:
this.empty = this.searchData.searchOption == SearchOption.TopDirectoryOnly;
break;
default:
this.HandleError(lastWin32Error, this.searchData.fullPath);
break;
}
}
if (this.searchData.searchOption == SearchOption.TopDirectoryOnly)
{
if (this.empty)
{
this._hnd.Dispose();
}
else
{
SearchResult searchResult = this.CreateSearchResult(this.searchData, wiN32FindData);
if (!this._resultHandler.IsResultIncluded(searchResult))
return;
this.current = this._resultHandler.CreateObject(searchResult);
}
}
else
{
this._hnd.Dispose();
this.searchStack.Add(this.searchData);
}
}
and here is what they do for "iterate" for what you're looking for:
if (this.searchData != null && this._hnd != null)
{
while (Win32Native.FindNextFile(this._hnd, wiN32FindData))
{
SearchResult searchResult = this.CreateSearchResult(this.searchData, wiN32FindData);
if (this._resultHandler.IsResultIncluded(searchResult))
{
if (this.needsParentPathDiscoveryDemand)
{
this.DoDemand(this.searchData.fullPath);
this.needsParentPathDiscoveryDemand = false;
}
this.current = this._resultHandler.CreateObject(searchResult);
return true;
}
}
int lastWin32Error = Marshal.GetLastWin32Error();
if (this._hnd != null)
this._hnd.Dispose();
if (lastWin32Error != 0 && lastWin32Error != 18 && lastWin32Error != 2)
this.HandleError(lastWin32Error, this.searchData.fullPath);
}
NOTE: you're not going to be able to use this code directly, you'll have to fit it to your solution, but this is a huge jump start on the API's. Get you a copy of dotPeek to fill in the gaps.
NOTE: the fileName parameter accepted by FindFirstFile is going to the parent directory. In your case, H:\.

When did SECURITY_ATTRIBUTES change and why?

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();
}
}

Categories