I'm trying to detect if the directory exists, but in this particular situation my directory is a network location.
I used VB.NET's My.Computer.FileSystem.DirectoryExists(PATH) and the more general System.IO.Directory.Exists(PATH), and in both cases, the system response is false.
I checked and the PATH exists, I can view it in MyComputer Folder.
If I debug the program, and watch the My.Computer.FileSystem.Drives variable, the network location does not appear in that list.
UPDATE: I checked and in Windows XP the Response is True, but not in Windows 7.
UPDATE2: I tested both proposed solutions but I still have the same problem, on the image below you will see that I can access using Explorer but my program cannot.
The GetUNCPath function returns a valid path (no errors), but Directory.Exists stil returns false.
I also tried with the UNC path "\\Server\Images"; same result.
UPDATE3: If I cannot link with a network drive, how can I link to UNC path directly?. I discovered that if i run VS in normal mode, it works, but my software must run in administrator mode. So, there is any way to check the existence of a network directory as administrator?
If UAC is turned on, mapped network drives only exist "by default" in the session they are mapped: Normal or elevated. If you map a network drive from explorer, then run VS as admin, the drive will not be there.
You need to enable what MS calls "linked connections":
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System: EnableLinkedConnections (REG_DWORD) = 0x1
Background Information about "two logon sessions" with UAC: http://support.microsoft.com/kb/937624/en-us
When you use System.IO.Directory.Exists, it only lets you know that it couldn't find the directory, but this could be because the directory doesn't actually exist or because the user doesn't have sufficient access rights to the directory.
In order to resolve this, we add a secondary test after Directory.Exists fails to obtain the real reason for the directory's absence and we have wrapped this into a global method that is used in place of the standard Directory.Exists method:
''' <summary>
''' This method tests to ensure that a directory actually does exist. If it does not, the reason for its
''' absence will attempt to be determined and returned. The standard Directory.Exists does not raise
''' any exceptions, which makes it impossible to determine why the request fails.
''' </summary>
''' <param name="sDirectory"></param>
''' <param name="sError"></param>
''' <param name="fActuallyDoesntExist">This is set to true when an error is not encountered while trying to verify the directory's existence. This means that
''' we have access to the location the directory is supposed to be, but it simply doesn't exist. If this is false and the directory doesn't exist, then
''' this means that an error, such as a security error, was encountered while trying to verify the directory's existence.</param>
Public Function DirectoryExists(ByVal sDirectory As String, ByRef sError As String, Optional ByRef fActuallyDoesntExist As Boolean = False) As Boolean
' Exceptions are partially handled by the caller
If Not IO.Directory.Exists(sDirectory) Then
Try
Dim dtCreated As Date
' Attempt to retrieve the creation time for the directory.
' This will usually throw an exception with the complaint (such as user logon failure)
dtCreated = Directory.GetCreationTime(sDirectory)
' Indicate that the directory really doesn't exist
fActuallyDoesntExist = True
' If an exception does not get thrown, the time that is returned is actually for the parent directory,
' so there is no issue accessing the folder, it just doesn't exist.
sError = "The directory does not exist"
Catch theException As Exception
' Let the caller know the error that was encountered
sError = theException.Message
End Try
Return False
Else
Return True
End If
End Function
public static class MappedDriveResolver
{
[DllImport("mpr.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int WNetGetConnection([MarshalAs(UnmanagedType.LPTStr)] string localName, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder remoteName, ref int length);
public static string GetUNCPath(string originalPath)
{
StringBuilder sb = new StringBuilder(512);
int size = sb.Capacity;
// look for the {LETTER}: combination ...
if (originalPath.Length > 2 && originalPath[1] == ':')
{
// don't use char.IsLetter here - as that can be misleading
// the only valid drive letters are a-z && A-Z.
char c = originalPath[0];
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
{
int error = WNetGetConnection(originalPath.Substring(0, 2), sb, ref size);
if (error == 0)
{
DirectoryInfo dir = new DirectoryInfo(originalPath);
string path = Path.GetFullPath(originalPath).Substring(Path.GetPathRoot(originalPath).Length);
return Path.Combine(sb.ToString().TrimEnd(), path);
}
}
}
return originalPath;
}
}
To use it, pass in a Network folder path, convert to UNC folder path and see if the folder exists:
File.Exists(MappedDriveResolver.GetUNCPath(filePath));
Edit:
I saw your second edit and the only difference (in my Windows7) when I view a network drive I see Computer > Images (\\xyzServer). Is your PC's name Equipo? is that team in spanish? is that your PC? I tried to reproduce your problem but it works for me:
Adding to this, I needed to do an 'Exists' check on network shares that could listed, but the account did not have permission to access, so Directory.Exists would return False.
Various solutions posted did not work for me, so here is my own:
public static bool DirectoryVisible(string path)
{
try
{
Directory.GetAccessControl(path);
return true;
}
catch (UnauthorizedAccessException)
{
return true;
}
catch
{
return false;
}
}
Related
I've reached the end of my rope with this one. I have some C# code that's trying to resolve paths containing drive letters mapped to network drives (e.g. "S:\") to UNC paths (e.g. "\\server\share\"). I'm doing this using WNetGetUniversalName via P/Invoke, but on some of my colleagues' machines (not mine, annoyingly) I'm seeing the function consistently fail with the error code ERROR_NOT_SUPPORTED.
Here's the code:
[DllImport("mpr.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.U4)]
static extern int WNetGetUniversalName(
string lpLocalPath,
[MarshalAs(UnmanagedType.U4)] int dwInfoLevel,
IntPtr lpBuffer,
[MarshalAs(UnmanagedType.U4)] ref int lpBufferSize);
/// <summary>
/// Gets the UNC path for the path passed in.
/// </summary>
/// <param name="path">The path for which we want the UNC path.</param>
/// <returns>The UNC path. Returns empty string if an error has occurred. </returns>
public static string GetUniversalPath(string path)
{
const int UNIVERSAL_NAME_INFO_LEVEL = 0x00000001;
const int ERROR_MORE_DATA = 234;
const int NOERROR = 0;
string retVal = null;
// Pointer to the memory buffer to hold the result.
IntPtr buffer = IntPtr.Zero;
try
{
// First, call WNetGetUniversalName to get the size.
// Passing (IntPtr)IntPtr.Size as the third parameter because WNetGetUniversalName doesn't
// like NULL, and IntPtr.Size will always be a properly-aligned (if not actually valid)
// IntPtr value.
int size = 0;
int apiRetVal = WNetGetUniversalName(path, UNIVERSAL_NAME_INFO_LEVEL, (IntPtr)IntPtr.Size, ref size);
if (apiRetVal == ERROR_MORE_DATA)
{
// Allocate the memory.
buffer = Marshal.AllocCoTaskMem(size);
// Now make the call.
apiRetVal = WNetGetUniversalName(path, UNIVERSAL_NAME_INFO_LEVEL, buffer, ref size);
if (apiRetVal == NOERROR)
{
// Now get the string. It's all in the same buffer, but
// the pointer is first, so offset the pointer by IntPtr.Size
// and pass to PtrToStringAnsi.
retVal = Marshal.PtrToStringAuto(new IntPtr(buffer.ToInt64() + IntPtr.Size), size);
retVal = retVal.Substring(0, retVal.IndexOf('\0'));
}
}
}
catch
{
// I know swallowing exceptions is nasty...
retVal = "";
}
finally
{
Marshal.FreeCoTaskMem(buffer);
}
return retVal;
}
I'm seeing the ERROR_NOT_SUPPORTED return value the first time WNetGetUniversalName is called. Like I said, it works every time on my machine but it always seems to fail on others.
UPDATE: I should probably add that in all cases, the operating system in use is Windows 7 Enterprise x64 with Service Pack 1.
UPDATE 2: I should have been clearer about the reason for my confusion. The documentation states that ERROR_NOT_SUPPORTED means that no network providers support UNC names. However, UNC names work fine on every machine where I've seen this problem. I'm wondering if anyone has seen this before and/or might be able to provide other possible explanations for ERROR_NOT_SUPPORTED being returned.
The documentation says that this return value means:
The dwInfoLevel parameter is set to UNIVERSAL_NAME_INFO_LEVEL, but the network provider does not support UNC names. (None of the network providers support this function.)
There's not much more to add to that.
Got it! After a bit of digging, I found the source of the problem. The machines where the call didn't work had a network provider called "Pismo File Mount" installed, apparently as part of something called the Pismo File Mount Audit Package.
I found the list of installed network providers as follows:
Open Network and Sharing Center.
Click "Change adapter settings" in the pane on the left.
Press ALT to bring up the menu bar, then select "Advanced Settings..." from the "Advanced" menu.
Go to the "Provider Order" tab.
This is what I saw:
I was able to resolve the problem by either:
Moving "Pismo File Mount" down below "Microsoft Windows Network" in the list of providers.
Uninstalling the Pismo File Mount Audit Package.
I still don't understand the problem as well as I'd like, but at least we have something to tell customers if they run into it.
I want to check if a folder exists and if not then create it. But I don't know if the path supplied will even valid. When the path is not valid the following happens.
string path = "this is an invalid path";
if (!Directory.Exists(path))
Directory.CreateDirectory(path); //Exception thrown here
If you supply an invalid path, it will throw a DirectoryNotFoundException exception.
How can I stop this exception from occurring? I don't want to use a try-catch. I want to detect that this exception will occur even before the exception happens.
Use Directory.Exists method to check if a folder exists
if(Directory.Exists(path))
{
//Directory exists
}
else
{
// doesn't exist
}
Remember to include System.IO;
The explanation for the failure of your code is that the path is invalid. The documentation says:
DirectoryNotFoundException
The specified path is invalid (for example, it is on an
unmapped drive).
Trying to predict in advance whether or not a directory can be created is a devil of a job. You'd need to account for security, OS name rules and limits, file system name rules and limits, and whether or not drives are mapped. And probably lots more concerns. I would not contemplate re-implementing what the system provides for free.
In any case, whilst you can call Directory.Exists, you do still need to allow for an exception being thrown. If the file system changes between the call to Directory.Exists and the subsequent call to Directory.CreateDirectory, then an exception will be raised. For example, if another process creates the directory that you are trying to create. Granted, this is a rather unlikely event, but it's perfectly possible.
In summary, the best option, by a distance, is to catch the exception. As the well known saying goes, it's better to ask for forgiveness than to ask for permission.
Why don't you want to catch (specific) Exceptions? It is considered as a good practice... anyway, these are my solutions without try/catch:
solution:
string path = "C:\test";
if (!Directory.Exists(path) && path.IndexOfAny(Path.GetInvalidFileNameChars()) == -1)
{
Directory.CreateDirectory(path);
}
solution:
string path = "C:\test";
var canCreate = true;
foreach (var c in path.Where(Path.GetInvalidFileNameChars().Contains))
{
canCreate = false;
}
if (canCreate && !Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
solution:
if (path.Any(c => Path.GetInvalidFileNameChars().Contains(c)) && !Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
Please, be aware that this code can still fail... e.g. think about SecurityExeption (do you have the credentionals to create a directory there?!?
Also be aware there is still a (little) chance that the directory has been created (by another process/thread/...) after your test with Exists() but before your call of CreateDirectory(). These are two calls and they are not atomic (together) when querying/modifying the file system.
You can also use Path.GetInvalidFileNameChars() to check whether the directory name supplied is valid for a new directory name:
// Directory.Exists will return false because path is not even valid
var path = "1:1 comparison?";
var firstBad = Path.GetInvalidFileNameChars().Cast<char?>()
.FirstOrDefault(c => path.Contains(c.Value));
if (firstBad != null)
Console.WriteLine("Char '{0}' is invalid for a directory name", firstBad.Value);
try this
string path = "this is an invalid path";
if (Path.IsPathRooted(path)
{
Directory.CreateDirectory(path);
}
I think the simplest way to test if a path is valid and the file exists without raising an exception in C# is to call the unmanaged function PathFileExists directly in shlwapi.dll.
[DllImport("shlwapi.dll", EntryPoint = "PathFileExistsW", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool PathFileExists([MarshalAs(UnmanagedType.LPTStr)]string pszPath);
Note that as David writes in his answer, you might nevertheless get an exception when you try to create the directory.
You can use a single if-else statement
if(!Directory.Exists(path))
Directory.CreateDirectory(path)
else
//Show user a custom error message
I am trying to get process path by pid but I'm getting Win32Exception (access id denied).
The code looks like this:
string path = Process.GetProcessById(pid).MainModule.FileName
I have tried using OpenProcess with GetModuleFileNameEx but OpenProcess is returning 0. I even tried enabling SeDebugPrivilege according to C# – How to enable SeDebugPrivilege but it didn't help.
The above code works for most of the processes but throws error for SynTPHelper.exe (Synaptics Pointing Device Helper) The application is running under the same username as my code. Both, my application and the process run in 64 bit.
Is it possible to retrieve the path without running my application as an administrator?
Edit
Task Manager is able to 'open file location' even though I'm not running it as an administrator.
Finally I managed to solve it. As it turned out there is new function in Vista and above for getting process path and new process access (PROCESS_QUERY_LIMITED_INFORMATION):
QueryFullProcessImageName
Here is the code that works from non-elevated process:
private static string GetExecutablePathAboveVista(UIntPtr dwProcessId)
{
StringBuilder buffer = new StringBuilder(1024);
IntPtr hprocess = OpenProcess(ProcessAccessFlags.PROCESS_QUERY_LIMITED_INFORMATION, false, dwProcessId);
if (hprocess != IntPtr.Zero)
{
try
{
int size = buffer.Capacity;
if (QueryFullProcessImageName(hprocess, 0, buff, out size))
{
return buffer.ToString();
}
}
finally
{
CloseHandle(hprocess);
}
}
return string.Empty;
}
Well, it is certainly not unheard of for services to remove access rights so that even an administrator cannot open the process. A service has enough privileges to do so, DRM components like audiodg.exe readily do so. A mouse pad helper doesn't strike me as something that would require such protection. But what the hey, why would anybody ever need to mess with a mouse pad helper?
If I try to access this system variable from the Run... dialog, Windows tells me the directory doesn't exist. Some system variables, like %SYSTEMROOT% and %USERPROFILE%, do work. Consequently, if I try to use a supposedly nonexistent variable like %DEFAULTUSERPROFILE% or %PROFILESFOLDER% in C#, I get nothing in return. Is there something special I need to do to get access to these variables?
Have you tried %ALLUSERSPROFILE%?
I need to point to
C:\Users\Default\AppData.
Are you sure? Be aware that this folder is used to populate the inital AppData directory for each new user added to the system.
If you want the actual shared application data directory in .NET, it's this:
String commonAppData = Environment.GetFolderPath(Environment.SpecialFolders.CommonApplicationData)
My suggestion is to retreive that value directly from the registry - in case you can't expand it:
public static string GetDefaultUserProfilePath() {
string path = System.Environment.GetEnvironmentVariable("DEFAULTUSERPROFILE") ?? string.Empty;
if (path.Length == 0) {
using (Microsoft.Win32.RegistryKey key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(#"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList")) {
path = (string)key.GetValue("Default", string.Empty);
}
}
return path;
}
You mention C# - you can't use environment variables inside C# path strings, you need to replace them using System.Environment.
System.Environment.GetEnvironmentalVariable("USERPROFILE");
I haven't seen %DefaultUserProfile% before - should it point to the first username that was installed?
Call SHGetFolderLocation with CSIDL_PROFILE and -1 as the token parameter
In my C# .NET 2.0 application I'm accessing network paths, and I'd like to be able to tell the difference between paths that don't exist, and paths which do exist but for which I don't have access rights. I tried doing the following:
try
{
string[] contents = Directory.GetFileSystemEntries( path );
}
catch( Exception e )
{
if( e is DirectoryNotFoundException )
MessageBox.Show( "Path not found" );
else
MessageBox.Show( "Access denied" );
}
This works fine for local paths, but for network paths, the exception is always System.IO.IOException, regardless of the reason for the error. The exception Message field shows a different message depending on whether the path exists or not, so clearly the information is available at some point, but I can't get to it. Is there a way to differentiate between "path not found" and "access denied" for network paths?
Edit: So, in case anyone else wants the quick solution to this, as suggested by henrik, and incorporating peSHIr's advice, here is what you can do:
try
{
// Issue this call just to find out whether the path exists
// We don't care about the result
string[] contents = Directory.GetFileSystemEntries( path );
// If we get this far then the path exists.
}
catch( IOException e )
{
uint error = (uint)Marshal.GetHRForException( e );
if( error == (uint)0x80070041 ) // ERROR_NETWORK_ACCESS_DENIED
{
// The poor deluded user doesn't have access rights
this.SuperProprietaryTechniqueForGettingAccessRights();
}
else
{
// Hah! The idiot user specified a path that doesn't exist!
// Chastise them severely, like all good GUI should.
MessageBox.Show( "NO! BAD USER!" );
}
}
catch
{
// Swallow all other types of exception - we only made the call
// to find out whether the path exists.
}
First, I would not catch (Exception), but do something like this:
try {
string[] contents = Directory.GetFileSystemEntries(path);
}
catch(DirectoryNotFoundException)
{
MessageBox.Show("Path not found");
}
catch(IOException)
{
MessageBox.Show("Could not access path");
}
But the real question is: why would you actually need to know the difference, if all you do is show a message box to the user with a generic error message?
Also, unfortunately, the path may exist and you don't have access, and the result is that the path appears non-existent to your account. Many companies modify the special privs on a folder to remove "intermediary" folders from visibility, but they are still present.
For example, consider the path \srv\test1\test2\test3\
If you modify the advanced security for the folder named "test2" above, you can prevent users from navigating into it. Attempts to open the folder will result in a not found exception, and viewing the parent folder "test1" will not show the folder as present. However, if you provide the full path above and have sufficient privileges on the folder named "test3" then the folder will open.
Just something to consider, for all intents and purposes the folder doesn't exist for the user, however, if you offer the ability for a user to specify a full path then you should keep the above in mind (e.g. allow users to reach paths by full name, because it's possible they lack privs to view/list/read/see an intermediary, and the error returned doesn't indicate that it's a security restriction, so preventing access to the full path because of an intermediary appeared non-existent coudl be considered bugged app logic.)
Hope that helps.
You can call Marshal.GetHRForException to get more detailed information as in:
if (Marshal.GetHRForException(e) == unchecked((int)0x800704cf)) // ERROR_NETWORK_UNREACHABLE
See WinError.h for error codes.