I have the ntdll.dll's NtCreateFile() function hooked to allow/deny the access of certain files. Unlike kernel32.dll's CreateFile() which easily gives you the full path to the file in question, ntdll.dll's NtCreateFile() function only gives you the handle to the file. I need to obtain the full path of the file from a file handle, to consequently allow/deny access. I've searched around and there doesn't seem to be a working C# solution.
This solution is in C++, and documented from Microsoft. I've tried to port it over to C# with not much success. Here is my attempt at the C# equivalent of the C++ version of "obtaining a filename from a file handle":
public string GetFileNameFromHandle(IntPtr FileHandle)
{
string fileName = String.Empty;
IntPtr fileMap = IntPtr.Zero, fileSizeHi = IntPtr.Zero;
UInt32 fileSizeLo = 0;
fileSizeLo = GetFileSize(FileHandle, fileSizeHi);
if (fileSizeLo == 0 && fileSizeHi == IntPtr.Zero)
{
// cannot map an 0 byte file
return String.Empty;
}
fileMap = CreateFileMapping(FileHandle, IntPtr.Zero, FileMapProtection.PageReadonly, 0, 1, null);
if (fileMap != IntPtr.Zero)
{
IntPtr pMem = MapViewOfFile(fileMap, FileMapAccess.FileMapRead, 0, 0, 1);
if (pMem != IntPtr.Zero)
{
StringBuilder fn = new StringBuilder(250);
GetMappedFileName(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle, pMem, fn, 250);
if (fileName.Length > 0)
{
UnmapViewOfFile(pMem);
CloseHandle(FileHandle);
return fn.ToString();
}
else
{
UnmapViewOfFile(pMem);
CloseHandle(FileHandle);
return String.Empty;
}
}
}
return String.Empty;
}
I have, of course, all the necessary DLLImports and user-defined types. When I use this function on handles, I get an empty string in return. It's also pretty hard to debug this, since this method is in a DLL that gets injected into a target process, not like something you can set a breakpoint at and enjoy Visual Studio's debugging system. I guess I could write a log file or some trace system, but I'm not that desperate yet. I just need a successful C# version of "get filename from file handle".
Any insight, code fixes, links?
Solved it myself. Here's the working code with the references and stuff.
[DllImport("kernel32.dll")]
static extern uint GetFileSize(IntPtr hFile, IntPtr lpFileSizeHigh);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr CreateFileMapping(
IntPtr hFile,
IntPtr lpFileMappingAttributes,
FileMapProtection flProtect,
uint dwMaximumSizeHigh,
uint dwMaximumSizeLow,
[MarshalAs(UnmanagedType.LPTStr)]string lpName);
[Flags]
public enum FileMapProtection : uint
{
PageReadonly = 0x02,
PageReadWrite = 0x04,
PageWriteCopy = 0x08,
PageExecuteRead = 0x20,
PageExecuteReadWrite = 0x40,
SectionCommit = 0x8000000,
SectionImage = 0x1000000,
SectionNoCache = 0x10000000,
SectionReserve = 0x4000000,
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr MapViewOfFile(
IntPtr hFileMappingObject,
FileMapAccess dwDesiredAccess,
uint dwFileOffsetHigh,
uint dwFileOffsetLow,
uint dwNumberOfBytesToMap);
[Flags]
public enum FileMapAccess : uint
{
FileMapCopy = 0x0001,
FileMapWrite = 0x0002,
FileMapRead = 0x0004,
FileMapAllAccess = 0x001f,
fileMapExecute = 0x0020,
}
[DllImport("psapi.dll", SetLastError = true)]
public static extern uint GetMappedFileName(IntPtr m_hProcess, IntPtr lpv, StringBuilder
lpFilename, uint nSize);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
public static string GetFileNameFromHandle(IntPtr FileHandle)
{
string fileName = String.Empty;
IntPtr fileMap = IntPtr.Zero, fileSizeHi = IntPtr.Zero;
UInt32 fileSizeLo = 0;
fileSizeLo = GetFileSize(FileHandle, fileSizeHi);
if (fileSizeLo == 0)
{
// cannot map an 0 byte file
return "Empty file.";
}
fileMap = CreateFileMapping(FileHandle, IntPtr.Zero, FileMapProtection.PageReadonly, 0, 1, null);
if (fileMap != IntPtr.Zero)
{
IntPtr pMem = MapViewOfFile(fileMap, FileMapAccess.FileMapRead, 0, 0, 1);
if (pMem != IntPtr.Zero)
{
StringBuilder fn = new StringBuilder(250);
GetMappedFileName(System.Diagnostics.Process.GetCurrentProcess().Handle, pMem, fn, 250);
if (fn.Length > 0)
{
UnmapViewOfFile(pMem);
CloseHandle(FileHandle);
return fn.ToString();
}
else
{
UnmapViewOfFile(pMem);
CloseHandle(FileHandle);
return "Empty filename.";
}
}
}
return "Empty filemap handle.";
}
From http://msdn.microsoft.com/en-us/library/aa366789.aspx
"The following example obtains a file name from a handle to a file object using a file mapping object. It uses the CreateFileMapping and MapViewOfFile functions to create the mapping. Next, it uses the GetMappedFileName function to obtain the file name."
Code looks legit to me, hope that helps.
The code you posted here has been copied from the MSDN.
It has several disadvantages: It requires a real file bigger than 0 Bytes to work. It does not work for files of 0 Bytes nor does it work for directories. (and I'm not even talking about network drives)
I have posted a perfectly working code here:
How to get name associated with open HANDLE
Related
I have the following directory structure:
D:\LinkTest
├─Links
│ ├─LinkToTargetDirectory.lnk
│ └─LinkToTargetFile.lnk
└─Targets
├─TargetFile.txt
└─TargetDirectory
└─FileInTargetDirectory.txt
.lnk files are symbolic links.
They show up in Windows Explorer without the .lnk extension, with the respective target's icon, and double-clicking LinkToTargetDirectory shows the contents of D:\LinkTest\Targets\TargetDirectory, and double-clicking LinkToTargetFile opens the file D:\LinkTest\Targets\TargetFile.txt in notepad.
.txt files are text files.
Everything else are directories.
I would like to use the following code taken from this answer to get the path of the file/directory pointed to:
public static class NativeMethods
{
private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
private const uint FILE_READ_EA = 0x0008;
private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x2000000;
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint GetFinalPathNameByHandle(IntPtr hFile, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszFilePath, uint cchFilePath, uint dwFlags);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateFile(
[MarshalAs(UnmanagedType.LPTStr)] string filename,
[MarshalAs(UnmanagedType.U4)] uint access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] uint flagsAndAttributes,
IntPtr templateFile);
public static string GetFinalPathName(string path)
{
var h = CreateFile(path,
FILE_READ_EA,
FileShare.ReadWrite | FileShare.Delete,
IntPtr.Zero,
FileMode.Open,
FILE_FLAG_BACKUP_SEMANTICS,
IntPtr.Zero);
if (h == INVALID_HANDLE_VALUE)
throw new Win32Exception();
try
{
var sb = new StringBuilder(1024);
var res = GetFinalPathNameByHandle(h, sb, 1024, 0);
if (res == 0)
throw new Win32Exception();
return sb.ToString();
}
finally
{
CloseHandle(h);
}
}
}
What should happen:
Console.WriteLine(NativeMethods.GetFinalPathName(#"D:\LinkTest\Links\LinkToTargetDirectory.lnk"));
should print \\?\D:\LinkTest\Targets\TargetDirectory
Console.WriteLine(NativeMethods.GetFinalPathName(#"D:\LinkTest\Links\LinkToTargetFile.lnk"));
should print \\?\D:\LinkTest\Targets\TargetFile.txt
What actually happens:
Console.WriteLine(NativeMethods.GetFinalPathName(#"D:\LinkTest\Links\LinkToTargetDirectory.lnk"));
prints \\?\D:\LinkTest\Links\LinkToTargetDirectory.lnk
Console.WriteLine(NativeMethods.GetFinalPathName(#"D:\LinkTest\Links\LinkToTargetFile.lnk"));
prints \\?\D:\LinkTest\Links\LinkToTargetFile.lnk
So instead of getting the target's path, it just returns the path of the link file.
What is wrong with this code and how can I fix it?
I already found this: https://social.msdn.microsoft.com/forums/windowsdesktop/en-US/d8a26a7f-6661-48ce-b183-8e476dfffecd/am-i-using-getfinalpathnamebyhandle-incorrectly?forum=windowsgeneraldevelopmentissues
Passing 8 instead of 0 as the last argument to GetFinalPathNameByHandle seems to help that user, but it does not change the behavior for me.
Does anybody know how can get real path from symlink file or folder? Thank you!
Hello guys after my research I found this solution for how to get real path of a Symlink. If you have a created symlink and want to check where is the real pointer of this file or folder. If someone have better way to write it please share.
[DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr securityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport("kernel32.dll", EntryPoint = "GetFinalPathNameByHandleW", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern int GetFinalPathNameByHandle([In] SafeFileHandle hFile, [Out] StringBuilder lpszFilePath, [In] int cchFilePath, [In] int dwFlags);
private const int CREATION_DISPOSITION_OPEN_EXISTING = 3;
private const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
public static string GetRealPath(string path)
{
if (!Directory.Exists(path) && !File.Exists(path))
{
throw new IOException("Path not found");
}
SafeFileHandle directoryHandle = CreateFile(path, 0, 2, IntPtr.Zero, CREATION_DISPOSITION_OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero); //Handle file / folder
if (directoryHandle.IsInvalid)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
StringBuilder result = new StringBuilder(512);
int mResult = GetFinalPathNameByHandle(directoryHandle, result, result.Capacity, 0);
if (mResult < 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
if (result.Length >= 4 && result[0] == '\\' && result[1] == '\\' && result[2] == '?' && result[3] == '\\')
{
return result.ToString().Substring(4); // "\\?\" remove
}
return result.ToString();
}
I am reading directly from a disk using C# and pinvoking the kernel32 ReadFile method.i want just read a particular sector for save time but ReadFile read from first to N sector. How can read only own sector with my choice?
[StructLayout(LayoutKind.Sequential)]
public struct OVERLAPPED
{
public uint Internal;
public uint InternalHigh;
public uint Offset;
public uint OffsetHigh;
public int hEvent;
}
[DllImport("kernel32", SetLastError = true)]
static extern int CreateFile(string filename, uint desiredAccess, uint shareMode, IntPtr attributes, uint creationDisposition, uint flagsAndAttributes, IntPtr templateFile);
[DllImport("kernel32", SetLastError = true)]
public static extern Boolean CloseHandle(int handle);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern Boolean ReadFile(IntPtr hFile, Byte[] buffer, UInt32 BytesToRead, ref UInt32 BytedRead, OVERLAPPED OverLapped);
static int EIGHT_K = 8192;
static int FIVE_TWELVE_BYTES = 512;
static uint GENERIC_READ = 0x80000000;
static uint OPEN_EXISTING = 3;
static uint FILE_SHARE_READ = 1;
static uint FILE_SHARE_WRITE = 2;
[STAThread]
private void button1_Click(object sender, EventArgs e)
{
int fileHandle = 0;
bool returnVal = true;
try
{
// Open the device specified (Using the boot partition)
string deviceName = #"\\.\f:";
fileHandle = CreateFile(deviceName, GENERIC_READ,FILE_SHARE_READ | FILE_SHARE_WRITE, (IntPtr)0, OPEN_EXISTING, 0,(IntPtr)0);
if (fileHandle != -1)
{
Byte[] sector = new Byte[EIGHT_K];
UInt32 bytesRead = (uint)EIGHT_K;
OVERLAPPED ol = new OVERLAPPED();
// Can't get a FileStream ctor to work so I am using Win32 API ReadFile
bool worked = ReadFile((IntPtr)fileHandle, sector, (uint)EIGHT_K, ref bytesRead, ol);
return;
}
}
catch (Exception ex)
{
return;
}
finally
{
CloseHandle(fileHandle);
}
return;
}
I want to mark the DVD till required Original DVD to run the program.
Your OVERLAPPED struct is declared poorly and is incorrect in a 64 bit process. But in any case you don't need it. You are not performing overlapped I/O. Which is just as well because the declaration of ReadFile is incorrect. That function wants a pointer to an OVERLAPPED struct. You pass it by value.
In any case, you just don't need to consider overlapped I/O. So fix this issue by deleting the OVERLAPPED struct declaration from your code. And declare ReadFile like this:
[DllImport("kernel32.dll", SetLastError = true)]
public static extern Boolean ReadFile(IntPtr hFile, Byte[] buffer,
UInt32 BytesToRead, out UInt32 BytedRead, IntPtr Overlapped);
Pass IntPtr.Zero as the Overlapped parameter. And do make sure that you check the return value of ReadFile for an error.
The next step is to seek to a location in the file. Use SetFilePointerEx for that.
DllImport("kernel32.dll")]
static extern bool SetFilePointerEx(IntPtr hFile, long liDistanceToMove,
out long lpNewFilePointer, uint dwMoveMethod);
Consult the documentation for SetFilePointerEx to work out how to call this function.
Since you are using direct disk access, you will of course need to align the reads to sector boundaries.
I am trying to port the following C++ code:
BOOL SyskeyGetClassBytes(HKEY hKeyReg,LPSTR keyName,LPSTR valueName,LPBYTE classBytes) {
HKEY hKey,hSubKey;
DWORD dwDisposition=0,classSize;
BYTE classStr[16];
LONG ret;
BOOL isSuccess = FALSE;
ret = RegCreateKeyEx(hKeyReg,keyName,0,NULL,REG_OPTION_NON_VOLATILE,KEY_QUERY_VALUE,NULL,&hKey,&dwDisposition);
if(ret!=ERROR_SUCCESS)
return FALSE;
else if(dwDisposition!=REG_OPENED_EXISTING_KEY) {
RegCloseKey(hKey);
return FALSE;
}
else {
if(RegOpenKeyEx(hKey,valueName,0,KEY_READ,&hSubKey)==ERROR_SUCCESS) {
classSize = 8+1;
ret = RegQueryInfoKey(hSubKey,(LPTSTR)classStr,&classSize,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
if((ret==ERROR_SUCCESS)&&(classSize==8)) {
classBytes[0]= (HexDigitToByte(classStr[0]) << 4) | HexDigitToByte(classStr[1]);
classBytes[1]= (HexDigitToByte(classStr[2]) << 4) | HexDigitToByte(classStr[3]);
classBytes[2]= (HexDigitToByte(classStr[4]) << 4) | HexDigitToByte(classStr[5]);
classBytes[3]= (HexDigitToByte(classStr[6]) << 4) | HexDigitToByte(classStr[7]);
isSuccess = TRUE;
}
RegCloseKey(hSubKey);
}
RegCloseKey(hKey);
}
return isSuccess;
}
I spent like 5 hours trying to figure out my problem, with no success. I know for a fact that I am properly calling this method. My C# code is
unsafe static bool SyskeyGetClassBytes(RegistryHive hKeyReg, string keyName, string valueName, byte* classBytes)
{
UIntPtr hSubKey;
UIntPtr hKey;
RegResult tmp; ;
uint classSize;
StringBuilder classStr = new StringBuilder();
int ret;
bool isSuccess = false;
ret = RegCreateKeyEx(hKeyReg, keyName, 0, null, RegOption.NonVolatile, RegSAM.QueryValue, UIntPtr.Zero, out hKey, out tmp);
if (ret != 0)
{
return false;
}
else if (tmp != RegResult.OpenedExistingKey)
{
return false;
}
else
{
int res = RegOpenKeyEx(hKey, valueName, 0, (int)RegSAM.Read, out hSubKey);
if (res == 0)
{
classSize = 8 + 1;
ret = RegQueryInfoKey(hSubKey, out classStr, ref classSize, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if ((classSize == 8))
{
classBytes[0] = (byte)((byte)(HexDigitToByte(classStr[0]) << (byte)4) | HexDigitToByte(classStr[1]));
classBytes[1] = (byte)((byte)(HexDigitToByte(classStr[2]) << (byte)4) | HexDigitToByte(classStr[3]));
classBytes[2] = (byte)((byte)(HexDigitToByte(classStr[4]) << (byte)4) | HexDigitToByte(classStr[5]));
classBytes[3] = (byte)((byte)(HexDigitToByte(classStr[6]) << (byte)4) | HexDigitToByte(classStr[7]));
isSuccess = true;
}
RegCloseKey(hSubKey);
}
else
{
return false;
}
RegCloseKey(hKey);
}
return isSuccess;
}
Its a little bit hard for me to debug, but eventually I determined that the problem is occurring at this line. Execution seems to halt afterwards.
ret = RegQueryInfoKey(hSubKey, out classStr, ref classSize, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
I know this is not a problem with permissions, as this C# program is running with admin perms AND as the local system account. The method that I need that the .Net APIs don't offer is RegQueryInfoKey. My P/Invoke signatures and types used are:
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public unsafe byte* lpSecurityDescriptor;
public int bInheritHandle;
}
[Flags]
public enum RegOption
{
NonVolatile = 0x0,
Volatile = 0x1,
CreateLink = 0x2,
BackupRestore = 0x4,
OpenLink = 0x8
}
[Flags]
public enum RegSAM
{
QueryValue = 0x0001,
SetValue = 0x0002,
CreateSubKey = 0x0004,
EnumerateSubKeys = 0x0008,
Notify = 0x0010,
CreateLink = 0x0020,
WOW64_32Key = 0x0200,
WOW64_64Key = 0x0100,
WOW64_Res = 0x0300,
Read = 0x00020019,
Write = 0x00020006,
Execute = 0x00020019,
AllAccess = 0x000f003f
}
public enum RegResult
{
CreatedNewKey = 0x00000001,
OpenedExistingKey = 0x00000002
}
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern int RegOpenKeyEx(
UIntPtr hKey,
string subKey,
int ulOptions,
int samDesired,
out UIntPtr hkResult);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern int RegCloseKey(
UIntPtr hKey);
[DllImport("advapi32.dll", SetLastError = true)]
static extern int RegCreateKeyEx(
RegistryHive hKey,
string lpSubKey,
int Reserved,
string lpClass,
RegOption dwOptions,
RegSAM samDesired,
UIntPtr lpSecurityAttributes,
out UIntPtr phkResult,
out RegResult lpdwDisposition);
[DllImport("advapi32.dll", EntryPoint = "RegQueryInfoKey", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
extern private static int RegQueryInfoKey(
UIntPtr hkey,
out StringBuilder lpClass,
ref uint lpcbClass,
IntPtr lpReserved,
IntPtr lpcSubKeys,
IntPtr lpcbMaxSubKeyLen,
IntPtr lpcbMaxClassLen,
IntPtr lpcValues,
IntPtr lpcbMaxValueNameLen,
IntPtr lpcbMaxValueLen,
IntPtr lpcbSecurityDescriptor,
IntPtr lpftLastWriteTime);
The lpClass parameter is declared incorrectly. Pass the StringBuilder by value.
[DllImport("advapi32.dll")]
extern private static int RegQueryInfoKey(
UIntPtr hkey,
StringBuilder lpClass,
ref uint lpcbClass,
IntPtr lpReserved,
IntPtr lpcSubKeys,
IntPtr lpcbMaxSubKeyLen,
IntPtr lpcbMaxClassLen,
IntPtr lpcValues,
IntPtr lpcbMaxValueNameLen,
IntPtr lpcbMaxValueLen,
IntPtr lpcbSecurityDescriptor,
IntPtr lpftLastWriteTime
);
You also need to allocate the StringBuilder instance to have the desired capacity. So, allocate the StringBuilder like this:
StringBuilder classStr = new StringBuilder(255);//or whatever length you like
And then set classSize like this:
classSize = classStr.Capacity+1;
I removed the parameters to DllImport. Most are not necessary, and the SetLastError is incorrect.
There may be other issues with your code, but with these changes at least the call to RegQueryInfoKey will match your C++ code.
Try this signature for RegQueryInfoKey:
[DllImport("advapi32.dll", EntryPoint="RegQueryInfoKey", CallingConvention=CallingConvention.Winapi, SetLastError=true)]
extern private static int RegQueryInfoKey(
UIntPtr hkey,
out StringBuilder lpClass,
ref uint lpcbClass,
IntPtr lpReserved,
out uint lpcSubKeys,
out uint lpcbMaxSubKeyLen,
out uint lpcbMaxClassLen,
out uint lpcValues,
out uint lpcbMaxValueNameLen,
out uint lpcbMaxValueLen,
out uint lpcbSecurityDescriptor,
IntPtr lpftLastWriteTime);
You are not declaring them as out params and in the RegQueryInfoKey Win32 call they are _Out_opt_.
You need to initialize your StringBuilder with enough capacity to store a classSize number of characters.
classSize = 8 + 1;
classStr.Capacity = classSize;
ret = RegQueryInfoKey(hSubKey, out classStr, ref classSize, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
The marshaller will use the capacity set on the StringBuilder to send a buffer of the capacity size to the RegQueryInfoKey function. Without that you are probably corrupted memory.
See http://msdn.microsoft.com/en-us/library/s9ts558h.aspx#cpcondefaultmarshalingforstringsanchor3
I have to modify a text in a file programmatically with C#. What's the best way to do this and avoid rewriting the entire file content. I want to edit only some words and save it.
As others have said, you can easily append to an existing file. Inserting data in the middle of the file requires that you write out the remainder of the file. There's no way to just shift everything in the file. If you want to overwrite individual bytes in place, you can open a FileStream. You then use the "Seek" to go to the specific bytes you want to overwrite, and use Write, or WriteByte to overwrite the existing data with new data.
You can append to an existing file using StreamWriter and AppendText easily.
If you want to do more serious modifications, then I think you must read the contents of the file into memory in C# using StreamReader for example, modifying the contents programmatically of that stream, and then re-writing the file with the stream's contents.
I think that's how it is anyway.
If you are targeting windows, you can use the winapi CreateFile, ReadFile, WriteFile, etc. You can easily write to any spot in a file and write as much data as you want. You do not have to rewrite the file. This doesn't even require unsafe code. I limited the functionality of the functions (no asynch or wacky file mappings) just to get this done quickly, but it can read and write files no problem. To get this example to work, make a small text file 100 bytes or so, and make sure it is in the location of the exe. A little bit of this code comes from the MSDN website. I have modified it significantly though.
using System;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Windows.Forms;
using Path = System.IO.Path;
class Program {
static void Main() {
string sTestFile = Path.Combine(Path.GetDirectoryName(
Application.ExecutablePath), "Test.txt");
// test reading a file
WinApiFile File = new WinApiFile(sTestFile,
WinApiFile.DesiredAccess.GENERIC_READ);
byte[] aBuffer = new byte[1000];
uint cbRead = File.Read(aBuffer, 1000);
File.Close();
// Test writing the file
File.Open(WinApiFile.DesiredAccess.GENERIC_WRITE);
// write the time to a 10 byte offset in the file
// conver the date time to byte array
int i = 0;
foreach (var ch in DateTime.Now.ToString())
aBuffer[i++] = (byte)ch;
// now write it out
File.MoveFilePointer(10); // 10 byte offset
File.Write(aBuffer, (uint)i); // write the time
File.Dispose();
}
}
public class WinApiFile : IDisposable {
/* ---------------------------------------------------------
* private members
* ------------------------------------------------------ */
private SafeFileHandle _hFile = null;
private string _sFileName = "";
private bool _fDisposed;
/* ---------------------------------------------------------
* properties
* ------------------------------------------------------ */
public bool IsOpen { get { return (_hFile != null); } }
public SafeFileHandle Handle { get { return _hFile; } }
public string FileName {
get { return _sFileName; }
set {
_sFileName = (value ?? "").Trim();
if (_sFileName.Length == 0)
CloseHandle(_hFile);
}
}
public int FileLength {
get {
return (_hFile != null) ? (int)GetFileSize(_hFile,
IntPtr.Zero) : 0;
}
set {
if (_hFile == null)
return;
MoveFilePointer(value, MoveMethod.FILE_BEGIN);
if (!SetEndOfFile(_hFile))
ThrowLastWin32Err();
}
}
/* ---------------------------------------------------------
* Constructors
* ------------------------------------------------------ */
public WinApiFile(string sFileName,
DesiredAccess fDesiredAccess) {
FileName = sFileName;
Open(fDesiredAccess);
}
public WinApiFile(string sFileName,
DesiredAccess fDesiredAccess,
CreationDisposition fCreationDisposition) {
FileName = sFileName;
Open(fDesiredAccess, fCreationDisposition);
}
/* ---------------------------------------------------------
* Open/Close
* ------------------------------------------------------ */
public void Open(
DesiredAccess fDesiredAccess) {
Open(fDesiredAccess, CreationDisposition.OPEN_EXISTING);
}
public void Open(
DesiredAccess fDesiredAccess,
CreationDisposition fCreationDisposition) {
ShareMode fShareMode;
if (fDesiredAccess == DesiredAccess.GENERIC_READ) {
fShareMode = ShareMode.FILE_SHARE_READ;
} else {
fShareMode = ShareMode.FILE_SHARE_NONE;
}
Open(fDesiredAccess, fShareMode, fCreationDisposition, 0);
}
public void Open(
DesiredAccess fDesiredAccess,
ShareMode fShareMode,
CreationDisposition fCreationDisposition,
FlagsAndAttributes fFlagsAndAttributes) {
if (_sFileName.Length == 0)
throw new ArgumentNullException("FileName");
_hFile = CreateFile(_sFileName, fDesiredAccess, fShareMode,
IntPtr.Zero, fCreationDisposition, fFlagsAndAttributes,
IntPtr.Zero);
if (_hFile.IsInvalid) {
_hFile = null;
ThrowLastWin32Err();
}
_fDisposed = false;
}
public void Close() {
if (_hFile == null)
return;
_hFile.Close();
_hFile = null;
_fDisposed = true;
}
/* ---------------------------------------------------------
* Move file pointer
* ------------------------------------------------------ */
public void MoveFilePointer(int cbToMove) {
MoveFilePointer(cbToMove, MoveMethod.FILE_CURRENT);
}
public void MoveFilePointer(int cbToMove,
MoveMethod fMoveMethod) {
if (_hFile != null)
if (SetFilePointer(_hFile, cbToMove, IntPtr.Zero,
fMoveMethod) == INVALID_SET_FILE_POINTER)
ThrowLastWin32Err();
}
public int FilePointer {
get {
return (_hFile != null) ? (int)SetFilePointer(_hFile, 0,
IntPtr.Zero, MoveMethod.FILE_CURRENT) : 0;
}
set {
MoveFilePointer(value);
}
}
/* ---------------------------------------------------------
* Read and Write
* ------------------------------------------------------ */
public uint Read(byte[] buffer, uint cbToRead) {
// returns bytes read
uint cbThatWereRead = 0;
if (!ReadFile(_hFile, buffer, cbToRead,
ref cbThatWereRead, IntPtr.Zero))
ThrowLastWin32Err();
return cbThatWereRead;
}
public uint Write(byte[] buffer, uint cbToWrite) {
// returns bytes read
uint cbThatWereWritten = 0;
if (!WriteFile(_hFile, buffer, cbToWrite,
ref cbThatWereWritten, IntPtr.Zero))
ThrowLastWin32Err();
return cbThatWereWritten;
}
/* ---------------------------------------------------------
* IDisposable Interface
* ------------------------------------------------------ */
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool fDisposing) {
if (!_fDisposed) {
if (fDisposing) {
if (_hFile != null)
_hFile.Dispose();
_fDisposed = true;
}
}
}
~WinApiFile() {
Dispose(false);
}
/* ---------------------------------------------------------
* WINAPI STUFF
* ------------------------------------------------------ */
private void ThrowLastWin32Err() {
Marshal.ThrowExceptionForHR(
Marshal.GetHRForLastWin32Error());
}
[Flags]
public enum DesiredAccess : uint {
GENERIC_READ = 0x80000000,
GENERIC_WRITE = 0x40000000
}
[Flags]
public enum ShareMode : uint {
FILE_SHARE_NONE = 0x0,
FILE_SHARE_READ = 0x1,
FILE_SHARE_WRITE = 0x2,
FILE_SHARE_DELETE = 0x4,
}
public enum MoveMethod : uint {
FILE_BEGIN = 0,
FILE_CURRENT = 1,
FILE_END = 2
}
public enum CreationDisposition : uint {
CREATE_NEW = 1,
CREATE_ALWAYS = 2,
OPEN_EXISTING = 3,
OPEN_ALWAYS = 4,
TRUNCATE_EXSTING = 5
}
[Flags]
public enum FlagsAndAttributes : uint {
FILE_ATTRIBUTES_ARCHIVE = 0x20,
FILE_ATTRIBUTE_HIDDEN = 0x2,
FILE_ATTRIBUTE_NORMAL = 0x80,
FILE_ATTRIBUTE_OFFLINE = 0x1000,
FILE_ATTRIBUTE_READONLY = 0x1,
FILE_ATTRIBUTE_SYSTEM = 0x4,
FILE_ATTRIBUTE_TEMPORARY = 0x100,
FILE_FLAG_WRITE_THROUGH = 0x80000000,
FILE_FLAG_OVERLAPPED = 0x40000000,
FILE_FLAG_NO_BUFFERING = 0x20000000,
FILE_FLAG_RANDOM_ACCESS = 0x10000000,
FILE_FLAG_SEQUENTIAL_SCAN = 0x8000000,
FILE_FLAG_DELETE_ON = 0x4000000,
FILE_FLAG_POSIX_SEMANTICS = 0x1000000,
FILE_FLAG_OPEN_REPARSE_POINT = 0x200000,
FILE_FLAG_OPEN_NO_CALL = 0x100000
}
public const uint INVALID_HANDLE_VALUE = 0xFFFFFFFF;
public const uint INVALID_SET_FILE_POINTER = 0xFFFFFFFF;
// Use interop to call the CreateFile function.
// For more information about CreateFile,
// see the unmanaged MSDN reference library.
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern SafeFileHandle CreateFile(
string lpFileName,
DesiredAccess dwDesiredAccess,
ShareMode dwShareMode,
IntPtr lpSecurityAttributes,
CreationDisposition dwCreationDisposition,
FlagsAndAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("kernel32", SetLastError = true)]
internal static extern Int32 CloseHandle(
SafeFileHandle hObject);
[DllImport("kernel32", SetLastError = true)]
internal static extern bool ReadFile(
SafeFileHandle hFile,
Byte[] aBuffer,
UInt32 cbToRead,
ref UInt32 cbThatWereRead,
IntPtr pOverlapped);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool WriteFile(
SafeFileHandle hFile,
Byte[] aBuffer,
UInt32 cbToWrite,
ref UInt32 cbThatWereWritten,
IntPtr pOverlapped);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern UInt32 SetFilePointer(
SafeFileHandle hFile,
Int32 cbDistanceToMove,
IntPtr pDistanceToMoveHigh,
MoveMethod fMoveMethod);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetEndOfFile(
SafeFileHandle hFile);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern UInt32 GetFileSize(
SafeFileHandle hFile,
IntPtr pFileSizeHigh);
}
When you open a file for writing there are only two possible outcomes. Either you open it for appending which will allow you to write at the end only, or you open it for normal write in which case you will write from the beginning of the file. There is no "open at mid point, write then shift remaining content according to insertion length".
It is possible that some API will allow such modifications but behind the scenes, they will only allow you those two basic operations. You shouldn't worry to much about rewriting the whole file however, it is not such a bad operation unless your file is huge and your hard drive is very slow. You should worry more about the number of times you do it instead.
Open the file
Read the whole file into a variable or array
Insert modifications
Write back the file
It's probably simpler to read the file in and modify it then read it out again.
To really be efficient (i.e. not read/write the whole file for every operation) you need to do some house keeping to keep track of metadata about the file, and then do binary editing.
You'll still have to manage the case where the file grows and needs to be appended, etc.