The following is an attempt to reproduce the CreateHardLink functionality as described here.
The reason I even need to do this is because this is the only way that I know I'll have the necessary permissions (this code is running in .Net, in WinPE and has asserted the necessary privileges for restore). In particular, I'm using the BackupSemantics flag and the SE_RESTORE_NAME privilege. The normal pInvoke mechanism to CreateHardLink has no provisions for a restore program to use BackupSemantics...and there are scads of files my account doesn't have "normal" access to - hence, this mess.
unsafe bool CreateLink( string linkName, string existingFileName )
{
var access =
NativeMethods.EFileAccess.AccessSystemSecurity |
NativeMethods.EFileAccess.WriteAttributes |
NativeMethods.EFileAccess.Synchronize;
var disp = NativeMethods.ECreationDisposition.OpenExisting;
var flags =
NativeMethods.EFileAttributes.BackupSemantics |
NativeMethods.EFileAttributes.OpenReparsePoint;
var share =
FileShare.ReadWrite |
FileShare.Delete;
var handle = NativeMethods.CreateFile
(
existingFileName,
access,
( uint ) share,
IntPtr.Zero,
( uint ) disp,
( uint ) flags,
IntPtr.Zero
);
if ( !handle.IsInvalid )
{
var mem = Marshal.AllocHGlobal( 1024 );
try
{
var linkInfo = new NativeMethods.FILE_LINK_INFORMATION( );
var ioStatus = new NativeMethods.IO_STATUS_BLOCK( );
linkInfo.replaceIfExisting = false;
linkInfo.directoryHandle = IntPtr.Zero;
linkInfo.fileName = linkName;
linkInfo.fileNameLength = ( uint )
Encoding
.Unicode
.GetByteCount( linkInfo.fileName );
Marshal.StructureToPtr( linkInfo, mem, true );
var result = NativeMethods.NtSetInformationFile
(
handle.DangerousGetHandle( ),
ref ioStatus,
mem.ToPointer( ),
1024,
NativeMethods.FILE_INFORMATION_CLASS.FileLinkInformation
);
return result == 0;
}
finally
{
Marshal.FreeHGlobal( mem );
}
}
return false;
}
I keep getting a result from NtSetInformationFile that says I have specified an invalid parameter to a system function. (Result=0xC000000D). I'm unsure about how I've declared the structures - as one of 'em has a file name's length is followed by the "first character" of the name. It's documented here.
Here's how I've declared the structures - and the import. This is just best-guess stuff, as I've not found anyone who's declared this in c# (pinvoke.net and other places) I've messed with a number of permutations...all with the exact same error:
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
internal struct FILE_LINK_INFORMATION
{
[MarshalAs( UnmanagedType.Bool )]
public bool replaceIfExisting;
public IntPtr directoryHandle;
public uint fileNameLength;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst = MAX_PATH )]
public string fileName;
}
internal struct IO_STATUS_BLOCK
{
uint status;
ulong information;
}
[DllImport( "ntdll.dll", CharSet = CharSet.Unicode )]
unsafe internal static extern uint NtSetInformationFile
(
IntPtr fileHandle,
ref IO_STATUS_BLOCK IoStatusBlock,
void* infoBlock,
uint length,
FILE_INFORMATION_CLASS fileInformation
);
Any light you can shed on the dumb thing I've done would be most appreciated.
EDIT:
At the risk of drawing more downvotes, I'll explain the context, without which, might have had some believing I was looking for a hack. It's a selective backup/restore program that lives in the midst of state-management software - mostly kiosks and POS terminals and library computers. Backup and restore operations happen in a pre-boot environment (WinPE).
What ended up working, with respect to using the function was the need to change the structure FILE_LINK_INFORMATION and a twist in the file naming. First, the working FILE_LINK_INFORMATION needs to go like this:
[StructLayout( LayoutKind.Sequential, CharSet = CharSet.Unicode )]
internal struct FILE_LINK_INFORMATION
{
[MarshalAs( UnmanagedType.U1 )]
public bool ReplaceIfExists;
public IntPtr RootDirectory;
public uint FileNameLength;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst = MAX_PATH )]
public string FileName;
}
As Harry Johnston mentioned, the Pack=4 was wrong - and the marshalling of the bool needed to be a little different. The MAX_PATH is 260.
Then, when calling NtSetInformationFile in the context of a file that is opened with Read,Write, and Delete access and sharing:
unsafe bool CreateLink( DirectoryEntry linkEntry, DirectoryEntry existingEntry, SafeFileHandle existingFileHandle )
{
var statusBlock = new NativeMethods.IO_STATUS_BLOCK( );
var linkInfo = new NativeMethods.FILE_LINK_INFORMATION( );
linkInfo.ReplaceIfExists = true;
linkInfo.FileName = #"\??\" + storage.VolumeQualifiedName( streamCatalog.FullName( linkEntry ) );
linkInfo.FileNameLength = ( uint ) linkInfo.FileName.Length * 2;
var size = Marshal.SizeOf( linkInfo );
var buffer = Marshal.AllocHGlobal( size );
try
{
Marshal.StructureToPtr( linkInfo, buffer, false );
var result = NativeMethods.NtSetInformationFile
(
existingFileHandle.DangerousGetHandle( ),
statusBlock,
buffer,
( uint ) size,
NativeMethods.FILE_INFORMATION_CLASS.FileLinkInformation
);
if ( result != 0 )
{
Session.Emit( "{0:x8}: {1}\n{2}", result, linkInfo.FileName, streamCatalog.FullName( existingEntry ) );
}
return ( result == 0 );
}
finally
{
Marshal.FreeHGlobal( buffer );
}
}
Note, in particular, the namespace prefix - didn't work until I added that.
By the way, the DirectoryEntry describes a file that was supposed to be on disk as of the last backup.
With respect to not using CreateHardLink, as the original article describes, there was a vulnerability illustrated using NtSetInformationFile where there caller didn't need any particular permissions to add the link. Bummer! I suspect that when Microsoft closed the hole, they also introduced an issue with CreateHardLink. I will revisit this posting when I know more.
While I wouldn't recommend using the kernel API except as a last resort, I believe your immediate problem is that you are packing the FILE_LINK_INFO structure incorrectly.
You have specified packing of 4 bytes, which according to the documentation will put directoryHandle at an offset of 4. However, you are probably running on a 64-bit system, in which case the correct offset is 8.
I'm not certain how to fix this, but my best guess is that you need to use the default packing rules, i.e., not specify Pack at all. (Note that if you specify a packing of 8 bytes, FileName will presumably be put at offset 24 when it should be at offset 20.)
Related
I am looking for some guidance when it comes to call DeviceIoControl from C#, knowing that its generic aspect of accepting pointer parameters isn't always easy to express in C#.
Here are two examples and explanations laid out below.
Example 1:
This works but is cumbersome, you have a disposable scope but you have to pass the parameters to the function and at the end assign the output buffer value back to the variable.
var toc = new CDROM_TOC(); // non blittable
var code = NativeConstants.IOCTL_CDROM_READ_TOC;
using (var scope = new UnmanagedMemoryScope<CDROM_TOC>(toc))
{
if (!UnsafeNativeMethods.DeviceIoControl(Handle, code, IntPtr.Zero, 0, scope.Memory, scope.Size, out _))
return Array.Empty<ITrack>();
toc = scope.Value; // this is weird
}
Example 1 helper:
internal struct UnmanagedMemoryScope<T> : IDisposable where T : struct
{
private bool IsDisposed { get; set; }
public uint Size { get; }
public IntPtr Memory { get; }
public T Value
{
get => Marshal.PtrToStructure<T>(Memory);
set => Marshal.StructureToPtr(value, Memory, true);
}
public UnmanagedMemoryScope(T value)
{
var size = Marshal.SizeOf<T>();
Memory = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(value, Memory, false);
Size = (uint)size;
IsDisposed = false;
}
public void Dispose()
{
if (IsDisposed)
return;
if (Memory != default)
Marshal.FreeHGlobal(Memory);
IsDisposed = true;
}
}
Example 2:
This one is already much more friendly, wrappers do marshalling and the value passed is ref.
var toc = new CDROM_TOC(); // non blittable
var code = NativeConstants.IOCTL_CDROM_READ_TOC;
var ioctl = DeviceIoControl(Handle, code, ref toc);
// ...
Example 2 helper 1:
private static bool DeviceIoControl<TTarget>(
SafeFileHandle handle, uint code, ref TTarget target)
where TTarget : struct
{
var sizeOf = Marshal.SizeOf<TTarget>();
var intPtr = Marshal.AllocHGlobal(sizeOf);
Marshal.StructureToPtr(target, intPtr, false);
var ioctl = UnsafeNativeMethods.DeviceIoControl(
handle,
code,
IntPtr.Zero,
0u,
intPtr,
(uint)sizeOf,
out var lpBytesReturned
);
target = Marshal.PtrToStructure<TTarget>(intPtr);
Marshal.FreeHGlobal(intPtr);
return ioctl;
}
Example 2 helper 2:
private static bool DeviceIoControl<TTarget, TSource>(
SafeFileHandle handle, uint code, ref TTarget target, ref TSource source)
where TSource : struct
where TTarget : struct
{
var sizeOf1 = Marshal.SizeOf(source);
var sizeOf2 = Marshal.SizeOf(target);
var intPtr1 = Marshal.AllocHGlobal(sizeOf1);
var intPtr2 = Marshal.AllocHGlobal(sizeOf2);
Marshal.StructureToPtr(source, intPtr1, false);
Marshal.StructureToPtr(target, intPtr2, false);
var ioctl = UnsafeNativeMethods.DeviceIoControl(
handle,
code,
intPtr1,
(uint)sizeOf1,
intPtr2,
(uint)sizeOf2,
out var lpBytesReturned
);
Marshal.PtrToStructure(intPtr1, source);
Marshal.PtrToStructure(intPtr2, target);
Marshal.FreeHGlobal(intPtr1);
Marshal.FreeHGlobal(intPtr2);
return ioctl;
}
But I feel that I might be missing something and maybe there's a better approach...
Question:
What are some good tricks when it comes to call DeviceIoControl from C#?
Knowing that,
want to avoid the use of unsafe keyword
there are non-blittable types so fixed is out of question for them
the function accepts arbitrary types, it's only buffers for it in the end
Of course there's the C++/CLI route but well, it's not C# anymore...
Hope that makes sense to you, else let me know.
I usually do it like this.
Parameters structure:
ref struct CDROM_TOC
{
const int MAXIMUM_NUMBER_TRACKS = 100;
public const int sizeInBytes = 4 + MAXIMUM_NUMBER_TRACKS * 8;
readonly Span<byte> buffer;
public CDROM_TOC( Span<byte> buffer )
{
if( buffer.Length != sizeInBytes )
throw new ArgumentException();
this.buffer = buffer;
}
/// <summary>Fixed header of the structure</summary>
public struct Header
{
public ushort length;
public byte firstTrack, lastTrack;
}
/// <summary>Fixed header</summary>
public ref Header header =>
ref MemoryMarshal.Cast<byte, Header>( buffer.Slice( 0, 4 ) )[ 0 ];
public struct TRACK_DATA
{
byte reserved;
public byte controlAndAdr;
public byte trackNumber;
byte reserved2;
public uint address;
}
/// <summary>Tracks collection</summary>
public Span<TRACK_DATA> tracks =>
MemoryMarshal.Cast<byte, TRACK_DATA>( buffer.Slice( 4 ) );
// Make this structure compatible with fixed() statement
public ref byte GetPinnableReference() => ref buffer[ 0 ];
}
Usage example:
CDROM_TOC toc = new CDROM_TOC( stackalloc byte[ CDROM_TOC.sizeInBytes ] );
unsafe
{
fixed( byte* buffer = toc )
{
// Here you have unmanaged pointer for that C interop.
}
}
// If you want to return the tracks, need to copy to managed heap:
var header = toc.header;
return toc.tracks
.Slice( header.firstTrack, header.lastTrack - header.firstTrack + 1 )
.ToArray();
Couple more notes.
The answer assumes you have a modern C#, i.e. .NET 5 or newer, or any version of .NET Core.
The example does use unsafe, but only on the lowest level. If you absolutely don’t want that, use GCHandle instead. With GCHandleType.Pinned, it’s an equivalent to unsafe keyword, only slower.
Unlike your code, this method does not use any heap memory for the interop, neither managed nor native.
The instance of the structure is stack allocated, and it exposes higher-level API to access the fields of that structure. The complete stack is already fixed in memory, the fixed keyword going to nothing for that code, just return the address. Doing nothing is free performance-wise.
I tried the solution proposed in Using SetFilePointer in C# has unblanced the stack to move file's pointer and it worked. The only problem was that when reading a device rather than a file, setfilepointer places the pointer at the beginning of the sector containing the address required, instead of placing it at the required address itself. Don't know why.
But my question is another. According to the docs i've read if you don't need to use moveDistanceHighBits because only the low order bytes are needed to address your wanted offset, you have to set moveDistanteHighBits to null. But i don't know how to do this.
Could someone help please?
Don't special-case this just because the winapi function does. Unlike C compilers that were in use 25 years ago, C# supports 64-bit integers well. And it is too risky. You want to write a wrapper method anyway so you can properly throw an exception when the winapi function fails. Never skip that. Do it like this:
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
public class NativeMethods {
public static void SetFilePointer(SafeFileHandle hFile, long dist, SeekOrigin method) {
int lodist = (int)dist;
int hidist = (int)(dist >> 32);
int retval = SetFilePointer(hFile, lodist, ref hidist, method);
if (retval == -1) throw new System.ComponentModel.Win32Exception();
}
[DllImport("Kernel32.dll", SetLastError = true)]
private static extern int SetFilePointer(SafeFileHandle hFile,
int distlo, ref int disthi, SeekOrigin method);
}
}
Do note that you don't need pinvoke it at all. You want to use one of the FileStream constructors that takes an IntPtr or SafeHandle. This one for example. Now you can simply use FileStream.Seek().
You can change the PInvoke signature to use an IntPtr instead and then you can pass IntPtr.Zero when you want to pass NULL. The new signature would look like this:
[DllImport("kernel32.dll", EntryPoint="SetFilePointer", SetLastError=true)]
static extern uint SetFilePointer(
[In] Microsoft.Win32.SafeHandles.SafeFileHandle hFile,
[In] int lDistanceToMove,
[In] IntPtr lpDistanceToMoveHigh,
[In] EMoveMethod dwMoveMethod);
This works because in both definitions you're passing a pointer but using an IntPtr lets you control the actual pointer value. When you use ref, you have to have an exisiting local variable and the compiler will take its address.
If you need to pass/receive a value back (other than NULL) from the SetFilePointer call, you can do it this way:
int nValue = ...; // Value to pass for lpDistanceToMoveHigh
IntPtr pInt = IntPtr.Zero;
try
{
pInt = Marshal.AllocHGlobal ( sizeof ( int ) );
Marshal.WriteInt32 ( pInt, nValue );
// Call SetFilePointer(); upon successful return,
// you can read the value returned for lpDistanceToMoveHigh
nValue = Marshal.ReadInt32 ( pInt );
// ...
}
finally
{
if ( pInt != IntPtr.Zero )
Marshal.FreeHGlobal ( pInt );
}
At work we make our own tablets. Some of the tablets have fingerprint biometrics, some don't. Sometimes a tech forgets to plug it in. I have yet to find a way to check if that device (or any for that matter) is present.
My first approach was to use the GUID for a biometric which is {53D29EF7-377C-4D14-864B-EB3A85769359}. I would search in the registry at hklm\system\currontcontrolset\control\class and check to see if that key is present.
That doesn't work because it seems that Windows 7 has that key present even if you've never had a biometric installed. It worked in XP, but I just tried again on a unit that used to have a biometric but I took it out and that key is still present.
The hardest part about this problem is that I have to work with Windows 7, 7 embedded, xp, and xp embedded.
Next idea was to use WMI, but I couldn't find the correct class to call to check if it is present.
I then found a biometric.dll but that only works in Windows 7.
Sometimes finding a common solution to a problem is not always easy. I'm doing this project in C# but iI'm willing to convert it to any language.
Any ideas on were I should start looking?
With the help of Joshua Drake who gave me an awesome link on how to solve my problem, those are my results:
The code that I am fixing to post is kind of specialized in that it looks for a specific GUID and only looks for the first one. I adapted it from the article about how to disable a device, although this code does not disable anything it merely checks for presence.
public static bool IsDevicePresent(string guid)
{
var info = IntPtr.Zero;
var NullGuid = new Guid(guid);
try
{
info = SetupDiGetClassDevsW(ref NullGuid,null,IntPtr.Zero,DIGCF_PRESENT);
CheckError("SetupDiGetClassDevs");
var devdata = new SP_DEVINFO_DATA();
devdata.cbSize = (UInt32)Marshal.SizeOf(devdata);
// Get first device matching device criterion.
SetupDiEnumDeviceInfo(info,0,out devdata);
// if no items match filter, throw
if (Marshal.GetLastWin32Error() == ERROR_NO_MORE_ITEMS)
CheckError("No device found matching filter.", 0xcffff);
CheckError("SetupDiEnumDeviceInfo");
}
catch
{
return false;
}
finally
{
if (info != IntPtr.Zero)
SetupDiDestroyDeviceInfoList(info);
}
return true;
}
private static void CheckError(string message, int lasterror = -1)
{
int code = lasterror == -1 ? Marshal.GetLastWin32Error() : lasterror;
if (code != 0)
throw new ApplicationException(String.Format("Error disabling hardware device (Code {0}): {1}",code, message));
}
[DllImport("setupapi.dll", SetLastError = true)]
private static extern IntPtr SetupDiGetClassDevsW([In] ref Guid ClassGuid,[MarshalAs(UnmanagedType.LPWStr)]string Enumerator,IntPtr parent,UInt32 flags);
[DllImport("setupapi.dll", SetLastError = true)]
private static extern bool SetupDiDestroyDeviceInfoList(IntPtr handle);
[DllImport("setupapi.dll", SetLastError = true)]
private static extern bool SetupDiEnumDeviceInfo(IntPtr deviceInfoSet,UInt32 memberIndex,[Out] out SP_DEVINFO_DATA deviceInfoData);
//used to find device info from device manager
[StructLayout(LayoutKind.Sequential)]
private struct SP_DEVINFO_DATA
{
public UInt32 cbSize;
public Guid classGuid;
public UInt32 devInst;
public IntPtr reserved;
}
private const uint DIGCF_PRESENT = 2;
private const uint ERROR_INVALID_DATA = 13;
private const uint ERROR_NO_MORE_ITEMS = 259;
private const uint ERROR_ELEMENT_NOT_FOUND = 1168;
And here is a simple unit test to prove it works for first device
[Test]
public void TestDevicePresent()
{
var bluetoothClassGuid = "e0cbf06c-cd8b-4647-bb8a-263b43f0f974";
var biometricClassGuid = "53D29EF7-377C-4D14-864B-EB3A85769359";
var cdromdrivClassGiud = "4d36e965-e325-11ce-bfc1-08002be10318";
Assert.False(Native.IsDevicePresent(bluetoothClassGuid));
Assert.False(Native.IsDevicePresent(biometricClassGuid));
Assert.True(Native.IsDevicePresent(cdromdrivClassGiud));
}
here is my code :
public static string ReadListViewItem(IntPtr lstview, int item)
{
const int dwBufferSize = 1024;
int dwProcessID;
LV_ITEM lvItem;
string retval;
bool bSuccess;
IntPtr hProcess = IntPtr.Zero;
IntPtr lpRemoteBuffer = IntPtr.Zero;
IntPtr lpLocalBuffer = IntPtr.Zero;
IntPtr threadId = IntPtr.Zero;
try
{
lvItem = new LV_ITEM();
lpLocalBuffer = Marshal.AllocHGlobal(dwBufferSize);
// Get the process id owning the window
threadId = GetWindowThreadProcessId(lstview, out dwProcessID);
if ((threadId == IntPtr.Zero) || (dwProcessID == 0))
throw new ArgumentException("hWnd");
// Open the process with all access
hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, dwProcessID);
if (hProcess == IntPtr.Zero)
throw new ApplicationException("Failed to access process");
// Allocate a buffer in the remote process
lpRemoteBuffer = VirtualAllocEx(hProcess, IntPtr.Zero, dwBufferSize, MEM_COMMIT,
PAGE_READWRITE);
if (lpRemoteBuffer == IntPtr.Zero)
throw new SystemException("Failed to allocate memory in remote process");
// Fill in the LVITEM struct, this is in your own process
// Set the pszText member to somewhere in the remote buffer,
// For the example I used the address imediately following the LVITEM stuct
lvItem.mask = LVIF_TEXT;
lvItem.iItem = item;
lvItem.iSubItem = 2;
lvItem.pszText = (IntPtr)(lpRemoteBuffer.ToInt32() + Marshal.SizeOf(typeof(LV_ITEM)));
lvItem.cchTextMax = 50;
// Copy the local LVITEM to the remote buffer
bSuccess = WriteProcessMemory(hProcess, lpRemoteBuffer, ref lvItem,
Marshal.SizeOf(typeof(LV_ITEM)), IntPtr.Zero);
if (!bSuccess)
throw new SystemException("Failed to write to process memory");
// Send the message to the remote window with the address of the remote buffer
SendMessage(lstview, LVM_GETITEMText, 0, lpRemoteBuffer);
// Read the struct back from the remote process into local buffer
bSuccess = ReadProcessMemory(hProcess, lpRemoteBuffer, lpLocalBuffer, dwBufferSize,IntPtr.Zero);
if (!bSuccess)
throw new SystemException("Failed to read from process memory");
// At this point the lpLocalBuffer contains the returned LV_ITEM structure
// the next line extracts the text from the buffer into a managed string
retval = Marshal.PtrToStringAnsi((IntPtr)(lpLocalBuffer +
Marshal.SizeOf(typeof(LV_ITEM))));
}
finally
{
if (lpLocalBuffer != IntPtr.Zero)
Marshal.FreeHGlobal(lpLocalBuffer);
if (lpRemoteBuffer != IntPtr.Zero)
VirtualFreeEx(hProcess, lpRemoteBuffer, 0, MEM_RELEASE);
if (hProcess != IntPtr.Zero)
CloseHandle(hProcess);
}
return retval;
}
no matter what i do retval returns empty, although lpLocalBuffer doesnt .
here is the def of ListItem :
[StructLayout(LayoutKind.Sequential)]
private struct LV_ITEM
{
public int mask;
public int iItem;
public int iSubItem;
public int state;
public int stateMask;
public IntPtr pszText;
public int cchTextMax;
public int iImage;
internal int lParam;
internal int iIndent;
}
i tried compiling for 86x , 64bit, any cpu , nothing seems to work at all !
any idea why this might be happening ?
C# + .net4 , windows 7 64bit.
Here's a different approach to doing this - use UI Automation. It does the cross-process, cross-bitness work for you, and will work against listviews, listboxes, or pretty much any other standard Windows UI. Here's a sample app that will get the HWND from the listview under the mouse pointer, and dump the items in it. It dumps just the name of each item; with Listviews, I think you can recurse into the fields in each item if you want.
// Compile using: csc ReadListView.cs /r:UIAutomationClient.dll
using System;
using System.Windows.Automation;
using System.Runtime.InteropServices;
class ReadListView
{
public static void Main()
{
Console.WriteLine("Place pointer over listview and hit return...");
Console.ReadLine();
// Get cursor position, then the window handle at that point...
POINT pt;
GetCursorPos(out pt);
IntPtr hwnd = WindowFromPoint(pt);
// Get the AutomationElement that represents the window handle...
AutomationElement el = AutomationElement.FromHandle(hwnd);
// Walk the automation element tree using content view, so we only see
// list items, not scrollbars and headers. (Use ControlViewWalker if you
// want to traverse those also.)
TreeWalker walker = TreeWalker.ContentViewWalker;
int i = 0;
for( AutomationElement child = walker.GetFirstChild(el) ;
child != null;
child = walker.GetNextSibling(child) )
{
// Print out the type of the item and its name
Console.WriteLine("item {0} is a \"{1}\" with name \"{2}\"", i++, child.Current.LocalizedControlType, child.Current.Name);
}
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
};
[DllImport("user32.dll")]
private static extern IntPtr WindowFromPoint(POINT pt);
[DllImport("user32.dll")]
private static extern int GetCursorPos(out POINT pt);
}
I know this is old, but I found it while trying to solve my problem and hopefully this will help someone else.
I used the recommendation in this question, that was in C++, and slightly modified the LV_ITEM structure to make it work with 64bit in VB.NET (I haven't tested in C# but I imagine the solution is quite similar.)
Public Structure LV_ITEM64
Public mask As Integer
Public iItem As Integer
Public iSubItem As Integer
Public state As Integer
Public stateMask As Integer
Public placeholder1 As Integer
Public pszText As Integer
Public placeholder2 As Integer
Public cchTextMax As Integer
Public iImage As Integer
End Structure
Then, when declaring the instance of the structure, I used the following code to choose between 64 bit and 32 bit structures:
Dim lvi As Object
If IntPtr.Size = 4 Then
lvi = New LV_ITEM
Else
lvi = New LV_ITEM64
End If
You have clarified that you are trying to read items from a list view control in a 32 bit process into a different 64 bit process.
I have seen many questions on this topic in various forums and not one ever seemed to achieve a successful outcome.
I think your best option is to create a 32 bit executable which will be able to read out of the other program's list view.
There is at least one obstacle to overcome if your program is 32-bit and the target program is 64-bit. Or the other way around. The LVITEM declaration will be wrong, IntPtr has the wrong number of bits. Which makes Marshal.SizeOf() return the wrong value. Alignment is okay, I think, by accident. Changing the field to either int or long can fix the problem, depending on the bitness of the target program. Which you can find out by looking at the Taskmgr.exe, Processes tab. The process name is post-fixed with "*32" if it is a 32-bit process. Or simply stay out of trouble by setting your project's Target platform setting to match the target process (x86 or AnyCPU).
Debug this by using Debug + Windows + Memory + Memory1. Put "lpLocalBuffer" in the Address box and observe what you see vs what your code reads. You should definitely be able to tell from the hex view that you got the string properly. Note that if you see zeros between the string characters then the target process uses the Unicode version of the list view. Marshal.PtrToStringUnicode is then required to read it.
Sorry my response is so late but I just came across the same issue. Here is the structure I used for VB.NET which works on both 32 and 64 bit systems.
<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Structure LV_ITEM
Public Mask As UInteger
Public Index As Integer
Public SubIndex As Integer
Public State As Integer
Public StateMask As IntPtr
Public Text As String
Public TextLength As Integer
Public ImageIndex As Integer
Public LParam As IntPtr
End Structure
I need to be able to move an entire directory in a single atomic operation, guaranteeing that nothing else on the system will be able to subvert the operation by creating new files after I start, having a lock on a file, etc.
Presumably, I would use System.IO.Directory.Move() if the directories were on the same volume (if Directory.GetDirectoryRoot() is the same), otherwise I'd have to create a new target directory on the other volume and recursively copy/move all the directories and files underneath.
Nothing I've read shows how to gain an exclusive lock to an entire directory leaf in .NET so this can be done safely. Is there a recommended/supported way to do this?
Vista does support transactions in NTFS volumes:
http://msdn.microsoft.com/en-us/magazine/cc163388.aspx
Could you work around this by renaming the "root" directory temporarily (creating a directory with the same name immediately thereafter so that anyone accessing that directory doesn't encounter an error), then work on the files in the renamed directory?
I remember being able to do this at the DOS level by simply renaming the directory. There was a move command, which also seemed to work. But it makes sense. You're not really moving all of the files in the directory, you're just changing the meta data in the directory structure itself. I also remember this from hacking directory structures directly using a disk editor on my fathers Zenith Data Systems 8088. I could make directories invisible by changing the attribute bits on disk, even hiding ".." and "." and making subdirectories appear to be root (the parent directories were invisible). Hope this works for you. I haven't revisited this in hmmm too many years to count ;-). May it work for you.
By the way, you should not have to lock anything because if you're just renaming, it happens really fast, and it's just a single operation.
You can use the Transactional NTFS via PInvoke. Note that it's unclear if it works properly across different volumes, please see the documentation. You may need to use Distributed Transactions, which is significantly more complicated. It will only work on NTFS volumes, not FAT.
Caveat: this code is entirely untested.
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
bool GetVolumeInformationW(
[In, MarshalAs(UnmanagedType.LPWStr)]
string lpRootPathName,
IntPtr lpVolumeNameBuffer,
int nVolumeNameSize,
out int lpVolumeSerialNumber,
out int lpMaximumComponentLength,
out int lpFileSystemFlags,
IntPtr lpFileSystemNameBuffer,
int nFileSystemNameSize
);
[DllImport("KtmW32.dll", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
private static extern SafeFileHandle CreateTransaction(
IntPtr lpTransactionAttributes,
IntPtr UOW,
int CreateOptions,
int IsolationLevel,
int IsolationFlags,
int Timeout,
[In, MarshalAs(UnmanagedType.LPWStr)]
string Description
);
[DllImport("KtmW32.dll", SetLastError = true, BestFitMapping = false)]
private static extern bool CommitTransaction(SafeFileHandle hTransaction);
public enum ProgressResponse
{
PROGRESS_CONTINUE, // Continue the copy operation.
PROGRESS_CANCEL, // Cancel the copy operation and delete the destination file.
PROGRESS_STOP, // Stop the copy operation. It can be restarted at a later time.
PROGRESS_QUIET, // Continue the copy operation, but stop invoking CopyProgressRoutine to report progress.
}
public delegate ProgressResponse ProgressRoutine(
long TotalFileSize,
long TotalBytesTransferred,
long StreamSize,
long StreamBytesTransferred,
int dwStreamNumber,
int dwCallbackReason,
IntPtr hSourceFile,
IntPtr hDestinationFile,
IntPtr lpData
);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
private static extern bool MoveFileTransactedW(
[In, MarshalAs(UnmanagedType.LPWStr)]
string lpExistingFileName,
[In, MarshalAs(UnmanagedType.LPWStr)]
string lpNewFileName,
ProgressRoutine lpProgressRoutine,
IntPtr lpData,
int dwFlags,
SafeFileHandle hTransaction
);
private static bool CheckSupportsTransactions(string filePath)
{
const int FILE_SUPPORTS_TRANSACTIONS = 0x00200000;
if(!GetVolumeInformationW(
Path.GetPathRoot(sourceFullPath),
IntPtr.Zero, 0,
out var _,
out var _,
out var flags,
IntPtr.Zero, 0)
throw new Win32Exception(Marshal.GetLastWin32Error());
return flags & FILE_SUPPORTS_TRANSACTIONS != 0;
}
public static void MoveDirectoryTransacted(string sourceFullPath, string destFullPath, ProgressRoutine progress = null)
{
const int MOVEFILE_COPY_ALLOWED = 0x2;
const int ERROR_REQUEST_ABORTED = 0x4D3;
sourceFullPath = Path.GetFullPath(sourceFullPath);
destFullPath = Path.GetFullPath(destFullPath);
if(!CheckSupportsTransactions(sourceFullPath) ||
!CheckSupportsTransactions(destFullPath))
{
throw new InvalidOperationException("Volume does not support transactions");
}
using (var tran = CreateTransaction(IntPtr.Zero, IntPtr.Zero, 0, 0, 0, 0, null))
{
if (tran.IsInvalid)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
if (!MoveFileTransactedW(
sourceFullPath,
destFullPath,
progress,
IntPtr.Zero,
MOVEFILE_COPY_ALLOWED,
tran))
{
var error = Marshal.GetLastWin32Error();
if (error == ERROR_REQUEST_ABORTED)
throw new OperationCanceledException();
throw new Win32Exception(error);
}
if (!CommitTransaction(tran))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
}
If you have a CancellationToken, you could invoke it like this
MoveDirectoryTransacted("sourcePath", "destPath",
() => cancelToken.IsCancellationRequested ? ProgressResponse.PROGRESS_CANCEL : ProgressResponse.PROGRESS_CONTINUE);
If you have the ability to run your copy process as "service account" that is only used by the copy process, you could set the permissions of the folder to only allow that account to work with it. Then reset the permissions back to what they were after the copy process finished.
For example, something like the following:
using System;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;
namespace ExclusiveLockFileCopy
{
public class ExclusiveLockMover
{
public DirectorySecurity LockFolder(DirectoryInfo di)
{
var originalSecurity = di.GetAccessControl(System.Security.AccessControl.AccessControlSections.All);
//make sure inherted permissions will come back when UnlockFolder is called
originalSecurity.SetAccessRuleProtection(true, true);
var tmpSecurity = di.GetAccessControl(System.Security.AccessControl.AccessControlSections.All);
// remove all rules
var currentRules = tmpSecurity.GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount));
foreach (AccessRule rule in currentRules)
{
tmpSecurity.PurgeAccessRules(rule.IdentityReference);
tmpSecurity.ModifyAccessRule(AccessControlModification.RemoveAll, rule, out var tmpModified);
Console.WriteLine($"Removed access for {rule.IdentityReference.Value}");
}
//add back the current process' identity after the for loop - don't assume the account will show up in the current rule list (i.e. inherited access)
var _me = WindowsIdentity.GetCurrent();
var _meNT = new NTAccount(_me.Name);
tmpSecurity.AddAccessRule(new FileSystemAccessRule(_meNT, FileSystemRights.FullControl, AccessControlType.Allow));
Console.WriteLine($"Ensuring {_meNT.Value} maintains full access");
//strip out inherited permissions
tmpSecurity.SetAccessRuleProtection(true, false);
di.SetAccessControl(tmpSecurity);
//send back the original security incase it is needed later for "unlocking"
return originalSecurity;
}
public void UnlockFolder(DirectoryInfo di, DirectorySecurity originalSecurity)
=> di.SetAccessControl(originalSecurity);
public void CopyFolderExclusive(string srcFolder, string dstFolder)
{
DirectorySecurity diSourceOriginalSecurity = null;
DirectorySecurity diDestinationOriginalSecurity = null;
var diSource = new DirectoryInfo(srcFolder);
var diDestination = new DirectoryInfo(dstFolder);
try
{
diSourceOriginalSecurity = LockFolder(diSource);
if (!diDestination.Exists)
diDestination.Create();
diDestinationOriginalSecurity = LockFolder(diDestination);
// perform your folder/file copy here //
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
if (diSourceOriginalSecurity != null)
UnlockFolder(diSource, diSourceOriginalSecurity);
if (diDestinationOriginalSecurity != null)
UnlockFolder(diDestination, diDestinationOriginalSecurity);
}
}
}
}
I'd say what you really need is a transactional file system... which NTFS ain't, and while there have been MS plans for such, it was cut from Longhorn before it became Vista (and from Cairo before that).
You could try to gain exclusive locks on every file in the directory before the move, and do the moving with explicit file reading/writing, but recursively? I'm not so sure that's a good idea... and besides, that won't protect against new files being added.
What are you really trying to do? Why are you worried about concurrent activity?