Related
My Programm which uses avicap32.dll for a Webcam Capture run on my Windows 10 dev computer without problems, but when I want to use the published version on another computer (Windows 7, no updates activated, offline) the program runs, but as soon I try to capture a picture from the camera, it doesn't work. I get an exception thrown and that's it. (Also I get a black image from the webcam, I double checked that the webcam is connected and accessible).
To add avicap32.dll I used the following code:
//This call is one of the most important and is vital to the operation of the OS.
[DllImport("user32", EntryPoint = "SendMessage")]
public static extern int SendMessage(int hWnd, uint Msg, int wParam, int lParam);
//This API creates the webcam instance so we can access it.
[DllImport("avicap32.dll", EntryPoint = "capCreateCaptureWindowA")]
public static extern int capCreateCaptureWindowA(string lpszWindowName, int dwStyle, int x, int y, int nWidth, int nHeight, int hwndParent, int nID);
//This API opens the clipboard so we can fetch webcam data.
[DllImport("user32", EntryPoint = "OpenClipboard")]
public static extern int OpenClipboard(int hWnd);
//This API cleans the clipboard.
[DllImport("user32", EntryPoint = "EmptyClipboard")]
public static extern int EmptyClipboard();
//This API closes the clipboard after use.
[DllImport("user32", EntryPoint = "CloseClipboard")]
public static extern int CloseClipboard();
//This API retrieves the data from the clipboard for use.
[DllImport("user32.dll")]
extern static IntPtr GetClipboardData(uint uFormat);
//This API is needed to execute the picture indication
[DllImport("cvextern.dll")]
extern static int MyFunction()
This is the function that captures the image and saves it:
public void Capture_Image() {
try {
ImageSize();
m_CapHwnd = capCreateCaptureWindowA("WebCap", 0, 0, 0, m_Width, m_Height, Handle.ToInt32(), 0);
SendMessage(m_CapHwnd, WM_CAP_CONNECT, 0, 0);
SendMessage(m_CapHwnd, WM_CAP_GT_FRAME, 0, 0);
SendMessage(m_CapHwnd, WM_CAP_COPY, 0, 0);
OpenClipboard(m_CapHwnd);
CloseClipboard();
SendMessage(m_CapHwnd, WM_CAP_DISCONNECT, 0, 0);
Image tempImg = (Bitmap)Clipboard.GetData("Bitmap");
pictureBox2.Image = tempImg;
pictureBox2.Refresh();
GC.Collect();
GC.WaitForPendingFinalizers();
fileName = "C:/FHT59N3/Bildanalyse_Projekt/image.jpg";
Clipboard.GetImage().Save(fileName, ImageFormat.Jpeg);
}
catch (NullReferenceException) {
string message = "No Camera found";
string title = "Please connect Camera";
MessageBoxButtons buttons = MessageBoxButtons.OK;
MessageBox.Show(message, title, buttons, MessageBoxIcon.Warning);
}
}
Are there special settings I have to set to make it run on Windows 7?
This is not a solution to your problem per se, but it's too long to fit in the comment box either.
You might want to consider removing
GC.Collect();
GC.WaitForPendingFinalizers();
as it's almost always better to use SafeHandles where possible and / or the IDisposable pattern when dealing with unmanaged resources.
GC.WaitForPendingFinalizers(); blocks program execution until all finalizers are run, and it can introduce deadlocks. GC.Collect(); can also slow down your program noticably because it tries to collect all generations - contrary to what happens on a normal GC run. It also has a high overload too, because it needs to stop all threads (= block program execution) and walk all object graphs to find memory that can be collected. Every object that cannot be collected right now will be promoted to the next generation, which means that your program hangs on to memory that would not actually be required anymore for longer than necessary.
For more information, see the "Remarks" section of GC.Collect() as well as the "Remarks" section of GC.WaitForPendingFinalizers.
See also When to call GC.Collect - rule number 1 is "Don't".
Okay i got a solution. I changed reading process from using external Dlls to using the OpenCV commands. Works fine. Even a few less codelines.
This is now my code to Read the Webcam:
public void Capture_Image()
{
try
{
capture = new VideoCapture();
Bitmap tempImg = capture.QueryFrame().Bitmap;
pictureBox2.Image = null;
pictureBox2.Image = tempImg;
pictureBox2.Refresh();
tempImg.Save("C:/FHT59N3/Bildanalyse_Projekt/image.jpg");
tempImg.Dispose();
capture.Dispose();
}
catch (NullReferenceException)
{
string message = "No Camera found";
string title = "Please connect Camera";
MessageBoxButtons buttons = MessageBoxButtons.OK;
MessageBox.Show(message, title, buttons, MessageBoxIcon.Warning);
}
}
All. I need to use winapi critical section in c# code.
First of all, I import functions:
[StructLayout(LayoutKind.Sequential)]
public struct CRITICAL_SECTION { public int dummy; }
// INIT CRITICAL SECTION
[DllImport("kernel32.dll")]
static extern bool InitializeCriticalSectionAndSpinCount(ref CRITICAL_SECTION
lpCriticalSection, uint dwSpinCount);
// DELETE CRITICAL SECTION
[DllImport("kernel32.dll")]
static extern void DeleteCriticalSection(ref CRITICAL_SECTION
lpCriticalSection);
// ENTER CRITICAL SECTION
[DllImport("kernel32.dll")]
static extern void EnterCriticalSection(ref CRITICAL_SECTION
lpCriticalSection);
// LEAVE CRITICAL SECTION
[DllImport("kernel32.dll")]
static extern void LeaveCriticalSection(ref CRITICAL_SECTION
lpCriticalSection);
In this way I try to use critical section:
static void Main(string[] args)
{
GenerateArray();
InvokeThread invokeThread = () =>
{
WaitForSingleObject(ghSemaphore, 0);
EnterCriticalSection(ref CriticalSection); // critical section
int[] array = new int[ARRAY_SIZE_PER_THREAD];
int baseI = thread * ARRAY_SIZE_PER_THREAD;
for (int i = 0; i < ARRAY_SIZE_PER_THREAD; ++i)
{
array[i] = gList[baseI + i];
}
LeaveCriticalSection(ref CriticalSection); // critical section
ReleaseSemaphore(ghSemaphore, 1, IntPtr.Zero);
return 0;
};
ghSemaphore = CreateSemaphore(ref seqAttr, THREADS_NUMBER, THREADS_NUMBER, "");
InitializeCriticalSectionAndSpinCount(ref CriticalSection, 0);
IntPtr threadPtr = Marshal.GetFunctionPointerForDelegate(invokeThread);
IntPtr[] handlers = new IntPtr[THREADS_NUMBER];
for (int i = 0; i < THREADS_NUMBER; ++i)
{
int handle = CreateThread(IntPtr.Zero, 0, threadPtr, IntPtr.Zero, 0, 0);
handlers[i] = new IntPtr(handle);
}
WaitForMultipleObjects(THREADS_NUMBER, handlers, true, Infinite);
DeleteCriticalSection(ref CriticalSection); // delete critical section
}
}
But at next line gList containes wrong values. And every thing is good, if I don't use critical section.
for (int i = 0; i < ARRAY_SIZE_PER_THREAD; ++i)
{
array[i] = gList[baseI + i];
}
Where could be a problem?
Your definition of the CRITICAL_SECTION struct is wrong. In the Windows headers is has 24 bytes or so, but yours has only 4.
Also, you don't do new CRITICAL_SECTION anywere. And you need it, InitializeCriticalSection sets the data, but don't allocate it.
I also agree that using the critical section API from .net seems like a bad design choice. If you must then I'd contemplate writing a C++/CLI mixed-mode wrapper. That way you can include the windows header files directly.
However, there is a rather obvious flaw in your p/invoke code. That's the declaration of the CRITICAL_SECTION struct. You've declared it as holding a single integer value. But the native struct is bigger than that. On x86 it is 24 bytes long, and on x64 it is 40 bytes long. You don't need to declare any of the fields since from your perspective it's just an opaque block of memory.
If I were you I would get rid of CRITICAL_SECTION. I would change all the ref CRITICAL_SECTION parameters to IntPtr. And I would use Marshal.AllocHGlobal(40) to create a large enough block of memory to hold the critical section data.
For example:
[DllImport("kernel32.dll")]
static extern bool InitializeCriticalSectionAndSpinCount(
IntPtr lpCriticalSection,
uint dwSpinCount
);
and so on.
I have developed a GUI test framework that does integrationtesting of our company website on a scheduled basis. When something fails, it'll take a screenshot of the desktop, among other things. This runs unattended on a logged in user on a dedicated Windows Server 2008.
The problem is that taking a screenshot on a desktop that I have disconnected my remote desktop session from. I get the following exception:
System.ComponentModel.Win32Exception (0x80004005): The handle is invalid
at System.Drawing.Graphics.CopyFromScreen(Int32 sourceX, Int32 sourceY, Int32 destinationX, Int32 destinationY, Size blockRegionSize, CopyPixelOperation copyPixelOperation)
at System.Drawing.Graphics.CopyFromScreen(Point upperLeftSource, Point upperLeftDestination, Size blockRegionSize)
at IntegrationTester.TestCaseRunner.TakeScreenshot(String name) in C:\VS2010\IntegrationTester\IntegrationTester\Config\TestCaseRunner.cs:line 144
at IntegrationTester.TestCaseRunner.StartTest() in C:\VS2010\IntegrationTester\IntegrationTester\Config\TestCaseRunner.cs:line 96
The TakeScreenshot() method looks like this:
public static void TakeScreenshot(string name)
{
var bounds = Screen.GetBounds(Point.Empty);
using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
}
bitmap.Save("someFileName", ImageFormat.Jpeg);
}
}
I have made sure that screensaver is set to "None" with no timeout.
I have also implemented a piece of code that does a couple of pinvokes to send a mouse move, hoping it would generate a desktop graphics handle.. but no.
IntPtr hWnd = GetForegroundWindow();
if (hWnd != IntPtr.Zero)
SendMessage(hWnd, 0x200, IntPtr.Zero, IntPtr.Zero);
Any advice is appreciated.
In order to capture the screen you need to run a program in the session of an user. That is because without the user there is no way to have a desktop associated.
To solve this you can run a desktop application to take the image, this application can be invoked in the session of the active user, this can be done from a service.
The code below allows you to invoke an desktop application in such way that it run on the local user's desktop.
If you need to execute as a particular user, check the code in article Allow service to interact with desktop? Ouch.. You can also consider using the function LogonUser.
The code:
public void Execute()
{
IntPtr sessionTokenHandle = IntPtr.Zero;
try
{
sessionTokenHandle = SessionFinder.GetLocalInteractiveSession();
if (sessionTokenHandle != IntPtr.Zero)
{
ProcessLauncher.StartProcessAsUser("Executable Path", "Command Line", "Working Directory", sessionTokenHandle);
}
}
catch
{
//What are we gonna do?
}
finally
{
if (sessionTokenHandle != IntPtr.Zero)
{
NativeMethods.CloseHandle(sessionTokenHandle);
}
}
}
internal static class SessionFinder
{
private const int INT_ConsoleSession = -1;
internal static IntPtr GetLocalInteractiveSession()
{
IntPtr tokenHandle = IntPtr.Zero;
int sessionID = NativeMethods.WTSGetActiveConsoleSessionId();
if (sessionID != INT_ConsoleSession)
{
if (!NativeMethods.WTSQueryUserToken(sessionID, out tokenHandle))
{
throw new System.ComponentModel.Win32Exception();
}
}
return tokenHandle;
}
}
internal static class ProcessLauncher
{
internal static void StartProcessAsUser(string executablePath, string commandline, string workingDirectory, IntPtr sessionTokenHandle)
{
var processInformation = new NativeMethods.PROCESS_INFORMATION();
try
{
var startupInformation = new NativeMethods.STARTUPINFO();
startupInformation.length = Marshal.SizeOf(startupInformation);
startupInformation.desktop = string.Empty;
bool result = NativeMethods.CreateProcessAsUser
(
sessionTokenHandle,
executablePath,
commandline,
IntPtr.Zero,
IntPtr.Zero,
false,
0,
IntPtr.Zero,
workingDirectory,
ref startupInformation,
ref processInformation
);
if (!result)
{
int error = Marshal.GetLastWin32Error();
string message = string.Format("CreateProcessAsUser Error: {0}", error);
throw new ApplicationException(message);
}
}
finally
{
if (processInformation.processHandle != IntPtr.Zero)
{
NativeMethods.CloseHandle(processInformation.processHandle);
}
if (processInformation.threadHandle != IntPtr.Zero)
{
NativeMethods.CloseHandle(processInformation.threadHandle);
}
if (sessionTokenHandle != IntPtr.Zero)
{
NativeMethods.CloseHandle(sessionTokenHandle);
}
}
}
}
internal static class NativeMethods
{
[DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
internal static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool CreateProcessAsUser(IntPtr tokenHandle, string applicationName, string commandLine, IntPtr processAttributes, IntPtr threadAttributes, bool inheritHandle, int creationFlags, IntPtr envrionment, string currentDirectory, ref STARTUPINFO startupInfo, ref PROCESS_INFORMATION processInformation);
[DllImport("Kernel32.dll", EntryPoint = "WTSGetActiveConsoleSessionId")]
internal static extern int WTSGetActiveConsoleSessionId();
[DllImport("WtsApi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool WTSQueryUserToken(int SessionId, out IntPtr phToken);
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr processHandle;
public IntPtr threadHandle;
public int processID;
public int threadID;
}
[StructLayout(LayoutKind.Sequential)]
internal struct STARTUPINFO
{
public int length;
public string reserved;
public string desktop;
public string title;
public int x;
public int y;
public int width;
public int height;
public int consoleColumns;
public int consoleRows;
public int consoleFillAttribute;
public int flags;
public short showWindow;
public short reserverd2;
public IntPtr reserved3;
public IntPtr stdInputHandle;
public IntPtr stdOutputHandle;
public IntPtr stdErrorHandle;
}
}
This code is a modification of the one found at the article Allow service to interact with desktop? Ouch. (MUST READ)
Addendum:
The code above allows to execute a program in the desktop of the user logged locally on the machine. This method specific for the current local user, but it is possible to do it for any user. Check the code at the article Allow service to interact with desktop? Ouch. for an example.
The core of this method is the function CreateProcessAsUser, you can find more about at MSDN.
Replace "Executable Path" with the path of the executable to run. Replace "Command Line" with the string passed as execution arguments, and replace "Working Directory" with the working directory you want. For example you can extract the folder of the executable path:
internal static string GetFolder(string path)
{
var folder = System.IO.Directory.GetParent(path).FullName;
if (!folder.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString()))
{
folder += System.IO.Path.DirectorySeparatorChar;
}
return folder;
}
If you have a service, you can use this code in the service to invoke a desktop application. That desktop application may also be the the service executable... for that you can use Assembly.GetExecutingAssembly().Location as the executable path. Then you can use System.Environment.UserInteractive to detect if the executable is not running as a service and pass as execution arguments information about the task needed to do. In the context of this answer that is to capture the screen (for example with CopyFromScreen), it could be something else.
What I did to solve this is call tscon.exe and tell it to redirect the session back to the console just before the screenshot is taken. It goes like this (note, this exact code is untested):
public static void TakeScreenshot(string path) {
try {
InternalTakeScreenshot(path);
} catch(Win32Exception) {
var winDir = System.Environment.GetEnvironmentVariable("WINDIR");
Process.Start(
Path.Combine(winDir, "system32", "tscon.exe"),
String.Format("{0} /dest:console", GetTerminalServicesSessionId()))
.WaitForExit();
InternalTakeScreenshot(path);
}
}
static void InternalTakeScreenshot(string path) {
var point = new System.Drawing.Point(0,0);
var bounds = System.Windows.Forms.Screen.GetBounds(point);
var size = new System.Drawing.Size(bounds.Width, bounds.Height);
var screenshot = new System.Drawing.Bitmap(bounds.Width, bounds.Height);
var g = System.Drawing.Graphics.FromImage(screenshot)
g.CopyFromScreen(0,0,0,0,size);
screenshot.Save(path, System.Drawing.Imaging.ImageFormat.Jpeg);
}
[DllImport("kernel32.dll")]
static extern bool ProcessIdToSessionId(uint dwProcessId, out uint pSessionId);
static uint GetTerminalServicesSessionId()
{
var proc = Process.GetCurrentProcess();
var pid = proc.Id;
var sessionId = 0U;
if(ProcessIdToSessionId((uint)pid, out sessionId))
return sessionId;
return 1U; // fallback, the console session is session 1
}
This is not a supported feature it is true that it works in XP and windows server 2003 however this is seen as security flaw.
To prevent this, don't use the 'x' to close the remote connection, but use %windir%\system32\tscon.exe 0 /dest:console instead. (That will insure that the screen isn't locked). - Nicolas Voron
It true that if you disconnect from the server in this way the "screen" wont be locked to ensure it stay unlocked you need to make sure you turn off the screen saver since as soon as that start up it will auto lock you screen.
There is quite a few examples only of people doing the same thing even here at stack overflow the post below suggest that you create a windows application that run under an actual user account that sends screen shots over IPC to the running service.
The correct way to get a custom GUI that works with a service is to
separate them into two processes and do some kind of IPC (inter
process communication). So the service will start-up when the machine
comes up and a GUI application will be started in the user session. In
that case the GUI can create a screenshot, send it to the service and
the service can do with it, whatever you like. - Screenshot of process under Windows Service
I have collated a few strategies I have found online that may give you some ideas.
Third party software
There is a lot of programs out there that make screen shots of web sites like http://www.websitescreenshots.com/ they have a user interface and command line tool. But if you are using some testing framework this might not work since it will make a new request to fetch all the assets and draw the page.
WebBrowser control
I am not sure what browser you are using to test you company web site however if you are not bothered about which browser It is you could use a WebBrowser control and use the DrawToBitmap method.
Virtualisation
I have seen a system where the developers were using virtual environments with the browser of their choice with all the settings done to make sure the machine didn't lock and if it did it would restart.
Selenium
It is also possible using selenium with the selenium-webdriver and headless a ruby gem developed by leonid-shevtsov if your test are in selenium this approach might be the best. Selenium itself support screen capture on the webdrivers they have available.
Of course all of this depends on what you are using for your testing framework if you can share some details on your setup we will be able to give you a better answer.
The problem seems to be that when you close the remote connection, the screen goes in a locked state which prevent the system to perform graphics operation like your g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
To prevent this, don't use the 'x' to close the remote connection, but use %windir%\system32\tscon.exe 0 /dest:console instead. (That will insure that the screen isn't locked).
Read this post for further informations (in VBA, but c#-understandable ;-) )
EDIT
If you want to do it directly in c#, try something like this :
Process p = new Process();
p.StartInfo.FileName = "tscon";
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
p.StartInfo.Arguments = "0 /dest:console";
p.Start();
I found a similar question Screen capture with C# and Remote Desktop problems. Hope it helps you solve the problem.
Here is the code from that answer:
public Image CaptureWindow(IntPtr handle)
{
// get te hDC of the target window
IntPtr hdcSrc = User32.GetWindowDC(handle);
// get the size
User32.RECT windowRect = new User32.RECT();
User32.GetWindowRect(handle, ref windowRect);
int width = windowRect.right - windowRect.left;
int height = windowRect.bottom - windowRect.top;
// create a device context we can copy to
IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
// create a bitmap we can copy it to,
// using GetDeviceCaps to get the width/height
IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc, width, height);
// select the bitmap object
IntPtr hOld = GDI32.SelectObject(hdcDest, hBitmap);
// bitblt over
GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, GDI32.SRCCOPY);
// restore selection
GDI32.SelectObject(hdcDest, hOld);
// clean up
GDI32.DeleteDC(hdcDest);
User32.ReleaseDC(handle, hdcSrc);
// get a .NET image object for it
Image img = Image.FromHbitmap(hBitmap);
// free up the Bitmap object
GDI32.DeleteObject(hBitmap);
return img;
}
I think the problem may be that you're on the wrong WindowStation. Have a look at these articles;
Why does print screen in a Windows Service return a black image?
Screen capture from windows service
It could be that your win-station is dissappearing when you disconnect. Are you running the application when you log in and then trying to leave it running when you disconnect?
If so, does it still do it if you connect with "mstsc /admin"? In other words, connecting to and running on the console session? If not, this might be a workaround.
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?
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();
}
}