Check if a file is real or a symbolic link - c#

Is there a way to tell using C# if a file is real or a symbolic link?
I've dug through the MSDN W32 docs, and can't find anything for checking this. I'm using CreateSymbolicLink from here, and it's working fine.

private bool IsSymbolic(string path)
{
FileInfo pathInfo = new FileInfo(path);
return pathInfo.Attributes.HasFlag(FileAttributes.ReparsePoint);
}

I have some source code for symlinks posted on my blog that will allow you to:
create symlinks
check whether a path is a symlink
retrieve the target of a symlink
It also contains NUnit test cases, that you may wish to extend.
The meaty bit is:
private static SafeFileHandle getFileHandle(string path)
{
return CreateFile(path, genericReadAccess, shareModeAll, IntPtr.Zero, openExisting,
fileFlagsForOpenReparsePointAndBackupSemantics, IntPtr.Zero);
}
public static string GetTarget(string path)
{
SymbolicLinkReparseData reparseDataBuffer;
using (SafeFileHandle fileHandle = getFileHandle(path))
{
if (fileHandle.IsInvalid)
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
int outBufferSize = Marshal.SizeOf(typeof(SymbolicLinkReparseData));
IntPtr outBuffer = IntPtr.Zero;
try
{
outBuffer = Marshal.AllocHGlobal(outBufferSize);
int bytesReturned;
bool success = DeviceIoControl(
fileHandle.DangerousGetHandle(), ioctlCommandGetReparsePoint, IntPtr.Zero, 0,
outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero);
fileHandle.Close();
if (!success)
{
if (((uint)Marshal.GetHRForLastWin32Error()) == pathNotAReparsePointError)
{
return null;
}
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
reparseDataBuffer = (SymbolicLinkReparseData)Marshal.PtrToStructure(
outBuffer, typeof(SymbolicLinkReparseData));
}
finally
{
Marshal.FreeHGlobal(outBuffer);
}
}
if (reparseDataBuffer.ReparseTag != symLinkTag)
{
return null;
}
string target = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer,
reparseDataBuffer.PrintNameOffset, reparseDataBuffer.PrintNameLength);
return target;
}
That is:
Open the file with CreateFile()
Call DeviceIoControl() to get the reparse point data (NOTE: it could be a junction point!)
Check out the returned data structure to inspect. The reparse tag will tell you if it is a junction point or symbolic link. This may be all you want to do.

Here is an example of differentiating files and directories from links to files and links to directories.
Links to either files or directories maintain their own attributes (creation date, permissions) separate from their targets.
File links can be deleted (e.g. using "del") without affecting the target file.
Directory links can be removed (e.g. "rmdir") without affecting the target directory. Take care when using "rd /s". This WILL remove the directory link target.
The key FileAttributes flag to check in both FileInfo and DirectoryInfo is FileAttributes.ReparsePoint.
static void Main( string[] args ) {
FileInfo file_info = new FileInfo(args[0]);
DirectoryInfo directory_info = new DirectoryInfo(args[0]);
bool is_file = file_info.Exists;
bool is_directory = directory_info.Exists;
if (is_file) {
Console.WriteLine(file_info.ToString() + " is a file");
if ( file_info.Attributes.HasFlag(FileAttributes.ReparsePoint) )
Console.WriteLine(args[0] + " is a Windows file link");
}
else if (is_directory) {
Console.WriteLine(directory_info.ToString() + " is a directory");
if ( directory_info.Attributes.HasFlag(FileAttributes.ReparsePoint) )
Console.WriteLine(args[0] + " is a Windows directory link");
}

It proves the above answers are not reliable.
Finally I got the right solution from MSDN:
To determine if a specified directory is a mounted folder, first call the GetFileAttributes function and inspect the FILE_ATTRIBUTE_REPARSE_POINT flag in the return value to see if the directory has an associated reparse point. If it does, use the FindFirstFile and FindNextFile functions to obtain the reparse tag in the dwReserved0 member of the WIN32_FIND_DATA structure. To determine if the reparse point is a mounted folder (and not some other form of reparse point), test whether the tag value equals the value IO_REPARSE_TAG_MOUNT_POINT. For more information, see Reparse Points.

Starting with .NET 6 you can use:
FileSystemInfo.LinkTarget Property
Property description:
Gets the target path of the link located in FullName, or null if this FileSystemInfo instance doesn't represent a link.
For example:
static bool IsSymbolicLink(string path)
{
FileInfo file = new FileInfo(path);
return file.LinkTarget != null;
}

GetFileInformationByHandle fills a BY_HANDLE_FILE_INFORMATION structure which has a field dwFileAttributes where bits are set with info about the file's attributes (details here). In particular, look at the bit at mask...:
FILE_ATTRIBUTE_REPARSE_POINT 1024 0x0400
A file or directory that has an
associated reparse point, or a file
that is a symbolic link.

According to this answer to Stack Overflow question Find out whether a file is a symbolic link in PowerShell, getting the System.IO.FileAttributes for the file (via File.GetAttributes), and testing for the ReparsePoint bit, works. If the bit is set, it is a symlink or a junction point. If not, it is a regular file (or hardlink).

The library MonoPosix provides API to check if a file is a symbolic link:
public bool IsSymlink(string filePath)
=> UnixFileSystemInfo.GetFileSystemEntry(filePath).IsSymbolicLink;

I know I am late to the party but found this discussion when researching same question
I found the below worked for me so thought I would post in case of use to anyone else
It works like this:-
var provider = ReparsePointFactory.Provider;
var link = provider.GetLink(#"c:\program files (x86)\common files\microsoft shared\vgx\vgx.dll");
MsgBox("Link Type: " + link.Type.ToString + " Link Target: " + link.Target + " Link Attributes: " + link.Attributes.ToString);
https://github.com/NCodeGroup/NCode.ReparsePoints https://www.nuget.org/packages/NCode.ReparsePoints/

Related

Which directory opens when ShowDialog() is performed [duplicate]

using (var openFileDialog1 = new OpenFileDialog())
{
openFileDialog1.Reset();
if (!string.IsNullOrEmpty(ExcelFilePath))
{
string fileName = Path.GetFileName(ExcelFilePath);
string fileExt = Path.GetExtension(ExcelFilePath);
//Avoid "you can't open this location using this program file" dialog
//if there is a file name in the path strip it )
if (!string.IsNullOrEmpty(fileName))
initialDirectory = Path.GetDirectoryName(ExcelFilePath);
//if not let it be
else
initialDirectory = ExcelFilePath;
openFileDialog1.InitialDirectory = initialDirectory;
}
else
openFileDialog1.InitialDirectory = "c:\\";
openFileDialog1.Filter = "Excel files (*.xls or *.xlsx)|*.xls;*.xlsx";
//openFileDialog1.Filter = "xls files (*.xls)|*.xls|xlsx files(*.xlsx)|.xlsx";
openFileDialog1.FilterIndex = 2;
openFileDialog1.RestoreDirectory = false;
openFileDialog1.CheckFileExists = true;
openFileDialog1.CheckPathExists = true;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
var browseSelectionMade = BrowseSelectionMade;
if (browseSelectionMade!=null)
browseSelectionMade(this, new DataEventArgs<string>(openFileDialog1.FileName));
}
}
Regardless of whether or not I set RestoreDirectory to true, I will always browse to the LAST used directory if my initial directory is set to a path that doesn't exist. Where is the last used directory saved by OpenFileDialog? And is there a way to override this behavior? (e.g. I always want to set it to C:\ if the initial directory doesn't exist?)
It seems like all you need to do is the following:
string path; // this is the path that you are checking.
if(Directory.Exists(path)) {
openFileDialog1.InitialDirectory = path;
} else {
openFileDialog1.InitialDirectory = #"C:\";
}
That is unless I'm missing something.
Where is the last used directory saved?
It is stored in the registry. The exact location depends on the Windows version, for Win7 it is HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32. A quick look with regedit ought to convince you that you don't want to mess with that.
The simple workaround is to provide a valid path. If the one you calculate isn't valid, Directory.Exists returns false, then provide a valid one. Like the Documents folder returned by Environment.GetFolderPath(). Then again, nothing wrong with the last used one either, the user will easily recognize it with good odds that it happens to be close to the desired one.
I don't think there is anything built in for that. Just check before you open the dialog:
if (!Directory.Exists(initialDirectory))
{
openFileDialog1.InitialDirectory = #"C:\";
}
Check to see if the ExcelFilePath exists, you check to see if it's null or empty, however if before your block you check to see if the directory exists, and if it doesn't reset the value to an empty string you should be golden.
(yes you'll need to apply your file name logic etc earlier) however once you've parsed all of that out, it's trivial to determine if the directory exits
if (!Directory.Exists(excelPath))
{
ExcelFilePath = String.Empty;
}
Also, to set the default extension you should set FilterIndex property instead of DefaultExt. see: https://stackoverflow.com/a/6104319/381082
Here's a good article on the OpenFileDialog in C#: http://www.c-sharpcorner.com/uploadfile/mahesh/openfiledialog-in-C-Sharp/
For future me
remember to do:
try
{
result = dialog.ShowDialog(Window);
}
catch
{
dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
result = dialog.ShowDialog(Window);
}
This helps in the situation when user opened file from location, that does not longer exists (ex. USB stick, mapped network drive) - ShowDialog throws exception if InitialDirectory is invalid.
Edit: After consulting with a more knowledgeable friend, perhaps the better solution is, in hindsight, obvious. Just store your own registry key in HKEY_CURRENT_USER\SOFTWARE\YourCompanyOrAppName\Whatevs or something similar (not sure on best practices, or which folders you have read/write access to, do your own research in that) and avoid this problem altogether. By simply letting the user navigate to where they want once, and then storing the path in the registry (as a normal string, and not a PIDL) and retrieving that path the next time. For reference, see the MSDN articles on the Registry and RegistryKey classes, and their example in the RegistryKey/Methods/SetValue article. Still, I'll leave this post as is, as a point of curiosity, or if someone has a very specific problem and needs this solution. As always, good luck!
For any poor soul wandering through here in the future, it seems I figured out how to find the last used directory. Just like stated previously, it's stored in the registry, more specifically in HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidlMRU\
Here is a set of folders for each file extension, including a "*" for unknown file extensions. I'll do this for txt files, change the path as needed. To access this path we make a RegistryKey and call OpenSubKey (BTW, full code below)
string RegistryPath = #"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU\\txt";
RegistryKey rk = Registry.CurrentUser.OpenSubKey(RegistryPath);
In here is a set of entries which all contain PIDLs (we'll get to that) of the last opened or saved items.
DO NOTE: the folder name OpenSavePidlMRU, i've seen called just OpenSaveMRU, and seems to be for versions older than win 10.
Also in here is an entry called "MRUListEx", MRU stands for "Most Recently Used". In this entry is an index of which item was... well, most recently used. So if I have 10 entries, named 0 to 9, and 9 was the last used, the first byte in MRUListEx will be 0x09. So for:
byte[] mrulistex = (byte[])rk.GetValue("MRUListEx");
byte Last = mrulistex[0];
Last will equal 0x09 (on my system)
Then we call GetValue again but for that entry
byte[] LastPathByteArray = (byte[])rk.GetValue(Last.ToString());
And here's where things get problematic, as this won't return a byte array where each byte is a character in our filepath, it returns what's known as a PIDL. While the byte array will seem to contain the path, in both char and wide char, it also contains a bunch of gibberish that can't be easily converted.
I won't pretend to understand it, but https://stackoverflow.com/a/4318663 provides a way to convert this to a string. (see code below)
string LastPath = GetPathFromPIDL(LastPathByteArray);
And we're done. PLEASE NOTE this doesn't necessarily represent a good solution, but I wasn't able to find much official documentation on this in my half hour of digging. And obviously this code doesn't check if the registry path is correct, if the registry keys exist, or do much error checking at all, but this does at least work.
using Microsoft.Win32; //for the registry class
using System.Runtime.InteropServices; //for converting the PIDL
//GetPathFromPIDL from matt.schechtman at https://stackoverflow.com/a/4318663
[DllImport("shell32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SHGetPathFromIDListW(IntPtr pidl, MarshalAs(UnmanagedType.LPTStr)] StringBuilder pszPath);
private string GetPathFromPIDL(byte[] byteCode)
{
//MAX_PATH = 260
StringBuilder builder = new StringBuilder(260);
IntPtr ptr = IntPtr.Zero;
GCHandle h0 = GCHandle.Alloc(byteCode, GCHandleType.Pinned);
try
{
ptr = h0.AddrOfPinnedObject();
}
finally
{
h0.Free();
}
SHGetPathFromIDListW(ptr, builder);
return builder.ToString();
}
public void OnClick_Button_OpenFile(object sender, RoutedEventArgs e)
{
string RegistryPath = #"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU\\txt";
RegistryKey rk = Registry.CurrentUser.OpenSubKey(RegistryPath);
byte[] mrulistex = (byte[])rk.GetValue("MRUListEx");
byte Last = mrulistex[0];
byte[] LastPathByteArray = (byte[])rk.GetValue(Last.ToString());
string LastPath = GetPathFromPIDL(LastPathByteArray);
// Configure open file dialog box
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();`
dlg.InitialDirectory = LastPath;
result = dlg.ShowDialog();
if (result == true)
{
string filename = dlg.FileName;
}
//etc etc, rest of your code
}
Good luck.
In case you're using file name stored in some string, it's better to use Path to cut the file name (on my W10 the open dialog doesn't open in initial directory, if I supply just file name):
if (!System.IO.Directory.Exists(filename))
{
openDlg.InitialDirectory =
System.IO.Path.GetDirectoryName(filename);
}

Search parent directory from DirectoryInfo

Is there any way to search for the particular parent directory?
I know I can get the parent of the directory using this
Directory.GetParent(Directory.GetCurrentDirectory()).FullName
But this returns immediate parent, is there any way I can kind of search for particular parent in the directory hierarchy of the path?
EDIT
What I am trying to achieve is, say if I have current directory like this
C:/Project/Source/Dev/Database
So I want to reach to the directory Source
I know I can reach it by calling GetParent method twice but this I don't think is the right way to do it because what if in future my file current directory changes and it goes further down.
So I want some full proof way where I can directly find the path of the directory Source no matter how deep I am in the current directory because that is for sure that I will be inside directory Source
So something like
FindParent('Source')
You can try something like this (for loop):
private static IEnumerable<String> ParentDirectories(string directory = null) {
for (string dir = null == directory ? Directory.GetCurrentDirectory() : directory;
dir != null;
dir = Directory.GetParent(dir)?.FullName)
yield return dir;
}
Demo:
var demo = string.Join(Environment.NewLine,
ParentDirectories(#"C:/Project/Source/Dev/Database"));
Console.Write(demo);
Outcome:
C:/Project/Source/Dev/Database // initial directory
C:\Project\Source\Dev // and its all parents
C:\Project\Source
C:\Project
C:\
If you don't want to include directory itself, add .Skip(1):
var demo = string.Join(Environment.NewLine,
ParentDirectories(#"C:/Project/Source/Dev/Database").Skip(1));
Finally, if you want to find out a parent directory which ends by Source:
string dirName = "Source";
string myParent = ParentDirectories(#"C:/Project/Source/Dev/Database")
.FirstOrDefault(dir => string.Equals(
dirName,
new DirectoryInfo(dir).Name,
StringComparison.OrdinalIgnoreCase));
Console.Write(myParent);
Outcome:
C:\Project\Source
Directory.GetCurrentDirectory() returns a string that represents the full absolute path of the current directory.
If you want to get the path of a specific parent directory, you can simply use substring:
var path = Directory.GetCurrentDirectory(); // Suppose C:/Project/Source/Dev/Database
var sourceDir = new string[] {Path.DirectorySeparatorChar + "Source" + Path.DirectorySeparatorChar,
Path.AltDirectorySeparatorChar + "Source" + Path.AltDirectorySeparatorChar};
var sourcePath = path.IndexOf(sourceDir[0], StringComparison.OrdinalIgnoreCase) > -1 ?
path.Substring(0, path.IndexOf(sourceDir[0]) + sourceDir[0].Length) :
path.IndexOf(sourceDir[1], StringComparison.OrdinalIgnoreCase) > -1 ?
path.Substring(0, path.IndexOf(sourceDir[1]) + sourceDir[1].Length) :
null;
I've used Path.DirectorySeparatorChar and Path.AltDirectorySeparatorChar as separators so that the code will work the same on each platform.
Two possibles without anymore additional information about your situation:
DirectoryInfo.GetDirectories would get you all the child directories of the Current directory so you would need to switch your current to the root + 1
System.IO.Path.Combine(myPath, "..") which would act more like cd ..
Actually I thought there is an already existing way or function but if I have to write it myself then this solution I wrote worked for me
private static string GetParent(string directoryPath, string parent)
{
DirectoryInfo directory = new DirectoryInfo(directoryPath);
while (directory.Parent!=null)
{
if (directory.Parent.Name.ToLower() == parent.ToLower())
{
return directory.Parent.FullName;
}
directory = directory.Parent;
}
return null;
}
When traversing any kind of sequence (a sequence of numbers, a sequence of directories, a sequence of nodes in a graph), you have the option of employing recursion.
Directory structures (when ignoring links of any kind) are a subset of the data structure known as a Directed Acyclic Graph (or DAG) - this subset typically doesn't rejoin and fans out forever (so long as the operating system allows for the given depth of your nested directories). We typically refer to this as a Tree structure.
When put in this light, its probably no surprise to most that the concept of recursion surfaces because its quite common for programmers to apply recursion to traverse down a tree. However, whether you traverse up or down a tree is simply an implementation detail.
There is no reason you cannot use recursion to also traverse up a tree.
Here is a simple console app (written using .net6 conventions & C#10 syntax) that you can study, and then perhaps apply to solve future problems.
using System.Runtime.InteropServices; // For the [Optional] attribute
var dir = Directory.GetCurrentDirectory();
var file = Directory.GetFiles(dir).First();
var projFile = DirectoryRecursor.RecurseUpwardsUntilFileIsFoundWith(".csproj", file, 5);
Console.WriteLine(projFile);
public static class DirectoryRecursor
{
public static FileInfo? RecurseUpwardsUntilFileIsFoundWith(
string stringToMatch,
string sourceFile,
[Optional] int? maxParentDirLevel)
{
if (maxParentDirLevel is not null && maxParentDirLevel == 0) return null; // try and stave off disaster
var dirName = Path.GetDirectoryName(sourceFile);
if (dirName is null) return null;
var csprojFile = Directory.GetFiles(dirName).Where(x => x.EndsWith(stringToMatch)).SingleOrDefault();
if (csprojFile is null)
{
if (ThereIsAParentDirectory(new DirectoryInfo(sourceFile), out var parentDir))
{
return RecurseUpwardsUntilFileIsFoundWith(stringToMatch, parentDir.FullName, maxParentDirLevel - 1);
}
else
{
return null;
}
}
return new FileInfo(csprojFile);
}
public static bool ThereIsAParentDirectory(DirectoryInfo dir, out DirectoryInfo parentDir)
{
if (dir.Parent is not null)
{
parentDir = dir.Parent;
return dir.Parent.Exists;
}
parentDir = dir;
return false;
}
}
A quick side note:
An example of applied recursion both up and down a tree (which I appologize is not currently open source), is the conversation builder over at www.palavyr.com. This conversation tree is implemented as a double linked list to facilitate recursion both up and down the conversation tree. I'm planning on producing a technical blog post explaining this implementation - I'll update this response with that once I've assembled it.

Getting Downloads Folder in C#? [duplicate]

This question already has answers here:
How to programmatically derive Windows Downloads folder "%USERPROFILE%/Downloads"?
(11 answers)
Closed 5 years ago.
I have made some code that will search directories and display files in a listbox.
DirectoryInfo dinfo2 = new DirectoryInfo(#"C:\Users\Hunter\Downloads");
FileInfo[] Files2 = dinfo2.GetFiles("*.sto");
foreach (FileInfo file2 in Files2)
{
listBox1.Items.Add(file2.Name);
}
However, where it says Users\Hunter - well, when people get my software, their name is not Hunter. So how can I automatically detect the user's Downloads folder?
I have tried this:
string path = Environment.SpecialFolder.UserProfile + #"\Downloads";
DirectoryInfo dinfo2 = new DirectoryInfo(Environment.SpecialFolder.UserProfile + path);
FileInfo[] Files2 = dinfo2.GetFiles("*.sto");
foreach (FileInfo file2 in Files2)
{
listBox1.Items.Add(file2.Name);
}
I get an error though.
The Downloads folder is a so called "known" folder, together with Documents, Videos, and others.
Do NOT:
combine hardcoded path segments to retrieve known folder paths
assume known folders are children of the user folder
abuse a long deprecated registry key storing outdated paths
Known folders can be redirected anywhere in their property sheets. I've gone into more detail on this several years ago in my CodeProject article.
Do:
use the WinAPI method SHGetKnownFolderPath as it is the intended and only correct method to retrieve those paths.
You can p/invoke it as follows (I've provided only a few GUIDs which cover the new user folders):
public enum KnownFolder
{
Contacts,
Downloads,
Favorites,
Links,
SavedGames,
SavedSearches
}
public static class KnownFolders
{
private static readonly Dictionary<KnownFolder, Guid> _guids = new()
{
[KnownFolder.Contacts] = new("56784854-C6CB-462B-8169-88E350ACB882"),
[KnownFolder.Downloads] = new("374DE290-123F-4565-9164-39C4925E467B"),
[KnownFolder.Favorites] = new("1777F761-68AD-4D8A-87BD-30B759FA33DD"),
[KnownFolder.Links] = new("BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968"),
[KnownFolder.SavedGames] = new("4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4"),
[KnownFolder.SavedSearches] = new("7D1D3A04-DEBB-4115-95CF-2F29DA2920DA")
};
public static string GetPath(KnownFolder knownFolder)
{
return SHGetKnownFolderPath(_guids[knownFolder], 0);
}
[DllImport("shell32",
CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
private static extern string SHGetKnownFolderPath(
[MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags,
nint hToken = 0);
}
Here's an example of retrieving the path of the Downloads folder:
string downloadsPath = KnownFolders.GetPath(KnownFolder.Downloads);
Console.WriteLine($"Downloads folder path: {downloadsPath}");
NuGet Package
If you don't want to p/invoke yourself, have a look at my NuGet package (note that the usage is different, please check its README).
The easiest way is:
Process.Start("shell:Downloads");
If you only need to get the current user's download folder path, you can use this:
I extracted it from #PacMani 's code.
// using Microsoft.Win32;
string GetDownloadFolderPath()
{
return Registry.GetValue(#"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders", "{374DE290-123F-4565-9164-39C4925E467B}", String.Empty).ToString();
}
Note:
SHGetKnownFolderPath will return the WRONG value if you changed the download-folder.
The only thing that will ever return you the correct value is reading the shell-folders registry-key 374DE290-123F-4565-9164-39C4925E467B on Windows.
Now you can either use the "!Do not use this registry key", or you can get the wrong value.
You decide which is better for you.
Cross-Platform version:
public static string GetHomePath()
{
// Not in .NET 2.0
// System.Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
if (System.Environment.OSVersion.Platform == System.PlatformID.Unix)
return System.Environment.GetEnvironmentVariable("HOME");
return System.Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%");
}
public static string GetDownloadFolderPath()
{
if (System.Environment.OSVersion.Platform == System.PlatformID.Unix)
{
string pathDownload = System.IO.Path.Combine(GetHomePath(), "Downloads");
return pathDownload;
}
return System.Convert.ToString(
Microsoft.Win32.Registry.GetValue(
#"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
,"{374DE290-123F-4565-9164-39C4925E467B}"
,String.Empty
)
);
}
string download = Environment.GetEnvironmentVariable("USERPROFILE")+#"\"+"Downloads";
http://msdn.microsoft.com/en-us//library/system.environment.specialfolder.aspx
There are the variables with the path to some special folders.
typically, your software shall have a configurable variable that stores the user's download folder, which can be assigned by the user, and provide a default value when not set. You can store the value in app config file or the registry.
Then in your code read the value from where it's stored.

System.IO.File.Copy() produces System.IO.IOException: The specified network name is no longer available

I have a windows service (C# .Net 3.5) that grabs data from a network share and does a copy to the host of the service.
The size of the data copied ranges from 50KB to 750MB, and the number of files copied varies. In perhaps 20% of the copies I am getting System.IO.IOException: The specified network name is no longer available.
My google-fu is failing to turn up an answer as to what might cause this during a File.Copy. Has anyone seen/solved this before?
Here is the recursive method that does the copy. The exception occurs on line File.Copy(fromFile, toFile, overwrite);
private static int RecursiveCopyDirectory(string from, string to, bool merge, bool overwrite, int depth)
{
depth++;
if (!from.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
to += Path.DirectorySeparatorChar;
}
if (!to.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
to += Path.DirectorySeparatorChar;
}
System.Diagnostics.Debug.WriteLine(string.Format("RecursiveDirectoryCopy( {0}, {1}, {2} )", from, to, merge));
if (Directory.Exists(to))
{
if (!merge)
{
return (int)EventEnum.FileSystemError_DirectoryAlreadyExists;
}
}
else
{
Directory.CreateDirectory(to);
}
string[] directories = Directory.GetDirectories(from);
foreach (string fromDirectory in directories)
{
string [] fromDirectoryComponents = fromDirectory.Split(Path.DirectorySeparatorChar);
string toDirectory = to + fromDirectoryComponents[fromDirectoryComponents.Length - 1];
RecursiveCopyDirectory(fromDirectory, toDirectory, merge, overwrite, depth);
}
string[] files = Directory.GetFiles(from);
foreach (string fromFile in files)
{
string fileName = Path.GetFileName(fromFile);
//System.Diagnostics.Debug.WriteLine(string.Format("Name: {0}", to + fileName));
string toFile = to + fileName;
File.Copy(fromFile, toFile, overwrite);
}
return (int)EventEnum.GeneralSuccess;
}
File.Copy() opens up underline streams. You might have lost connection while File.Copy() is in progress. So, it can't flush and close the stream.
One possibility to recover from this, is to use the FileStream class and
call Win32 API CloseHandle when such exception occurs, doing so will release
the OS file handle so you can re-open the file.
[ DllImport("Kernel32") ]
public static extern bool CloseHandle(IntPtr handle);
FileStream fs;
try {
...
}
catch(IOException)
{
// If resource no longer available, or unable to write to.....
if(...)
CloseHandle(fs.Handle);
}
Also, MSDN recommends not to rely on overwrite. Try deleting existing file and creating new one when copying them.
File.Copy(..., ..., TRUE) does not work properly.
Be very careful with this method, as the Overwrite = True does NOT work properly.
I had an existing destination file that had some information inside it that was somehow preserved and carried over to the source file that was supposed to copy over it. This should be impossible, but I confirmed it for myself.
The error seems to indicate that the network connection is lost partway through and probably isn't to do with the code at all. If the same folder copy succeeds sometimes and fails other times then this would back up that it's not the code to blame and must be a resource access issue.
It turns out that the customer that was using this software was running two instances of it simultaneously, against the same data set. Once the redundant instance was stopped it resolved the error. Thanks everyone who answered.

How to determine if a directory path was SUBST'd

How can I figure out if a file is in a folder that has been SUBST'ed or is located in a user folder using C#?
I think you need to P/Invoke QueryDosDevice() for the drive letter. Subst drives will return a symbolic link, similar to \??\C:\blah. The \??\ prefix indicates it is substituted, the rest gives you the drive+directory.
This is the code I use to get the information if a path is substed:
(Some parts come from pinvoke)
using System.Runtime.InteropServices;
[DllImport("kernel32.dll", SetLastError=true)]
static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax);
public static bool IsSubstedPath(string path, out string realPath)
{
StringBuilder pathInformation = new StringBuilder(250);
string driveLetter = null;
uint winApiResult = 0;
realPath = null;
try
{
// Get the drive letter of the path
driveLetter = Path.GetPathRoot(path).Replace("\\", "");
}
catch (ArgumentException)
{
return false;
//<------------------
}
winApiResult = QueryDosDevice(driveLetter, pathInformation, 250);
if(winApiResult == 0)
{
int lastWinError = Marshal.GetLastWin32Error(); // here is the reason why it fails - not used at the moment!
return false;
//<-----------------
}
// If drive is substed, the result will be in the format of "\??\C:\RealPath\".
if (pathInformation.ToString().StartsWith("\\??\\"))
{
// Strip the \??\ prefix.
string realRoot = pathInformation.ToString().Remove(0, 4);
// add backshlash if not present
realRoot += pathInformation.ToString().EndsWith(#"\") ? "" : #"\";
//Combine the paths.
realPath = Path.Combine(realRoot, path.Replace(Path.GetPathRoot(path), ""));
return true;
//<--------------
}
realPath = path;
return false;
}
I think you have a few choices --
Via System.Management classes:
http://briancaos.wordpress.com/2009/03/05/get-local-path-from-unc-path/
Or
Via P/Invoking this MAPI function:
ScUNCFromLocalPath
http://msdn.microsoft.com/en-us/library/cc842520.aspx
If SUBST is run without parameters it produces a listing of all current substitutions. Get the list, and check your directory against the list.
There is also the issue of mapping a volume to a directory. I have never attempted to detect these, but the mount point directories do show up differently than regular directories, so they must have a different attribute of some kind, and that could be detected.

Categories