File Read-only access irrespective of locks (C#) - c#

How do I open (using c#) a file that is already open (in MS Word, for instance)? I thought if I open the file for read access e.g.
FileStream f= new FileStream('filename', FileMode.Open, FileAccess.ReadWrite);
I should succeed, but I get an exception:
"the process cannot access the file
because it is locked ..."
I know there must be a way to read the file irrespective of any locks placed on it, because I can use windows explorer to copy the file or open it using another program like Notepad, even while it is open in WORD.
However, it seems none of the File IO classes in C# allows me to do this. Why?

You want to set FileAccess=Read and FileShare=ReadWrite. Here is a great article on this (along with an explanation of why):
http://coding.infoconex.com/post/2009/04/How-do-I-open-a-file-that-is-in-use-in-C.aspx

Your code is using the FileAccess.Read*Write* flag. Try just Read.

I know this is an old post. But I needed this and I think this answer can help others.
Copying a locked file the way the explorer does it.
Try using this extension method to get a copy of the locked file.
Usage example
private static void Main(string[] args)
{
try
{
// Locked File
var lockedFile = #"C:\Users\username\Documents\test.ext";
// Lets copy this locked file and read the contents
var unlockedCopy = new
FileInfo(lockedFile).CopyLocked(#"C:\Users\username\Documents\test-copy.ext");
// Open file with default app to show we can read the info.
Process.Start(unlockedCopy);
}
catch (Exception ex)
{
Trace.TraceError(ex.Message);
}
}
Extension method
internal static class LockedFiles
{
/// <summary>
/// Makes a copy of a file that was locked for usage in an other host application.
/// </summary>
/// <returns> String with path to the file. </returns>
public static string CopyLocked(this FileInfo sourceFile, string copyTartget = null)
{
if (sourceFile is null)
throw new ArgumentNullException(nameof(sourceFile));
if (!sourceFile.Exists)
throw new InvalidOperationException($"Parameter {nameof(sourceFile)}: File should already exist!");
if (string.IsNullOrWhiteSpace(copyTartget))
copyTartget = Path.GetTempFileName();
using (var inputFile = new FileStream(sourceFile.FullName, FileMode.Open,
FileAccess.Read, FileShare.ReadWrite))
using (var outputFile = new FileStream(copyTartget, FileMode.Create))
inputFile.CopyTo(outputFile, 0x10000);
return copyTartget;
}
}

Related

How to get content of excel's temporary file?

When I open an excel file, a hidden temporary file is generated in the same folder. I can open it with the TotalCommander Viewer, but I always get an IO exception when trying to open with powershell or c#.
new FileStream(#"D:\~$test.xlsx", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
System.IO.IOException: 'The process cannot access the file 'D:~$test.xlsx' because it is being used by another process.'
So how can I get the content?
Unfortunately for some reason you can not open the direct file, so I suggest another method when you copy a the file to a temp file, then read it and finally you delete the temp file, this way you can read it, I suppose TotalCommander uses the same method for opening files in Viewer.
static void Main(string[] args)
{
CopyReadAndDelete(#"c:\Documents\~$test.xlsx");
}
static void CopyReadAndDelete(string filePath)
{
var tempFileFullPath = Path.Combine(Path.GetDirectoryName(filePath), Guid.NewGuid().ToString());
File.Copy(filePath, tempFileFullPath);
try
{
using (var sr = new StreamReader(tempFileFullPath))
{
Console.WriteLine(sr.ReadToEnd()); //or do anything with the content
}
}
finally
{
File.Delete(tempFileFullPath);
}
}

Copy FIlestream to new stream giving empty file

I'm working on converting some files, but I'm having some issues on the 2nd step of this.
Load file from source location
Save file to temp folder
Save converted file to Output location
I have 2 methods for reading the original file, but there is a problem with both of them.
Method 1: The file remains locked (so when something goes wrong, I have to restart the app)
Method 2: The temp file is empty
Anybody got an idea on how to fix one of those problems?
Utilities class
/// <summary>
/// Get document stream
/// </summary>
/// <param name="DocumentName">Input document name</param>
public static Stream GetDocumentStreamFromLocation(string documentLocation)
{
try
{
//ExStart:GetDocumentStream
// Method one: works, but locks file
return File.Open(documentLocation, FileMode.Open, FileAccess.Read);
// Method two: gives empty file on temp folder
using (FileStream fsSource = File.Open(documentLocation, FileMode.Open, FileAccess.Read))
{
var stream = new MemoryStream((int)fsSource.Length);
fsSource.CopyTo(stream);
return stream;
}
//ExEnd:GetDocumentStream
}
catch (FileNotFoundException ioEx)
{
Console.WriteLine(ioEx.Message);
return null;
}
}
/// <summary>
/// Save file in any format
/// </summary>
/// <param name="filename">Save as provided string</param>
/// <param name="content">Stream as content of a file</param>
public static void SaveFile(string filename, Stream content, string location = OUTPUT_PATH)
{
try
{
//ExStart:SaveAnyFile
//Create file stream
using (FileStream fileStream = File.Create(Path.Combine(Path.GetFullPath(location), filename)))
{
content.CopyTo(fileStream);
}
//ExEnd:SaveAnyFile
}
catch (System.Exception ex)
{
Console.WriteLine(ex.Message);
}
}
I Call the following functions as following:
public static StreamContent Generate(string sourceLocation)
{
// Get filename
var fileName = Path.GetFileName(sourceLocation);
// Create tempfilename
var tempFilename = $"{Guid.NewGuid()}_{fileName}";
// Put file in storage location
Utilities.SaveFile(tempFilename, Utilities.GetDocumentStreamFromLocation(sourceLocation), Utilities.STORAGE_PATH);
// ... More code
}
In order to copy the source file to a temp folder, the easiest way is to use the File.Copy method from the System.IO namespace. Consider the following:
// Assuming the variables have been set as you already had, this creates a copy in the intended location.
File.Copy(documentLocation, filename);
After some further digging. I found out that you can add a property in the File.Open that "fixes" this issue:
return File.Open(documentLocation, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
With the downside that you still can't move / rename the file, but the lock is removed.

File locked only if copy/paste, not if cut/paste

I'm monitoring a folder for new files, and when the new file is present I read (and save in a txt) the file as following:
FileStream file = File.Open(this.filePath, FileMode.Open, FileAccess.Read);
StreamReader reader = new System.IO.StreamReader(file);
string text = reader.ReadToEnd();
reader.Close();
If I copy/paste in the folder the source file, I receive an IOExcpetion that tells me that the file is used by another process.
If I cut/paste in the folder, all works.
Moreover locking problem happens also If I copy (but also cut in this case)/paste the file from another machine into the monitored folder.
Do you have an idea about what is happening?
There is a safer way to access to the file in order to avoid this type of locks?
Thanks!
Here is a little snippet I do to ensure the file is finished copying or not in use by another process.
private bool FileUploadCompleted(string filename)
{
try
{
using (FileStream inputStream = File.Open(filename, FileMode.Open,
FileAccess.Read,
FileShare.None))
{
return true;
}
}
catch (IOException)
{
return false;
}
}
Then you can implement this before your process logic
while (!FileUploadCompleted(filePath))
{
//if the file is in use it will enter here
//So you could sleep the thread here for a second or something to allow it some time
// Also you could add a retry count and if it goes past the allotted retries you
// can break the loop and send an email or log the file for manual processing or
// something like that
}

How to check an excel workbook is already open in C# win forms

I am opening multiple excel files in my application. Some will be automatically open when the application starts whereas some are opening at the run time.
Now I want to get data from an excel file on a button click. But before opening it, I want to check that excel file is already open.
If it is open, I want to read from it directly.
if it is not open, I want to open it and read from it.
But in both the cases I don't want to close the file after reading.`
I am using this method to open excel file.
objExcel = new Excel.ApplicationClass();
objWorkbook = objExcel.Workbooks.Open(...);`
Please help I am new to C#.
if i understand corectly, you actually want to find whether some files are already open by this winform application, right?
if so, i think it should be fairly simple - just cache the opened workbook to some dictionary or so:
static Dictionary<string, Workbook> _openedWorkBooks = new Dictionary<string, Workbook>();
public static Workbook GetWorkbook(string filePath) {
Workbook wkb = null;
if (!(_openedWorkBooks.TryGetValue(filePath, out wkb)))
{
// Open the file and store it into the dictionary
}
return wkb;
}
// remember to remove it when it's closed
public static CloseWorkbook()
{ // need to remove the corresponding key from the dictionary
}
also, you could use single instance of excel application too, and then all the opened workbook could be rerieved from App.Workbooks, however, it throws some exception sometimes (not sure why, but i did encounter before).
var app = new Microsoft.Office.Interop.Excel.Application();
var bk1 = app.Workbooks.Open("c:\temp\myfile.xls");
var allOpenBks = app.Workbooks;
Actually it's still worth to call IsFileLock method to check the file is already open by other apps, otherwise you could encounter some errors.
You can checkk, if you have Read-Write access:
/// <summary>
/// Check wether a file is locked
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
public static bool IsFileLocked(FileInfo file)
{
FileStream stream = null;
try
{
stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (IOException)
{
//the file is unavailable because it is:
//still being written to
//or being processed by another thread
//or does not exist (has already been processed)
return true;
}
finally
{
if (stream != null)
stream.Close();
}
//file is not locked
return false;
}
The Code is from some one else on StackOverflow.
Do .xlsx? If so, you can use for reading and writing Excel-Files OpenXML.

Why can't this file be deleted after using C1ZipFile?

The following code gives me a System.IO.IOException with the message 'The process cannot access the file'.
private void UnPackLegacyStats()
{
DirectoryInfo oDirectory;
XmlDocument oStatsXml;
//Get the directory
oDirectory = new DirectoryInfo(msLegacyStatZipsPath);
//Check if the directory exists
if (oDirectory.Exists)
{
//Loop files
foreach (FileInfo oFile in oDirectory.GetFiles())
{
//Check if file is a zip file
if (C1ZipFile.IsZipFile(oFile.FullName))
{
//Open the zip file
using (C1ZipFile oZipFile = new C1ZipFile(oFile.FullName, false))
{
//Check if the zip contains the stats
if (oZipFile.Entries.Contains("Stats.xml"))
{
//Get the stats as a stream
using (Stream oStatsStream = oZipFile.Entries["Stats.xml"].OpenReader())
{
//Load the stats as xml
oStatsXml = new XmlDocument();
oStatsXml.Load(oStatsStream);
//Close the stream
oStatsStream.Close();
}
//Loop hit elements
foreach (XmlElement oHitElement in oStatsXml.SelectNodes("/*/hits"))
{
//Do stuff
}
}
//Close the file
oZipFile.Close();
}
}
//Delete the file
oFile.Delete();
}
}
}
I am struggling to see where the file could still be locked. All objects that could be holding onto a handle to the file are in using blocks and are explicitly closed.
Is it something to do with using FileInfo objects rather than the strings returned by the static GetFiles method?
Any ideas?
I do not see problems in your code, everything look ok. To check is the problem lies in C1ZipFile I suggest you initialize zip from stream, instead of initialization from file, so you close stream explicitly:
//Open the zip file
using (Stream ZipStream = oFile.OpenRead())
using (C1ZipFile oZipFile = new C1ZipFile(ZipStream, false))
{
// ...
Several other suggestions:
You do not need to call Close() method, with using (...), remove them.
Move xml processing (Loop hit elements) outsize zip processing, i.e. after zip file closeing, so you keep file opened as least as possible.
I assume you're getting the error on the oFile.Delete call. I was able to reproduce this error. Interestingly, the error only occurs when the file is not a zip file. Is this the behavior you are seeing?
It appears that the C1ZipFile.IsZipFile call is not releasing the file when it's not a zip file. I was able to avoid this problem by using a FileStream instead of passing the file path as a string (the IsZipFile function accepts either).
So the following modification to your code seems to work:
if (oDirectory.Exists)
{
//Loop files
foreach (FileInfo oFile in oDirectory.GetFiles())
{
using (FileStream oStream = new FileStream(oFile.FullName, FileMode.Open))
{
//Check if file is a zip file
if (C1ZipFile.IsZipFile(oStream))
{
// ...
}
}
//Delete the file
oFile.Delete();
}
}
In response to the original question in the subject: I don't know if it's possible to know if a file can be deleted without attempting to delete it. You could always write a function that attempts to delete the file and catches the error if it can't and then returns a boolean indicating whether the delete was successful.
I'm just guessing: are you sure that oZipFile.Close() is enough? Perhaps you have to call oZipFile.Dispose() or oZipFile.Finalize() to be sure it has actually released the resources.
More then Likely it's not being disposed, anytime you access something outside of managed code(streams, files, etc.) you MUST dispose of them. I learned the hard way with Asp.NET and Image files, it will fill up your memory, crash your server, etc.
In the interest of completeness I am posing my working code as the changes came from more than one source.
private void UnPackLegacyStats()
{
DirectoryInfo oDirectory;
XmlDocument oStatsXml;
//Get the directory
oDirectory = new DirectoryInfo(msLegacyStatZipsPath);
//Check if the directory exists
if (oDirectory.Exists)
{
//Loop files
foreach (FileInfo oFile in oDirectory.GetFiles())
{
//Set empty xml
oStatsXml = null;
//Load file into a stream
using (Stream oFileStream = oFile.OpenRead())
{
//Check if file is a zip file
if (C1ZipFile.IsZipFile(oFileStream))
{
//Open the zip file
using (C1ZipFile oZipFile = new C1ZipFile(oFileStream, false))
{
//Check if the zip contains the stats
if (oZipFile.Entries.Contains("Stats.xml"))
{
//Get the stats as a stream
using (Stream oStatsStream = oZipFile.Entries["Stats.xml"].OpenReader())
{
//Load the stats as xml
oStatsXml = new XmlDocument();
oStatsXml.Load(oStatsStream);
}
}
}
}
}
//Check if we have stats
if (oStatsXml != null)
{
//Process XML here
}
//Delete the file
oFile.Delete();
}
}
}
The main lesson I learned from this is to manage file access in one place in the calling code rather than letting other components manage their own file access. This is most apropriate when you want to use the file again after the other component has finished it's task.
Although this takes a little more code you can clearly see where the stream is disposed (at the end of the using), compared to having to trust that a component has correctly disposed of the stream.

Categories

Resources