Open Directory Using CreateFile - c#

I am trying to utilize the CreateFile function to access directory information. I am recieving a win32 error code of 5 however, which means Access Denied. Please advise.
CreateFile(path, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero);
This is the call being made, and as noted in the documentation the 'FILE_FLAG_BACKUP_SEMANTICS' is being used. The DLL import seems to be working fine and looks like the following:
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateFile(string filename,
uint desiredAccess,
uint sharedMode,
IntPtr securityAttributes,
uint creationDisposition,
uint flagsAndAttributes,
IntPtr templateFile);
Update: I need to obtain the handle to a directory so i can use GetFileInformationByHandle() and extract the unique id. This method currently works with files, it is not working with directories currently.
Update: The X for this question is i need a unique identifier of a directory that is something other than its absolute path. It needs to remain the same even if directory is moved or renamed. .NET does not provide any unique identifiers as just mentioned, it can only be accomplished by using win32

First of all you should include manifest in your application to be sure that it runs under Administrator privileges. Then you should enable SE_BACKUP_NAME privilege using AdjustTokenPrivileges API. Then I would recommend you to use FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE flags as the sharedMode. Now you should be able to use CreateFile to open the directory handle and use GetFileInformationByHandle to get BY_HANDLE_FILE_INFORMATION.
UPDATED: Probably the following simple demo program can help you
#include <windows.h>
#include <tchar.h>
int _tmain()
{
HANDLE hAccessToken = NULL;
HANDLE hFile = INVALID_HANDLE_VALUE;
__try {
LUID luidPrivilege;
DWORD dwErrorCode;
BY_HANDLE_FILE_INFORMATION fiFileInfo;
// -----------------------------------------------------
// first of all we need anable SE_BACKUP_NAME privilege
// -----------------------------------------------------
if (!OpenProcessToken (GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&hAccessToken))
__leave;
if (LookupPrivilegeValue (NULL, SE_BACKUP_NAME, &luidPrivilege)) {
TOKEN_PRIVILEGES tpPrivileges;
tpPrivileges.PrivilegeCount = 1;
tpPrivileges.Privileges[0].Luid = luidPrivilege;
tpPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges (hAccessToken, FALSE, &tpPrivileges,
0, NULL, NULL);
if ((dwErrorCode = GetLastError ()) != ERROR_SUCCESS)
__leave;
}
else
__leave;
// -----------------------------------------------------
// now one can open directory and get
// -----------------------------------------------------
hFile = CreateFile (TEXT("C:\\"),
0, //GENERIC_READ,
0, //FILE_SHARE_READ, //FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
__leave;
if (!GetFileInformationByHandle (hFile, &fiFileInfo))
__leave;
_tprintf(TEXT("VolumeSerialNumber: 0x%08X\n"), fiFileInfo.dwVolumeSerialNumber);
_tprintf(TEXT("FileIndex: 0x%08X%08X\n"), fiFileInfo.nFileIndexHigh, fiFileInfo.nFileIndexLow);
}
__finally {
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle (hFile);
if (hAccessToken != NULL)
CloseHandle (hAccessToken);
}
return 0;
}
The program opens C:\ directory and display Volume Serial Number and File Index which identify the directory on the NTFS. To make program shorter I removed all error messages (see __leave statements). Like I already mention before you should use requireAdministrator as "UAC Execution Level" (see "Manifest File" part of the Linker settings). The above code is tested and it work at me. You can reproduce the same code in C#.

UPDATED: Probably the following simple demo program can help you...
I tried to SetFileTime() but it was wrong. I have modified so:
hFile = CreateFile( TEXT("C:\\MyDirectory"),
// 0, //GENERIC_READ,
GENERIC_READ | GENERIC_WRITE,
// 0, //FILE_SHARE_READ, //FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
and it is ok. Thanks. Andre.

Related

How to specify to createfilew function get uncached result?

I need to check whether a file (on a remote server via UNC path) exists or not (permission is not a problem here; I make a required impersonation etc.).
I use CreateFileW function for creating file handle.
I've also tried GetFileAttributesEx but the behavior is the same.
HANDLE CreateFileW(
LPCWSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
If I deal with UNC paths I might get wrong result because of UNC cache (different process copies or removes a file that I need to check).
It depends on FileNotFoundCacheLifetime registry key value (by default the value is 10 seconds).
// lets say I would like to check a file
// in the beginning the file exists
// than another process delete this file
// (e.g. executing drop database command by sql server)
// than another process copies this file back
// and all steps above takes less then FileNotFoundCacheLifetime value
// path = #"\\server\C$\Tmp\Folder\database\myDb.mdf"
private static void Test(string path)
{
File.Exists(path); //exists
Directory.Exists(Path.GetDirectoryName(path)); //exists
using (var handle = CreateFile(path, EFileAccess.GenericRead, EFileShare.Read, IntPtr.Zero,
ECreationDisposition.OpenExisting, EFileAttributes.Normal, IntPtr.Zero))
{
if (handle == null || handle.IsInvalid)
{
//FileNotFoundCacheLifetime = 0 => exists
//FileNotFoundCacheLifetime = 10 => Win32Exception - The system cannot find the file specified
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
}
}
[DllImport("kernel32.dll", EntryPoint = "CreateFileW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern SafeFileHandle CreateFile(
string lpFileName,
EFileAccess dwDesiredAccess,
EFileShare dwShareMode,
IntPtr lpSecurityAttributes,
ECreationDisposition dwCreationDisposition,
EFileAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile);
#region CreateFile
[Flags]
public enum EFileAccess : uint
{
GenericRead = 0x80000000,
GenericWrite = 0x40000000,
GenericExecute = 0x20000000,
GenericAll = 0x10000000,
}
[Flags]
public enum EFileShare : uint
{
None = 0x00000000,
Read = 0x00000001,
Write = 0x00000002,
Delete = 0x00000004,
}
public enum ECreationDisposition : uint
{
New = 1,
CreateAlways = 2,
OpenExisting = 3,
OpenAlways = 4,
TruncateExisting = 5,
}
[Flags]
public enum EFileAttributes : uint
{
Readonly = 0x00000001,
Hidden = 0x00000002,
System = 0x00000004,
Directory = 0x00000010,
Archive = 0x00000020,
Device = 0x00000040,
Normal = 0x00000080,
Temporary = 0x00000100,
SparseFile = 0x00000200,
ReparsePoint = 0x00000400,
Compressed = 0x00000800,
Offline = 0x00001000,
NotContentIndexed = 0x00002000,
Encrypted = 0x00004000,
Write_Through = 0x80000000,
Overlapped = 0x40000000,
NoBuffering = 0x20000000,
RandomAccess = 0x10000000,
SequentialScan = 0x08000000,
DeleteOnClose = 0x04000000,
BackupSemantics = 0x02000000,
PosixSemantics = 0x01000000,
OpenReparsePoint = 0x00200000,
OpenNoRecall = 0x00100000,
FirstPipeInstance = 0x00080000
}
#endregion CreateFile
Do you know how to get uncached result ?
Basically I can disable UNC cache I know about it.
Here I need a different approach - how to get uncached result precisely for a specific method call.
I know about the following approach - $NOCSC$ (#"\\server$NOCSC$\C$\folder\file") modifier but unfortunately it does not work on all operating systems.
File.Exists() and Folder.Exists() works but I need fileapi because it supports long paths (basically it's only one working solution).
Another good solution for me is cleaning UNC file system cache programmatically (again precisely before a specific method call).
There is a fix for long paths (as per your comment to this post), at blogs mdsn and it is applicant for .net 4.6.2 and above.
You will have to add the application manifest file item to your project, there is a detailed guide in the blog for the process but all to all, it seems like a good solution to your issue without any necessary headache.
In the example they do test it on Console Application, so you will have to figure how to make proper adjustments to your solution.

C# get default application for file type

I'm using this code to get the default application for file types like ".txt".
It works completely fine but why do I have to call the same method two times? The only thing I found out is, that the lenth is set after the first call. But do I really have to execute the function twice because I need the length first?
If I execute it only once, it crashes.
CODE
using System.Runtime.InteropServices;
[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
public static extern uint AssocQueryString(
AssocF flags,
AssocStr str,
string pszAssoc,
string pszExtra,
[Out] StringBuilder pszOut,
ref uint pcchOut
);
[Flags]
public enum AssocF
{
None = 0,
Init_NoRemapCLSID = 0x1,
Init_ByExeName = 0x2,
Open_ByExeName = 0x2,
Init_DefaultToStar = 0x4,
Init_DefaultToFolder = 0x8,
NoUserSettings = 0x10,
NoTruncate = 0x20,
Verify = 0x40,
RemapRunDll = 0x80,
NoFixUps = 0x100,
IgnoreBaseClass = 0x200,
Init_IgnoreUnknown = 0x400,
Init_Fixed_ProgId = 0x800,
Is_Protocol = 0x1000,
Init_For_File = 0x2000
}
public enum AssocStr
{
Command = 1,
Executable,
FriendlyDocName,
FriendlyAppName,
NoOpen,
ShellNewValue,
DDECommand,
DDEIfExec,
DDEApplication,
DDETopic,
InfoTip,
QuickTip,
TileInfo,
ContentType,
DefaultIcon,
ShellExtension,
DropTarget,
DelegateExecute,
Supported_Uri_Protocols,
ProgID,
AppID,
AppPublisher,
AppIconReference,
Max
}
SAMPLE USAGE:
static string AssocQueryString(AssocStr association, string extension)
{
const int S_OK = 0;
const int S_FALSE = 1;
uint length = 0;
uint ret = AssocQueryString(AssocF.None, association, extension, null, null, ref length);
if (ret != S_FALSE)
{
throw new InvalidOperationException("Could not determine associated string");
}
var sb = new StringBuilder((int)length); // (length-1) will probably work too as the marshaller adds null termination
ret = AssocQueryString(AssocF.None, association, extension, null, sb, ref length);
if (ret != S_OK)
{
throw new InvalidOperationException("Could not determine associated string");
}
return sb.ToString();
}
AssocQueryString is a WinAPI function. This is a kind of system-level general-purpose function, which might be used in different kinds of applications. Using applications might happen to have VERY tight performance and/or memory requirements. That's why WinAPI functions never do memory allocations themselves (as memory allocation might be a relatively expensive task performance-wise), they expect all the required memory to be provided to them by the caller.
In many cases (like with this AssocQueryString functionality) one can not know the required amount of memory before the function execution. Here the API developers "merged" two functions into one: if you call AssocQueryString with null instead of output string it will calculate you the required string length, otherwise it will use the string you've provided expecting that you've already allocated enough memory for that string.
You don't need to worry about calling the function twice. In fact, you're calling two slightly different functions: one is to calculate the required string length and another is to actually do the job (i.e. search the registry for a file association).

Send key from C# to interrupt Fortran process

I need to communicate with a command line Fortran app using a c# wrapper. The Fortran process is started using the following c# code.
var process = new Process();
process.StartInfo = new ProcessStartInfo(pathToFortranExe)
{
WorkingDirectory = directory,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true
};
...
//listen for prompts from the Fortran program
//and send replies using standardInput as follows
process.StandardInput.WriteLine(data);
When the Fortran program is waiting for user input on the command line I can successfully send messages using the above code.
Now here is the problem. The Fortran program uses long running analysis loops which can be interrupted by sending keys such as Esq or Q. I've been told this interrupt feature is implemented in the Fortran code using the Intel Fortran command PEEKCHARQQ. When I try and trigger these keys from c# using StandardInput they are ignored by the Fortran program. To send these interrupt signals I use:
char key = 'q'
process.StandardInput.Write(key);
//Note that StandardInput.AutoFlush==true
I've also tried SendMessage via pinvoke, but again no luck so far:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
private const UInt32 WM_KEYDOWN = 0x0100;
private const UInt32 WM_KEYUP = 0x0101;
public static void SendKey(Process process, char key)
{
var keyCode = (IntPtr)key;
var hWnd = process.Handle;
SendMessage(hWnd, WM_KEYDOWN, keyCode, IntPtr.Zero);
SendMessage(hWnd, WM_KEYUP, keyCode, IntPtr.Zero);
}
So the question is: Are there any other ways to place keys into the keyboard buffer such that they might be picked up PEEKCHARQQ in the Fortran process? Or anything else that I might be missing here?
Update 1:
I've also tried WriteConsoleInput, but I don't think I have the right handle:
var keyCode = (short)key;
var hWnd = process.Handle;
INPUT_RECORD[] lpBuffer = new INPUT_RECORD[1];
lpBuffer[0].KeyEvent.wVirtualKeyCode = keyCode;
int nLength = lpBuffer.Length;
int lpNumberOfEventsWritten;
if (!WriteConsoleInput(
hWnd,
lpBuffer,
nLength,
out lpNumberOfEventsWritten))
{
//this results error code 6: Invalid handle
Console.WriteLine("Error: {0}", GetLastError());
}
You can write to the console input buffer for the child process using a handle to the CONIN$ device with the WriteConsoleInput API.
Two Fortran programs below to demonstrate. The program for the child process:
PROGRAM peek_a_boo
USE IFCORE
IMPLICIT NONE
CHARACTER(*), PARAMETER :: fmt = "('Press any key!')"
PRINT fmt
DO WHILE (.NOT. PEEKCHARQQ())
CALL SLEEPQQ(2000)
PRINT fmt
END DO
END PROGRAM peek_a_boo
The program that shows the Windows API calls. You can translate this to whichever language floats your boat.
PROGRAM peek_parent
USE IFWIN
IMPLICIT NONE
INTEGER(BOOL) :: api_result
INTEGER(HANDLE) :: console_input
TYPE(T_STARTUPINFO) :: startup_info
TYPE(T_PROCESS_INFORMATION) :: process_info
TYPE(T_INPUT_RECORD) :: input_record
INTEGER(DWORD) :: events_written
INTEGER(DWORD) :: wait_event
! Create the child process.
CALL ZeroMemory(LOC(startup_info), SIZEOF(startup_info))
startup_info%cb = SIZEOF(startup_info)
api_result = CreateProcess( &
'peek_a_boo.exe', &
NULL, & ! command line.
NULL, & ! process security attributes.
NULL, & ! thread security attributes.
.FALSE., & ! inherit handles.
0_DWORD, &
NULL, & ! environment.
NULL, & ! current directory.
startup_info, &
process_info )
IF (api_result == 0) THEN
ERROR STOP 'Couldn''t start it :('
END IF
api_result = CloseHandle(process_info%hThread)
! Let the child run for a bit.
CALL SLEEPQQ(5000)
! Get a handle to our console input buffer.
console_input = CreateFile( &
'CONIN$', &
GENERIC_WRITE, &
IANY([FILE_SHARE_READ, FILE_SHARE_WRITE]), &
NULL, & ! security attrs
OPEN_EXISTING, &
0_DWORD, & ! attributes
NULL ) ! template file
IF (console_input == 0) THEN
ERROR STOP 'Couldn''t open it :('
END IF
! Poke something into the buffer.
input_record%EventType = KEY_EVENT
input_record%KeyEvent%bKeyDown = .TRUE.
input_record%KeyEvent%wRepeatCount = 1
input_record%KeyEvent%wVirtualKeyCode = INT(Z'51', WORD)
input_record%KeyEvent%wVirtualScanCode = INT(Z'51', WORD)
input_record%KeyEvent%AsciiChar = 'Q'
input_record%KeyEvent%dwControlKeyState = 0
api_result = WriteConsoleInput( &
console_input, &
input_record, &
1, &
LOC(events_written) )
! Wait for the child to terminate.
wait_event = WaitForSingleObject(process_info%hProcess, INFINITE)
api_result = CloseHandle(console_input)
api_result = CloseHandle(process_info%hProcess)
END PROGRAM peek_parent

Why ConvertSecurityDescriptorToStringSecurityDescriptor produce different SDDL when directory inheritance is changed?

We use SDDL to compare directories permissions.
In most cases, when I use ConvertSecurityDescriptorToStringSecurityDescriptor or GetSecurityDescriptorSddlForm I get identical results (SDDL).
But when the directory permissions inheritance is changed the pseudo code below will produce different strings (SDDL).
What do I need to do in the Win32 implementation to make it produce same SDDL as the .Net?
Win32 (called from a .Net app):
IntPtr pSidOwner = IntPtr.Zero;
IntPtr pSidGroup = IntPtr.Zero;
IntPtr pDacl = IntPtr.Zero;
IntPtr pSacl = IntPtr.Zero;
IntPtr pSecurityDescriptor = IntPtr.Zero;
uint errorReturn = GetNamedSecurityInfo(path, SE_OBJECT_TYPE.SE_FILE_OBJECT, ACS, out pSidOwner, out pSidGroup, out pDacl, out pSacl, out pSecurityDescriptor);
if (errorReturn != 0)
{
throw new Win32Exception((int)errorReturn);
}
int len = 0;
IntPtr pBuffer = IntPtr.Zero;
if (ConvertSecurityDescriptorToStringSecurityDescriptor(pSecurityDescriptor, 1, si, out pBuffer, out len))
{
String sddl = Marshal.PtrToStringAuto(pBuffer);
}
.Net 4.5:
FileSystemSecurity fss = Directory.GetAccessControl(path);
String sddl = fss.GetSecurityDescriptorSddlForm(ACS);
ACS = Owner and DACL
[edit]
The issue was found elsewhere
I suspected the order but after further investigation found that the issue was a missing flag in the code that make use of the SDDL string.
Apparently I had to use both DACL_SECURITY_INFORMATION and UNPROTECTED_DACL_SECURITY_INFORMATION when calling SetNamedSecurityInfo to set DACL. Adding the flag solved the issue.
Thank you Harry for looking into this!
The issue was found elsewhere (please see edit for details)
From what we found ConvertSecurityDescriptorToStringSecurityDescriptor and GetSecurityDescriptorSddlForm produce the same SDDL string (different order in some cases).

Finding the default application for opening a particular file type on Windows

I'm developing an application targeting .NET Framework 2.0 using C# for which I need to be able to find the default application that is used for opening a particular file type.
I know that, for example, if you just want to open a file using that application you can use something like:
System.Diagnostics.Process.Start( "C:\...\...\myfile.html" );
to open an HTML document in the default browser, or
System.Diagnostics.Process.Start( "C:\...\...\myfile.txt" );
to open a text file in the default text editor.
However, what I want to be able to do is to open files that don't necessarily have a .txt extension (for example), in the default text editor, so I need to be able to find out the default application for opening .txt files, which will allow me to invoke it directly.
I'm guessing there's some Win32 API that I'll need to P/Invoke in order to do this, however a quick look with both Google and MSDN didn't reveal anything of much interest; I did find a very large number of completely irrelevant pages, but nothing like I'm looking for.
All current answers are unreliable. The registry is an implementation detail and indeed such code is broken on my Windows 8.1 machine. The proper way to do this is using the Win32 API, specifically AssocQueryString:
using System.Runtime.InteropServices;
[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
public static extern uint AssocQueryString(
AssocF flags,
AssocStr str,
string pszAssoc,
string pszExtra,
[Out] StringBuilder pszOut,
ref uint pcchOut
);
[Flags]
public enum AssocF
{
None = 0,
Init_NoRemapCLSID = 0x1,
Init_ByExeName = 0x2,
Open_ByExeName = 0x2,
Init_DefaultToStar = 0x4,
Init_DefaultToFolder = 0x8,
NoUserSettings = 0x10,
NoTruncate = 0x20,
Verify = 0x40,
RemapRunDll = 0x80,
NoFixUps = 0x100,
IgnoreBaseClass = 0x200,
Init_IgnoreUnknown = 0x400,
Init_Fixed_ProgId = 0x800,
Is_Protocol = 0x1000,
Init_For_File = 0x2000
}
public enum AssocStr
{
Command = 1,
Executable,
FriendlyDocName,
FriendlyAppName,
NoOpen,
ShellNewValue,
DDECommand,
DDEIfExec,
DDEApplication,
DDETopic,
InfoTip,
QuickTip,
TileInfo,
ContentType,
DefaultIcon,
ShellExtension,
DropTarget,
DelegateExecute,
Supported_Uri_Protocols,
ProgID,
AppID,
AppPublisher,
AppIconReference,
Max
}
Relevant documentation:
AssocQueryString
ASSOCF
ASSOCSTR
Sample usage:
static string AssocQueryString(AssocStr association, string extension)
{
const int S_OK = 0;
const int S_FALSE = 1;
uint length = 0;
uint ret = AssocQueryString(AssocF.None, association, extension, null, null, ref length);
if (ret != S_FALSE)
{
throw new InvalidOperationException("Could not determine associated string");
}
var sb = new StringBuilder((int)length); // (length-1) will probably work too as the marshaller adds null termination
ret = AssocQueryString(AssocF.None, association, extension, null, sb, ref length);
if (ret != S_OK)
{
throw new InvalidOperationException("Could not determine associated string");
}
return sb.ToString();
}
You can check under registry section HKEY_CLASSES_ROOT for the extension and action details. Documentation for this is on MSDN. Alternatively, you can use the IQueryAssociations interface.
Doh! Of course.
HKEY_CLASSES_ROOT\.txt
includes a reference to
HKEY_CLASSES_ROOT\txtfile
which contains a subkey
HKEY_CLASSES_ROOT\txtfile\shell\open\command
which references Notepad.
Sorted, many thanks!
Bart
Here is a blog post with about this topic. The code samples are in VB.net, but it should be easy to port them to C#.
You can just query the registry. First get the Default entry under HKEY_CLASSES_ROOT\.ext
That will give you the classname. For example .txt has a default of txtfile
Then open up HKEY_CLASSES_ROOT\txtfile\Shell\Open\Command
That will give you the default command used.
A late answer, but there is a good NUGET package that handles file associations: File Association
Link NUGET File Association
Usage is simple, for instance to add all allowed file extensions to a context menu:
private void OnMenuSourceFileOpening(object sender, ...)
{ // open a context menu with the associated files + ".txt" files
if (File.Exists(this.SelectedFileName))
{
string fileExt = Path.GetExtension(this.SelectedFileNames);
string[] allowedExtensions = new string[] { fileExt, ".txt" };
var fileAssociations = allowedExtensions
.Select(ext => new FileAssociationInfo(ext));
var progInfos = fileAssociations
.Select(fileAssoc => new ProgramAssociationInfo (fileAssoc.ProgID));
var toolstripItems = myProgInfos
.Select(proginfo => new ToolStripLabel (proginfo.Description) { Tag = proginfo });
// add also the prog info as Tag, for easy access
// when the toolstrip item is selected
// of course this can also be done in one long linq statement
// fill the context menu:
this.contextMenu1.Items.Clear();
this.contextMenuOpenSourceFile.Items.AddRange (toolstripItems.ToArray());
}
}

Categories