Enumerating the shell with Next > 1 - c#

All sample codes anywhere on the net just don't question the habit of passing 1 as the first argument to an enumeration interface Next() call. Yet the documentation clearly promises that more than one items can be obtained in one call. As it can be seen in this code fragment, doing so would tremendously speed up the process of counting the files in a folder (actually, the fundamentally similar WPD interface works that way).
string FolderPath = #"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\<Phone-USB-ID>\Internal storage\Pictures\Test";
SHCreateItemFromParsingName(FolderPath, IntPtr.Zero, typeof(IShellItem).GUID, out IShellItem item);
item.BindToHandler(IntPtr.Zero, BHID_SFObject, typeof(IShellFolder).GUID, out IShellFolder folder);
folder.EnumObjects(IntPtr.Zero, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, out IEnumIDList list);
uint count = 0;
try {
do {
//var ObjectIDs = new ObjectIDLargeArray();
//var pidl = Marshal.AllocHGlobal(Marshal.SizeOf(ObjectIDs));
//Marshal.StructureToPtr(ObjectIDs, pidl, true);
//var pidl = Marshal.AllocCoTaskMem(100 * IntPtr.Size);
int hr = list.Next(100, out var pidl, out uint fetched); // <<<<<
if (hr == 0)
count += fetched;
if (fetched == 0)
break;
//Marshal.FreeHGlobal(pidl);
Marshal.FreeCoTaskMem(pidl);
}
while (true);
}
catch (Exception e) {
Console.WriteLine(e);
}
[StructLayout(LayoutKind.Sequential)]
internal class ObjectIDLargeArray {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
public IntPtr[] IDs;
}
The documentation and the usage on the web is not entirely clear about whether the caller has to allocate the buffer for the IDs returned but as can be seen from the code, I tried all approaches, both a specific array and an allocated block of memory. For very small values like 2 and 3, the shell might simply return garbage in fetched. For anything larger, an access violation exception.
Just to put it into a perspective of why it's important: with a newly connected phone, no previous caching, counting the files in a folder of 500 pictures takes about 15 (!) seconds. The second time with data already cached, 3 to 4. Needless to say, even the second is unacceptable for a mere file count but 15 seconds are absolutely stupid. With the otherwise mediocre speeds of WPD but with a 100-step count, a few hundred ms at most.

Solved courtesy of the new C# 7.2 feature, in parameters:
internal interface IEnumIDList {
[PreserveSig]
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
HResult Next(uint celt, in IntPtr rgelt, out uint pceltFetched);
}
That makes it play all right with both the preallocated array and the single value case. It's not really that much faster, unfortunately...

Related

PInvoke Nikon c++ DLL Function from c#

I'm trying to access the Nikon image SDK(for those interested see: 1) to implement access to *.nef file in a programm. I'm stuck at a return code from the dll which should be interpreted as "invalid parameter" and I'm running out of ideas.
Yes I know the chance, that somebody is exactly using this dll is sparse, but I'm rather looking for "writing"/"thinking" errors... I'm still learning (so excuse to any wrong used terms, etc...) and also for this reason this is a little "longer" post (some "aloud thinking" on my side ;-) )
1.) the dll has an entry function where you pass a identifier and a struct as parameter. The identifier stands for a specific command (like open,close,etc....). The struct is used for data exchange with the camera.
2.) I do have everything together and working (since, I'm getting a "return code") but I can't figure out the reason for the return code (maybe some datatype is incompatible?)
So first the "C++"-part:
c++ function definition:
extern "C" unsigned long __declspec(dllexport) WINAPI Nkfl_Entry(unsigned long ulCommand, void* pParam );
this is stdcall, so I do need to worry about any further options to dllimport, since usigned long(c++) corresponds to uint(c#) i get two uints one "out" and one "in"...
c++ struct defintion:
typedef struct tagNkflLibraryParam
{
unsigned long ulSize; // Size of structure
unsigned long ulVersion; // Version
unsigned long ulVMMemorySize; // Size of vertual memory
NkflPtr* pNkflPtr; // Pointer of StratoObject
unsigned char VMFileInfo[ MAX_PATH ]; // Swap file info
} NkflLibraryParam, *NkflLibraryPtr;
so I do need to pass 3 times uints, one pointer to an "StratoObject" ((1.) the doc says "typedef void* NkflPtr" so this is "just" a void* pointer 2.) the doc says if this is zero it will be filled up by the sdk) and finally one byte (since unsigned char(c++) corresponds to byte(c#)).
So first question: Is this correct?
Then going to the "coding-part":
c# struct defintion:
namespace NikonStruct
{
[StructLayout(LayoutKind.Sequential)]
public struct NkflLibraryParam
{
public uint ulSize; // size of the NkflLibraryParam structure
public uint ulVersion; // version number of the interface specification
public uint ulVMMMemorySize; // upper limit of the physical memory that can be used
public IntPtr pNkflPtr; // pointer to the StratoManager object
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 260)]
public byte[] VMFileInfo; // swap file information
}
}
now this should correspond to my defintions above...
c# Program class:
class Program
{
public enum eNkflCommand : int
{
kNkfl_Cmd_OpenLibrary = 1,
kNkfl_Cmd_CloseLibrary = 2,
};
[DllImport("NkImgSDK.dll", EntryPoint = "Nkfl_Entry")]
public static extern uint kNkfl_Cmd_OpenLibrary(eNkflCommand ulCommand, ref NikonStruct.NkflLibraryParam data);
[DllImport("NkImgSDK.dll", EntryPoint = "Nkfl_Entry")]
public static extern uint kNkfl_Cmd_CloseLibrary(eNkflCommand ulCommand, IntPtr close);
static void Main(string[] args)
{
try
{
// specify return value of entry function
uint result1, result2;
/// call the kNkfl_Cmd_OpenLibrary Function
// generate data structure, which is used to communicate with kNkfl_Cmd_OpenLibrary function
NikonStruct.NkflLibraryParam _NkflLibraryParam = new NikonStruct.NkflLibraryParam();
// fill the fields of _NkflLibraryParam structure for kNkfl_Cmd_OpenLibrary function
_NkflLibraryParam.ulVersion = 16777216;
_NkflLibraryParam.ulSize = ((uint)Marshal.SizeOf(_NkflLibraryParam)); ;
// call the entry function with parameters for kNkfl_Cmd_OpenLibrary
result1 = kNkfl_Cmd_OpenLibrary(eNkflCommand.kNkfl_Cmd_OpenLibrary, ref _NkflLibraryParam);
Console.WriteLine(result1);
/// call the kNkfl_Cmd_CloseLibrary Function
result2 = kNkfl_Cmd_CloseLibrary(eNkflCommand.kNkfl_Cmd_CloseLibrary, IntPtr.Zero);
Console.WriteLine(result2);
}
catch
{
string errorMsg = new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()).Message;
throw new ArgumentException(errorMsg);
}
}
}
So nothing specific here:
eNkflCommand is from the doc
the structure is passed by reference so ref...
the "close" function expects "null pointer" (according to doc)
ulVersion is 0x01000000 (according to doc)
all other struct values are not set (and are zero by default if I understood the clr doc correctly)
Compiles and runs as already mentioned but result1 returns wrong "status-code" which translates to "invalid param" as already mentioned.
Any help appreciated....
FOUND IT:
never trust a documentation of a software developer: there was actually a missing parameter (not declared in the documentation BUT in an additional header definition file which was in another sub-directory of the sdk-package...)
so actually the struct defintion in the c# should be:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct struktur
{
public uint ulSize; // size of the NkflLibraryParam structure
public uint ulVersion; // version number of the interface specification
public uint ulVMMMemorySize; // upper limit of the physical memory that can be used
public IntPtr pNkflPtr; // pointer to the StratoManager object
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 260)]
public byte[] VMFileInfo; // swap file information
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 260)]
public byte[] DefProfPath; // <- this one is not included in the doc of NIKON (I still don't now what this should hold but it works if it's empty...)
}
Thanks to jszigeti and DavidHeffernan for trying...

Problems with "SetupScanFileQueue" callback from SetupAPI C#

I've implemented a SetupAPI wrapper in C# that enumerates devices based on GUIDs. I have, more or less, converted the DevCon c++ example code found on MSDN to C# (Yes I very much know that this is all veyr painfully to do in .NET, but it's fun and a challenge).
I'm able to get all the appropriate information about a certain device, but a problem occurred when I reached the "SetupScanFileQueue" method.
I cant seem to be able to make the "SetupScanFileQueue" to call my callback. The method returns true, so it seems to be working.
My end goal is to get the driver files of the specific device.
Additional information:
The files appear to be added to the FileQueue correctly, I get this popup window that seems to copy the correct files.
// create a file queue so we can look at this queue later
var queueHandler = SetupOpenFileQueue();
if (queueHandler == IntPtr.Zero)
return false;
// modify flags to indicate we're providing our own queue
var deviceInstallParams = new SP_DEVINSTALL_PARAMS();
deviceInstallParams.cbSize = Marshal.SizeOf(deviceInstallParams);
if (!SetupDiGetDeviceInstallParams(handle, ref devInfo, ref deviceInstallParams))
{
error = Marshal.GetLastWin32Error();
return false;
}
// we want to add the files to the file queue, not install them!
deviceInstallParams.FileQueue = queueHandler;
deviceInstallParams.Flags |= DI_NOVCP;
if (!SetupDiGetDeviceInstallParams(handle, ref devInfo, ref deviceInstallParams))
{
error = Marshal.GetLastWin32Error();
return false;
}
// now fill queue with files that are to be installed
// this involves all class/co-installers
if (!SetupDiCallClassInstaller(DIF_INSTALLDEVICEFILES, handle, ref devInfo))
{
error = Marshal.GetLastWin32Error();
return false;
}
// we now have a list of delete/rename/copy files
// iterate the copy queue twice - 1st time to get # of files
// 2nd time to get files
// (WinXP has API to get # of files, but we want this to work
// on Win2k too)
var scanResult = 0;
var count = 0;
var callback = new PSP_FILE_CALLBACK(PSP_FILEFOUND_CALLBACK);
var t = SetupScanFileQueue(queueHandler, SPQ_SCAN_USE_CALLBACK, IntPtr.Zero,
callback, ref count, ref scanResult);
SetupDiDestroyDriverInfoList(handle, ref devInfo, SPDIT_CLASSDRIVER);
if (queueHandler != IntPtr.Zero)
SetupCloseFileQueue(queueHandler);
The definition of my Callback is as follows:
public delegate uint PSP_FILE_CALLBACK(uint context, uint notifaction, IntPtr param1, IntPtr param2);
public static uint PSP_FILEFOUND_CALLBACK(uint context, uint notifaction, IntPtr param1, IntPtr param2)
{
//This callback is never triggered
return 0;
}
Does anyone have any suggestions to what I'm doing wrong in the "SetupScanFileQueue" function, and why the callback is never called?
Any help is very much appreciated!
Edit:
I should also have added the DllImport for the SetupScanFileQueue function:
[DllImport("setupapi.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
private static extern uint SetupScanFileQueue(IntPtr QueueHandle,
int Flags,
IntPtr Window,
PSP_FILE_CALLBACK CallbackRoutine,
int CallbackContext,
out int ScanResult
);
I've also tried it without the CallingConvention.

How to get all memory address space used by a process?

I need to know all memory address space used by a process. The memory space will later be scanned to locate values within the process and identify their locations / addresses. My current process for this is to take each module's base address through its (base address + memory size).
I'm testing this on a process with a known value at a known address. When I look up that specific address, I get the value I expect. However, when I scan (what I believe to be) all address space used by the process, I can't find the value anywhere.
I know that a numeric value "4143000" exists at 0x0CF8DC38 and 0x0CF8DDDC. When I call ReadMemoryBytes(module, module.BaseAddress, 4, (IntPtr)(0x0CF8DC38)) I get back bytes (152, 55, 63, 0). When I call BitConverter.GetBytes(4143000) I get back the same set of bytes. When I use a different memory scanner on that process, I find that value at those addresses.
However, when I scan the "known addresses", I don't find this value anywhere. It doesn't look like my code is even finding those addresses in use by the process.
Thusly, my question is twofold:
How can I find these addresses within this process?
I'm concerned I may be dealing with absolute addresses in system memory versus relative addresses within a process. Am I doing this right?
.
// (in the calling method)
foreach (ProcessModule module in process.Modules) {
ParameterizedThreadStart pst = new ParameterizedThreadStart(p => SearchModule(module, value));
Thread t = new Thread(pst);
t.Start(); }
private unsafe void SearchModule(ProcessModule module, string value)
{
Process process = getProcess;
int iVal;
double dVal;
int.TryParse(value, out iVal);
double.TryParse(value, out dVal);
for (Int64 addr = (Int64)module.BaseAddress; addr + value.Length < (Int64)module.BaseAddress + module.ModuleMemorySize; addr++)
{
// Compare ints
if (iVal > 0)
{
byte[] ExpectedBytes = BitConverter.GetBytes(iVal);
byte[] ActualBytes = ReadMemoryBytes(module, (IntPtr)addr, (uint)ExpectedBytes.Length, (IntPtr)addr);
bool isMatch = true;
for (int i = 0; i < ExpectedBytes.Length; i++)
if (ExpectedBytes[i] != ActualBytes[i])
isMatch = false;
if (isMatch)
PossibleAddresses.Add((IntPtr)addr);
}
}
private byte[] ReadMemoryBytes(ProcessModule mod, IntPtr memAddress, uint size, IntPtr BaseAddress)
{
byte[] buffer = new byte[size];
IntPtr bytesRead;
unsafe
{
ReadProcessMemory(processPointer, BaseAddress, buffer, size, out bytesRead);
return buffer;
}
}
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(UInt32 dwDesiredAccess, Int32 bInheritHandle, UInt32 dwProcessId);
[DllImport("kernel32.dll")]
public static extern Int32 CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll")]
public static extern Int32 ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [In, Out] byte[] buffer, UInt32 size, out IntPtr lpNumberOfBytesRead);
The addresses you're getting are pointers to the managed (CLR) heap. They won't generally map to absolute memory addresses and they can move from call to call as the GC decides to run.
If you use "unsafe" code, you can get relative pointers as well as managing your own memory space. It's still on the heap but at least you're guaranteed the GC won't modify your address space.
Do not expect to be able to access things on the heap from non-CLR code without extensive wrapping. There are ways to do IPC between CLR-managed processes but you'd have to write access proxies to the "outside world" if you want a non-CLR process to get to your memory.

Getting Text from SysListView32 in 64bit

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

How do I delete a file which is locked by another process in C#?

I'm looking for a way to delete a file which is locked by another process using C#. I suspect the method must be able to find which process is locking the file (perhaps by tracking the handles, although I'm not sure how to do this in C#) then close that process before being able to complete the file delete using File.Delete().
Killing other processes is not a healthy thing to do. If your scenario involves something like uninstallation, you could use the MoveFileEx API function to mark the file for deletion upon next reboot.
If it appears that you really need to delete a file in use by another process, I'd recommend re-considering the actual problem before considering any solutions.
The typical method is as follows. You've said you want to do this in C# so here goes...
If you don't know which process has the file locked, you'll need to examine each process's handle list, and query each handle to determine if it identifies the locked file. Doing this in C# will likely require P/Invoke or an intermediary C++/CLI to call the native APIs you'll need.
Once you've figured out which process(es) have the file locked, you'll need to safely inject a small native DLL into the process (you can also inject a managed DLL, but this is messier, as you then have to start or attach to the .NET runtime).
That bootstrap DLL then closes the handle using CloseHandle, etc.
Essentially: the way to unlock a "locked" file is to inject a DLL file into the offending process's address space and close it yourself. You can do this using native or managed code. No matter what, you're going to need a small amount of native code or at least P/Invoke into the same.
Helpful links:
Three Ways to Inject Your Code into Another Process
.NET Code Injection
Good luck!
If you want to do it programmatically. I'm not sure... and I'd really recommend against it.
If you're just troubleshooting stuff on your own machine, SysInternals Process Explorer can help you
Run it, use the Find Handle command (I think it's either in the find or handle menu), and search for the name of your file. Once the handle(s) is found, you can forcibly close them.
You can then delete the file and so on.
Beware, doing this may cause the program which owns the handles to behave strangely, as you've just pulled the proverbial rug out from under it, but it works well when you are debugging your own errant code, or when visual studio/windows explorer is being crapped and not releasing file handles even though you told them to close the file ages ago... sigh :-)
You can use this program, Handle, to find which process has the lock on your file. It's a command-line tool, so I guess you use the output from that. I'm not sure about finding it programmatically.
If deleting the file can wait, you could specify it for deletion when your computer next starts up:
Start REGEDT32 (W2K) or REGEDIT (WXP) and navigate to:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager
W2K and WXP
W2K:EditAdd Value...Data Type: REG_MULTI_SZValue Name: PendingFileRenameOperationsOK
WXP:EditNewMulti-String Valueenter
PendingFileRenameOperations
In the Data area, enter "\??\" + filename to be deleted. LFNs may
be entered without being embedded in quotes. To delete C:\Long Directory Name\Long File Name.exe, enter the following data:
\??\C:\Long Directory Name\Long File Name.exe
Then press OK.
The "destination file name" is a null (zero) string. It is entered
as follows:
W2K:EditBinaryselect Data Format: Hexclick at the end of the hex stringenter 0000 (four zeros)OK
WXP:Right-click the valuechoose "Modify Binary Data"click at the end of the hex stringenter 0000 (four zeros)OK
Close REGEDT32/REGEDIT and reboot to delete the file.
(Shamelessly stolen from some random forum, for posterity's sake.)
Using Orion Edwards advice I downloaded the Sysinternals Process Explorer which in turn allowed me to discover that the file I was having difficulties deleting was in fact being held not by the Excel.Applications object I thought, but rather the fact that my C# code send mail code had created an Attachment object that left a handle to this file open.
Once I saw this, I quite simple called on the dispose method of the Attachment object, and the handle was released.
The Sysinternals explorer allowed me to discover this used in conjunction with the Visual Studio 2005 debugger.
I highly recommend this tool!
Oh, one big hack I employed years ago, is that Windows won't let you delete files, but it does let you move them.
Pseudo-sort-of-code:
mv %WINDIR%\System32\mfc42.dll %WINDIR\System32\mfc42.dll.old
Install new mfc42.dll
Tell user to save work and restart applications
When the applications restarted (note we didn't need to reboot the machine), they loaded the new mfc42.dll, and all was well. That, coupled with PendingFileOperations to delete the old one the next time the whole system restarted, worked pretty well.
This looks promising. A way of killing the file handle....
http://www.timstall.com/2009/02/killing-file-handles-but-not-process.html
You can use code that you supply the full file path to, and it will return a List<Processes> of anything locking that file:
using System.Runtime.InteropServices;
using System.Diagnostics;
static public class FileUtil
{
[StructLayout(LayoutKind.Sequential)]
struct RM_UNIQUE_PROCESS
{
public int dwProcessId;
public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
}
const int RmRebootReasonNone = 0;
const int CCH_RM_MAX_APP_NAME = 255;
const int CCH_RM_MAX_SVC_NAME = 63;
enum RM_APP_TYPE
{
RmUnknownApp = 0,
RmMainWindow = 1,
RmOtherWindow = 2,
RmService = 3,
RmExplorer = 4,
RmConsole = 5,
RmCritical = 1000
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct RM_PROCESS_INFO
{
public RM_UNIQUE_PROCESS Process;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
public string strAppName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
public string strServiceShortName;
public RM_APP_TYPE ApplicationType;
public uint AppStatus;
public uint TSSessionId;
[MarshalAs(UnmanagedType.Bool)]
public bool bRestartable;
}
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
static extern int RmRegisterResources(uint pSessionHandle,
UInt32 nFiles,
string[] rgsFilenames,
UInt32 nApplications,
[In] RM_UNIQUE_PROCESS[] rgApplications,
UInt32 nServices,
string[] rgsServiceNames);
[DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)]
static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);
[DllImport("rstrtmgr.dll")]
static extern int RmEndSession(uint pSessionHandle);
[DllImport("rstrtmgr.dll")]
static extern int RmGetList(uint dwSessionHandle,
out uint pnProcInfoNeeded,
ref uint pnProcInfo,
[In, Out] RM_PROCESS_INFO[] rgAffectedApps,
ref uint lpdwRebootReasons);
/// <summary>
/// Find out what process(es) have a lock on the specified file.
/// </summary>
/// <param name="path">Path of the file.</param>
/// <returns>Processes locking the file</returns>
/// <remarks>See also:
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx
/// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing)
///
/// </remarks>
static public List<Process> WhoIsLocking(string path)
{
uint handle;
string key = Guid.NewGuid().ToString();
List<Process> processes = new List<Process>();
int res = RmStartSession(out handle, 0, key);
if (res != 0) throw new Exception("Could not begin restart session. Unable to determine file locker.");
try
{
const int ERROR_MORE_DATA = 234;
uint pnProcInfoNeeded = 0,
pnProcInfo = 0,
lpdwRebootReasons = RmRebootReasonNone;
string[] resources = new string[] { path }; // Just checking on one resource.
res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);
if (res != 0) throw new Exception("Could not register resource.");
//Note: there's a race condition here -- the first call to RmGetList() returns
// the total number of process. However, when we call RmGetList() again to get
// the actual processes this number may have increased.
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
if (res == ERROR_MORE_DATA)
{
// Create an array to store the process results
RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
pnProcInfo = pnProcInfoNeeded;
// Get the list
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
if (res == 0)
{
processes = new List<Process>((int)pnProcInfo);
// Enumerate all of the results and add them to the
// list to be returned
for (int i = 0; i < pnProcInfo; i++)
{
try
{
processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
}
// catch the error -- in case the process is no longer running
catch (ArgumentException) { }
}
}
else throw new Exception("Could not list processes locking resource.");
}
else if (res != 0) throw new Exception("Could not list processes locking resource. Failed to get size of result.");
}
finally
{
RmEndSession(handle);
}
return processes;
}
}
Then, iterate the list of processes and close them and delete the files:
string[] files = Directory.GetFiles(target_dir);
List<Process> lstProcs = new List<Process>();
foreach (string file in files)
{
lstProcs = ProcessHandler.WhoIsLocking(file);
if (lstProcs.Count > 0) // deal with the file lock
{
foreach (Process p in lstProcs)
{
if (p.MachineName == ".")
ProcessHandler.localProcessKill(p.ProcessName);
else
ProcessHandler.remoteProcessKill(p.MachineName, txtUserName.Text, txtPassword.Password, p.ProcessName);
}
File.Delete(file);
}
else
File.Delete(file);
}
And depending on if the file is on the local computer:
public static void localProcessKill(string processName)
{
foreach (Process p in Process.GetProcessesByName(processName))
{
p.Kill();
}
}
or a network computer:
public static void remoteProcessKill(string computerName, string fullUserName, string pword, string processName)
{
var connectoptions = new ConnectionOptions();
connectoptions.Username = fullUserName; // #"YourDomainName\UserName";
connectoptions.Password = pword;
ManagementScope scope = new ManagementScope(#"\\" + computerName + #"\root\cimv2", connectoptions);
// WMI query
var query = new SelectQuery("select * from Win32_process where name = '" + processName + "'");
using (var searcher = new ManagementObjectSearcher(scope, query))
{
foreach (ManagementObject process in searcher.Get())
{
process.InvokeMethod("Terminate", null);
process.Dispose();
}
}
}
References:
How do I find out which process is locking a file using .NET?
Delete a directory where someone has opened a file
Using dotnet core (net6) I solved this problem by using the win32 restart manager (as others have also mentioned). However some of the linked articles have elaborate code importing DLLs and calling those.
After finding an app to kill processes that lock a file written by meziantou. I found out that he publishes .Net wrappers for win32 dlls (including the restart manager).
Leveraging his work, I was able to fix this problem with the following code:
using Meziantou.Framework.Win32;
public static IEnumerable<Process> GetProcessesLockingFile(string filePath)
{
using var session = RestartManager.CreateSession();
session.RegisterFile(filePath);
return session.GetProcessesLockingResources();
}
public static void KillProcessesLockingFile(string filePath)
{
var lockingProcesses = GetProcessesLockingFile(filePath);
foreach (var lockingProcess in lockingProcesses)
{
lockingProcess.Kill();
}
}

Categories

Resources