Blank space after file extension -> weird FileInfo behaviour - c#

Somehow a file has appeared in one of my directories, and it has space at the end of its extension -
its name is "test.txt ". The weird thing is that Directory.GetFiles() returns me the path of this
file, but I'm unable to retrieve file information with FileInfo class.
The error manifests here:
DirectoryInfo di = new DirectoryInfo("c:\\somedir");
FileInfo fi = di.GetFileSystemInfos("test*")[0] as FileInfo;
//correctly fi.FullName is "c:\somedir\test.txt "
//but fi.Exists==false (!)
Is FileInfo class broken? Can I somehow retrieve information about this file? I really don't know how did that file appear on my file system, and I am unable to recreate some more of them.
All of my attempts to create a new file with this type of extension have failed, but now my program is
crashing when encoutering it. I can easily handle the exception when finding the file, but boy am I
curious about this!

Ending file names with a space is documented as a Bad Idea.
From MSDN "Naming Files, Paths, and Namespaces (Windows)":
Do not end a file or directory name with a space or a period. Although the underlying file system may support such names, the Windows shell and user interface does not.
Also, the KB article "INFO: Filenames Ending with Space or Period Not Supported":
Problems can arise when a Macintosh client creates a file on a Windows NT server. The code to remove trailing spaces and periods is not carried out and the Macintosh user gets the correctly punctuated filename. The Win32 APIs FindFirstFile() and FindNextFile() return a filename that ends in a space or in a period; however, there is no way to create or open the file using the Win32 API.
DirectoryInfo probably uses FindFirstFile() and friends to produce directory listings. File.Exists is most likely implemented through GetFileAttributes() which probably suffers from the same problem as CreateFile() and will report a nonexistent file.
Hence, not a problem in .NET specifically, but in Windows itself.

Yes i know of these files. I also got once such a beast thing. To get rid of it i don't know about a programming way in C#, but good old command line is your friend:
Open a console window in the given folder (or execute cmd and navigate to the folder with cd command). Now enter dir /x to retrieve the shortname of the files in this directory. Use this name to delete or rename the file by using the del or ren command.

You can manipulate files with trailing spaces (among other edge cases) if you use the \\?\ path syntax: \\?\c:\somedir\test.txt .

Related

Can't get to files with multiple consecutive spaces at the end of a FOLDER name

I'm trying to programmaticaly locate a file on a network server where there are multiple directory levels, one of which has two spaces at the end of the name. EG:
\\MyServer\C$\TopLevel\Account \someone#somewhere.com\AFileName.pdf
Note that the Account level represents many folders using account codes for the folder name, and most of them have the two spaces at the end.
I can't amend the directory structure as it's used by other processes outside my control. I will know in advance the path down to and including the Account level, so I need to search the Email level subfolders for a file containing an order number in the file name. I also know the order number.
The problem is that FileInfo, Directory and DirectoryInfo all balk at the two spaces. Whilst Windows has allowed the folders to be created, and an automated third party process can create folders and files below the Account level, nothing I have tried so far has worked because the spaces get stripped out by the c# file and directory classes.
I can't use:
Directory.GetFiles(PathToTopLevel, "*.pdf")
because there are over 1000 Account level folders, each of which might have 5 or 6 Email level folders with possibly many hundreds of pdf files in each one. I did try it but had to kill it when it was still thinking about it after 5 minutes.
Directory.GetFiles(PathToAccountLevel, "*.pdf")
This returns the 'can't find part of the path' error.
I also tried:
filePath = Path.Combine(PathToAccountLevel, $"{order_no}.pdf");
var fi = new FileInfo(filePath);
which also returns 'can't find part of the path'.
I've done lots of trawling through SO and other forums with no success as they are mainly concerned either with file names or single spaces, but I did find a useful method here which I have tried:
filePath = AddQuotesIfRequired(PathToAccountLevel);
Directory.GetFiles(filePath, "*.pdf")
Which does indeed add the quotes, but it makes no difference to the outcome.
Also tried (with and without adding quotes):
Directory.GetFiles(#filePath, "*.pdf")
I've tried as many combinations as I can think of using quotes and '#' etc. but absolutely nothing has worked so far. I've even hard coded the full path down to the Email level and still no joy.
I'm using C# with VS2019 on Windows 10. The server is running Windows Server 2012 R2 DataCenter. I've run the test executable locally on the server and the results are the same.
The really galling aspect of this is that a simple test program written in delphi and using FindFirst/Next works perfectly!
I should also mention that permissions are not an issue, and the files exist.
Any help at all would be great, thanks.
Re the possible duplicate/proposed answer from qaabaam:
Answer 1 for that question is not valid code - the line
DirectoryInfo wantedDir = tempWithoutMac.GetDirectories.Where(c => c.Contains(MacID)).First();
throws an error for GetDirectories being a method and not valid in the context. If you change it to GetDirectories() it then complains that DirectoryInfo does not have a definition for Contains.
Answer 2 using alt 0160 still throws the can't find part of the path error.
Answer 3 is basically an opinion and doesn't help at all.
If you can figure out how to get Answer 1 to work I'll definitely give it a try!
Leading or trailing spaces in directory names is one of those grey areas in Windows: you can do it, but you probably shouldn't. The reason for this is simple - the vast majority of the time, a leading or trailing space is a typo, and the Windows APIs around paths have been designed to take this into account, to the point where (as you've discovered) they will outright ignore leading or trailing spaces. (For example, in Windows 10, it's literally impossible to delete a directory with trailing spaces via Windows Explorer!) Since C# is built on top of these APIs, it inherits their "quirks".
Fortunately there is a way to tell Windows "don't try to fix this path, it is correct, I know what I'm doing" via the use of DOS device paths. In brief:
For local paths, prefix them with the literal \\?\. In other words, C:\foo\bar would become \\?\C:\foo\bar.
For UNC paths, replace the leading \\ with \\?\UNC\. In other words, \\server\foo\bar would become \\?\UNC\server\foo\bar.
In your case, then, all you need to do is change \\MyServer\C$\TopLevel\Account \ to \\?\UNC\MyServer\C$\TopLevel\Account \ and everything should work as you expect.
As an aside, what you are doing is a document search/query operation, which will be incredibly slow as it's performing I/O, and over the network at that. You'd be far better served in writing a tool to migrate these documents into a proper database, then searching that database for the documents.

How to get a file/folder type in C#?

I was thinking of making a more "advanced" version of System.IO.Path, System.IO.File & System.IO.Directory.
Obviously I started with System.IO.Path, because the base for File and Directory will be Path, I started writing a function named ContainsInvalidChars (checks to see if said path contains invalid characters), for this I had to use GetDirectoryName & GetFileName but, after writing these 2 methods I stopped and thought to myself:
How do these 2 methods distinguish between the input path being a directory, file or dir + file". I ran a couple of tests and found that they don't. For example, if you had something like C:\\Users\\UserName\\AppData and you were to put this into GetFileName it would return AppData even tho AppData is a folder and technically should be part of the directory and file name should be null, same with GetDirectoryName, even tho AppData is part of the directory name it only returns C:\\Users\\UserName. So I came to the conclusion that I will first have to write a proper GetFileName & GetDirectoryName methods and then only write ContainsInvalidChars. The question still stands, how do I distinguish between the 3 types of path. While thinking about this I started looking for a couple file on my computer when I noticed (something that's easily ignored) the Type column where it shows the type of file/folder, if folder then File folder if file (even without extension) (Ext) File so, here I am. Is there any way to get the file/folder type in C#?
I know that at the end of the day you could always write C:\\Users\\UserName\\AppData\\ and yes, this will be DIR="C:\Users\UserName\AppData" & FILE="" but i know some don't write \\ at the end and just write C:\\Users\\UserName\\AppData.
System.IO provided functions:
File.Exists(string Path) and Directory.Exists(string Path) . Returns boolean value based on value of Path
if (Directory.Exists(#"C:\Users\UserName\AppData\"))
Console.WriteLine("Folder exists");
else
Console.WriteLine("Path might be of file");
Ouput:
Folder exists
if you are using .net 4.0 and above then there is one way to do this by using below code.
File.GetAttributes("your path to directory or file").HasFlag(FileAttributes.Directory)
But make sure the path is valid.
Or
you may also use
File.Exists();
Directory.Exists();

File.OpenRead() throws UnauthorizedAccessException when a directory with same path exists

When invoking File.OpenRead() on a file on a network share where a folder with the same name (but different casing) exists, UnauthorizedAccessException. This can happen on Linux shares where casing matters.
Example:
* P: is mapped to \somemachine\someshare
* P:\files\ is a folder
* P:\files\OUTPUT is a file
* P:\files\output is a folder
The following code will throw:
const string path = #"P:\files\OUTPUT";
DirectoryInfo dir = new DirectoryInfo(Path.GetDirectoryName(path));
FileInfo file = dir.EnumerateFiles().FirstOrDefault(x => string.Equals(Path.GetFileName(path), x.Name));
// All of the below throws UnauthorizedAccessException
file.OpenRead();
FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
File.OpenRead(path);
Is there any way I can open the file case sensitively? It's not an option to rename the file or move the folder out of the way as this is a read only share.
That's the default SAMBA behaviour:
That which is lexically first will be accessible to MS Windows users;
the others are invisible and unaccessible any other solution would be
suicidal.
The only safe option is to use different names for the folder and file. Trying to ensure that one or the other is lexically first is (per the docs) ... suicidal.
EDIT
From the comments, it appears that Notepad can open the correct file. Despite its simplistic look, Notepad does a lot of work to handle complex cases, like detecting a file's when no BOM is available.
It may also be using long Unicode paths (eg \\?\P:\files\OUTPUT) to access files, alternate streams and shares, or it may be detecting that a network volume is in use and switch to the long path format.
System.IO doesn't support this as it's NTFS specific, but the the open-source AlphaFS provides access to this and a LOT of other useful NTFS functionality like transactions and Object IDs.
You may be able to use AlphaFS to open the file using a long path, although I haven't tried this.
Check Security access permission of file.

Pass file name to .exe in registry key

I have a small installer that installs some .exe and DLLs to the target machine, and sets a key in registry so that on right click on Excel files the user sees a new context menu item. This command key has for value something like :
[TARGETDIR]myexecutable.exe %1
The %1 has for effect to pass the file name to my executable in args[0]. I expect this to give to my .exe the full path. But it gives me some sort of compressed path. For example for :
C:\Documents and Settings\user\Desktop\teestqqqq.xls
it will give me :
C:\DOCUME~1\user\Desktop\TEEST~1.XLS
That's a problem for me. How can i solve this ? I need a proper full path.
UPDATE : As requested in comments and in answers, this is an issue to me because from the path I receive I derive some new folder names. These folder names are to be committed so SVN repositories later on by some users. I have noticed that on some machines of my users, the .exe receive the sort path, and on other, the long path. My users consider the folder names to be "unique" for DIFF purposes on SVN. I just need a way to ensure uniqueness of the folder names (I think there is already what I need in answers below at the time of writing)
Replace %1 with "%1", i.e. put quotes around, you'll get the long file name. The thing is 'Documents and Settings' has spaces in its name which can't be passed to your program correctly without quotes. That's why the shell converts the path to the short name, without spaces, and then passes it to your program.
Try
Path.GetFullPath(path)
That should do it. But you should just be able to work with the short path.
You could try:
string shortName = #"C:\DOCUME~1\user\Desktop\TEEST~1.XLS";
string longName = System.IO.Path.GetFullPath(shortName);
Long before "long file names". File names in MS DOS were limited to 8 characters for the name, followed by a dot and maximum of 3 characters for the extension. What you are seeing is the short version of the path with long file names. Here is a longer explanation: http://www.computerhope.com/issues/ch000209.htm
Now explain how the given path is a problem for you? Because windows APIs should be able to work with the short file name versions as well.
As ken2k mentioned, System.IO.Path.GetFullPath(..) will return the expanded path given a short file name path.
Quoting the documentation:
If you pass in a short file name, it is expanded to a long file name.

Is it worth it to lookup the default application in the registry when opening a file from a C# application?

I'm building an application (a side project which is likely to enlist the help of the stackoverflow community on more than one occasion) which will need to open a variety of file types (i.e. open Word documents in Word, not natively in my application).
I've been playing with some code for looking up the default application for the file type in the registry and passing this to Process.Start(). There seem to be two issues with this approach:
1) The application name is quoted in some instances, and not in others.
2) Process.Start() requires that the application path and it's arguments are passed separately (i.e. Process.Start("notepad.exe", #"C:\myfile.txt"); rather than Process.Start(#"notepad.exe C:\myfile.txt");).
This means when I retrieve the path from the registry, I have to split it (after determining if I need to split on quotes or spaces) to determine what part is the application path and what parts are arguments, then pass those separately to Process.Start().
The alternative seems to be to just pass the filename, as in Process.Start(#"C:\myfile.txt"), but I think this only works if the application is in the Path environment variable.
Which way is better? In the case of the registry, is there a common solution for how to do the argument parsing?
Thanks for any and all help!
Update:
I guess the short answer is 'No.'
It seems like I was really going the overkill route, and that passing just the filename will work whenever there's an associated value in the registry. I.e. anything I find in the registry myself, Process.Start() already knows how to do.
I did discover that when I try this with a "new" filetype, I get a Win32Exception stating "No application is associated with the specified file for this operation." Fredrik Mörk mentions in a comment that this doesn't occur for him in Vista. What's the proper way to handle this?
If the extension is registered to be opened with a certain application, it doesn't need to be in the PATH in order to run.
The application does not need to be in the PATH if you only specify the filename. The following code worked fine for me:
System.Diagnostics.Process.Start(#"C:\Users\Dan\Desktop\minors.pdf");
You typically do not need to lookup the program for registered types, and the program does not typically need to be in the PATH environment variable. Usually the command in the registry contains the full path. This is how the command for .kml files (Google Earth) looks (in my computer):
C:\Program Files\Google\Google Earth\googleearth.exe "%1"
Given that, you can safely just use Process.Start together with the document file names. Should it be that the file type is not registered you will invoke the default Windows behaviour for this (asking you which program to use, and so on).

Categories

Resources