Have a network location that shows paths in the 8.3 short format. I need to convert these to long format for use on UNIX.
Since its a network location I need it needs to work with UNC paths.
Any ideas?
The answer to converting short to long is just a Google search away. This will work for UNC paths only on Windows Vista and up (and possible XP w/ some service packs).
using System;
using System.Runtime.InteropServices;
using System.Text;
public class _Main
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern int GetLongPathName(
string path,
StringBuilder longPath,
int longPathLength
);
public static void Main()
{
StringBuilder longPath = new StringBuilder(255);
GetLongPathName(#"\\?\UNC\server\d$\MYTEMP~1\RESOUR~1\sql.txt", longPath, longPath.Capacity);
Console.WriteLine(longPath.ToString());
}
}
There are several functions to convert a 8.3 name to a fullname. I prefer the FileInfo class.
string shortName = #"c:\PROGRA~1";
string longName = new FileInfo(shortName).FullName;
// ==> longname: c:\Program Files
this works even with UNC paths:
string shortName = #"\\MyComputer\c$\PROGRA~1";
string longName = new FileInfo(shortName).FullName;
// ==> longname: \\MyComputer\c$\Program Files
// Use "C:>dir /X" to see the short names
string dosPath = #"C:\Windows\WINDOW~1.LOG";
string winPath = Path.GetFullPath(dosPath);
Related
I need to access contents in the folder %AppData%\Roaming\Microsoft.
This usually works fine by doing the following:
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Microsoft");
The problem is that now the explorer lets you change the location of %AppData% by right clicking the Roaming folder and setting the location to some other place. However, this doesn't change the location of the Microsoft folder, which will remain in the original %AppData%.
I've thought about doing something like this:
string roaming = "C:\Users\" + Environment.UserName + #"\AppData\Roaming";
Though this just looks bad and looks like it could break easily.
Any suggestions?
I don't know if .NET can do it but WinAPI can. PInvoke SHGetFolderPath with the SHGFP_TYPE_DEFAULT flag:
using System;
using System.Runtime.InteropServices;
namespace Test { class TestApp {
public class WinApi
{
public const int CSIDL_APPDATA = 0x1a;
public const int SHGFP_TYPE_DEFAULT = 1;
[DllImport("shell32.dll")]
public static extern int SHGetFolderPath(IntPtr hwnd, int csidl, IntPtr hToken, uint flags, [Out] System.Text.StringBuilder Path);
}
[STAThread]
static void Main()
{
System.Text.StringBuilder builder = new System.Text.StringBuilder(260);
int result = WinApi.SHGetFolderPath(IntPtr.Zero, WinApi.CSIDL_APPDATA, IntPtr.Zero, WinApi.SHGFP_TYPE_DEFAULT, builder);
string path = "";
if (result == 0) path = builder.ToString();
Console.WriteLine(string.Format("{0}:{1}", result, path));
}
} }
You can try use following code to access %AppData%\Roaming\Microsoft:
string appData= Environment.ExpandEnvironmentVariables("%AppData%");
string roamingMicrosoft = Path.Combine(appData, #"Microsoft");
But I'm not really sure if Windows changes environment variable %AppData% by default when user changes path to AppData by it own.
The System.Diagnostics.Process.Start() method accepts a ProcessStartInfo class instance initialized with an executable with no path, such as Notepad.exe. After the process starts one can find the full path it used, such as C:\Windows\SysWOW64\notepad.exe. This is perfect, except when you would like to know the full path without actually launching the program. In my case, I'd like to get the icon from the executable ahead of time.
This is similar to the behavior of the windows "where" command, for example:
C:>where notepad.exe
C:>\Windows\System32\notepad.exe
C:>\Windows\notepad.exe
The first response C:\Windows\System32\notepad.exe is essentially the same as that used by "Process".
The order in which paths are searched is actually registry-dependent, so simply enumerating through the PATH environment variable is not guaranteed to produce the expected result, particularly where there is a file of the expected name in the current working directory. To reliably get the executable path, you will want to call the SearchPath Win32 function in Kernel32.
There is no framework .NET function that exposes SearchPath, but the function can be invoked directly through P/Invoke.
The following sample program illustrates the usage of this function. If notepad.exe exists in the system search paths, per the system configuration, it will print the path; if it does not exist, it will print "File not found" instead.
using System;
using System.Text;
using System.Runtime.InteropServices;
class Program
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint SearchPath(string lpPath,
string lpFileName,
string lpExtension,
int nBufferLength,
[MarshalAs ( UnmanagedType.LPTStr )]
StringBuilder lpBuffer,
out IntPtr lpFilePart);
const int MAX_PATH = 260;
public static void Main()
{
StringBuilder sb = new StringBuilder(MAX_PATH);
IntPtr discard;
var nn = SearchPath(null, "notepad.exe", null, sb.Capacity, sb, out discard);
if (nn == 0)
{
var error = Marshal.GetLastWin32Error();
// ERROR_FILE_NOT_FOUND = 2
if (error == 2) Console.WriteLine("No file found.");
else
throw new System.ComponentModel.Win32Exception(error);
}
else
Console.WriteLine(sb.ToString());
}
}
If you enter an application name (like notepad.exe) in the command line, it searches in the current directory and all paths which are specified in the PATH environment variable. This works similarly when you use Process.Start.
So you would need to search in all paths of the PATH environment variable for your executable file and then extract the icon from it.
I want to open file's location with window Explorer. I am using C# with code
System.Diagnostics.Process.Start("Explorer.exe", #"/select," + FilePath)
it works well with simple English character, but it could not open the file's location if the file's name is Unicode character (Thia language).
Anyone could help please?
Try putting it in quotes:
System.Diagnostics.Process.Start("Explorer.exe", #"/select,""" + FilePath + "\"")
No trouble with this code snippet:
static void Main(string[] args) {
string path = #"c:\temp\លួចស្រលាញ់សង្សារគេ.DAT";
System.IO.File.WriteAllText(path, "hello");
string txt = System.IO.File.ReadAllText(path);
}
Windows 7, the file is created and displays correctly in Explorer. You didn't document your operating system version so that's one failure mode, albeit a very small one. Much more likely is trouble with the file system that's mapped to your E: drive. Like a FAT32 volume on a flash drive or a network redirector. Ask questions about that, respectively, at superuser.com and serverfault.com. Do not forget to document those essential details.
The following code works for me with files with korean characters (are unicode characters). Please try it and let me know if it works.
...
if (this.IsDirectory())
{
OpenFileWith("explorer.exe", this.FullPath, "/root,");
}
else
{
OpenFileWith("explorer.exe", this.FullPath, "/select,");
}
...
public static void OpenFileWith(string exePath, string path, string arguments)
{
if (path == null)
return;
try
{
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.WorkingDirectory = Path.GetDirectoryName(path);
if (exePath != null)
{
process.StartInfo.FileName = exePath;
//Pre-post insert quotes for fileNames with spaces.
process.StartInfo.Arguments = string.Format("{0}\"{1}\"", arguments, path);
}
else
{
process.StartInfo.FileName = path;
process.StartInfo.WorkingDirectory = Path.GetDirectoryName(path);
}
if (!path.Equals(process.StartInfo.WorkingDirectory))
{
process.Start();
}
}
catch(System.ComponentModel.Win32Exception ex)
{
FormManager.DisplayException(ex, MessageBoxIcon.Information);
}
}
Explorer will go to a default folder in this case 'My Documents', if the folder you are attempting to open is not there. Make sure it exists.
Here's the deal as far as I've determined: at least as of Windows 8.1, "Explorer.exe" appears to strip out all combining characters before looking for the file. You can test this either in c# or a console (do chcp 65001 first to get in unicode mode). If you try to open a target named ปู (thai for "crab") it won't work, but if you remove the vowel mark under so that you have just ป, it will work. Further, if you have a folder named ป and you as it to open ปู, it will open the ป folder!
This explains why some other devs had no problem; the problem is not non-ascii: rather, it is filenames with composable characters. Not all languages use them, and even in languages that do, not all file names have them.
The good news is, there's a different way to open these that doesn't have this problem, which is described by #bert-huijben in this answer.
For completeness, here's the version similar to what I ended up using:
[DllImport("shell32.dll", ExactSpelling = true)]
public static extern void ILFree(IntPtr pidlList);
[DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern IntPtr ILCreateFromPathW(string pszPath);
[DllImport("shell32.dll", ExactSpelling = true)]
public static extern int SHOpenFolderAndSelectItems(IntPtr pidlList, uint cild, IntPtr children, uint dwFlags);
public void SelectItemInExplorer(string path)
{
var pidlList = ILCreateFromPathW(path);
if(pidlList == IntPtr.Zero)
throw new Exception(string.Format("ILCreateFromPathW({0}) failed",path));
try
{
Marshal.ThrowExceptionForHR(SHOpenFolderAndSelectItems(pidlList, 0, IntPtr.Zero, 0));
}
finally
{
ILFree(pidlList);
}
}
I need to know which is the real path of a given path.
For example:
The real path is: d:\src\File.txt
And the user give me: D:\src\file.txt
I need as a result: d:\src\File.txt
You can use this function:
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
static extern uint GetLongPathName(string ShortPath, StringBuilder sb, int buffer);
[DllImport("kernel32.dll")]
static extern uint GetShortPathName(string longpath, StringBuilder sb, int buffer);
protected static string GetWindowsPhysicalPath(string path)
{
StringBuilder builder = new StringBuilder(255);
// names with long extension can cause the short name to be actually larger than
// the long name.
GetShortPathName(path, builder, builder.Capacity);
path = builder.ToString();
uint result = GetLongPathName(path, builder, builder.Capacity);
if (result > 0 && result < builder.Capacity)
{
//Success retrieved long file name
builder[0] = char.ToLower(builder[0]);
return builder.ToString(0, (int)result);
}
if (result > 0)
{
//Need more capacity in the buffer
//specified in the result variable
builder = new StringBuilder((int)result);
result = GetLongPathName(path, builder, builder.Capacity);
builder[0] = char.ToLower(builder[0]);
return builder.ToString(0, (int)result);
}
return null;
}
As an old-timer, I always used FindFirstFile for this purpose. The .Net translation is:
Directory.GetFiles(Path.GetDirectoryName(userSuppliedName), Path.GetFileName(userSuppliedName)).FirstOrDefault();
This only gets you the correct casing for the filename portion of the path, not then entire path.
JeffreyLWhitledge's comment provides a link to a recursive version that can work (though not always) to resolve the full path.
Alternative Solution
Here is a solution that worked for me to move files between Windows and a server using case sensitive paths. It walks down the directory tree and corrects each entry with GetFileSystemEntries(). If part of the path is invalid (UNC or folder name), then it corrects the path only up to that point and then uses the original path for what it can't find. Anyway, hopefully this will save others time when dealing with the same issue.
private string GetCaseSensitivePath(string path)
{
var root = Path.GetPathRoot(path);
try
{
foreach (var name in path.Substring(root.Length).Split(Path.DirectorySeparatorChar))
root = Directory.GetFileSystemEntries(root, name).First();
}
catch (Exception e)
{
// Log("Path not found: " + path);
root += path.Substring(root.Length);
}
return root;
}
The way to get the actual path of a file (this won't work for folders) is to follow these steps:
Call CreateFileMapping to create a mapping for the file.
Call GetMappedFileName to get the name of the file.
Use QueryDosDevice to convert it to an MS-DOS-style path name.
If you feel like writing a more robust program that also works with directories (but with more pain and a few undocumented features), follow these steps:
Get a handle to the file/folder with CreateFile or NtOpenFile.
Call NtQueryObject to get the full path name.
Call NtQueryInformationFile with FileNameInformation to get the volume-relative path.
Using the two paths above, get the component of the path that represents the volume itself. For example, if you get \Device\HarddiskVolume1\Hello.txt for the first path and \Hello.txt for the second, you now know the volume's path is \Device\HarddiskVolume1.
Use either the poorly-documented Mount Manager I/O Control Codes or QueryDosDevice to convert substitute the volume portion of the full NT-style path with the drive letter.
Now you have the real path of the file.
As Borja's answer does not work for volumes where 8.3 names are disabled, here the recursive implementation that Tergiver suggests (works for files and folders, as well as the files and folders of UNC shares but not on their machine names nor their share names).
Non-existing file or folders are no problem, what exists is verified and corrected, but you might run into folder-redirection issues, e.g when trying to get the correct path of "C:\WinDoWs\sYsteM32\driVErs\eTC\Hosts" you'll get "C:\Windows\System32\drivers\eTC\hosts" on a 64bit windows as there is no "etc" folder withing "C:\Windows\sysWOW64\drivers".
Test Scenario:
Directory.CreateDirectory(#"C:\Temp\SomeFolder");
File.WriteAllLines(#"C:\Temp\SomeFolder\MyTextFile.txt", new String[] { "Line1", "Line2" });
Usage:
FileInfo myInfo = new FileInfo(#"C:\TEMP\SOMEfolder\MyTeXtFiLe.TxT");
String myResult = myInfo.GetFullNameWithCorrectCase(); //Returns "C:\Temp\SomeFolder\MyTextFile.txt"
Code:
public static class FileSystemInfoExt {
public static String GetFullNameWithCorrectCase(this FileSystemInfo fileOrFolder) {
//Check whether null to simulate instance method behavior
if (Object.ReferenceEquals(fileOrFolder, null)) throw new NullReferenceException();
//Initialize common variables
String myResult = GetCorrectCaseOfParentFolder(fileOrFolder.FullName);
return myResult;
}
private static String GetCorrectCaseOfParentFolder(String fileOrFolder) {
String myParentFolder = Path.GetDirectoryName(fileOrFolder);
String myChildName = Path.GetFileName(fileOrFolder);
if (Object.ReferenceEquals(myParentFolder, null)) return fileOrFolder.TrimEnd(new char[]{Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
if (Directory.Exists(myParentFolder)) {
//myParentFolder = GetLongPathName.Invoke(myFullName);
String myFileOrFolder = Directory.GetFileSystemEntries(myParentFolder, myChildName).FirstOrDefault();
if (!Object.ReferenceEquals(myFileOrFolder, null)) {
myChildName = Path.GetFileName(myFileOrFolder);
}
}
return GetCorrectCaseOfParentFolder(myParentFolder) + Path.DirectorySeparatorChar + myChildName;
}
}
Here's an alternate solution, works on files and directories. Uses GetFinalPathNameByHandle, which is only supported for desktop apps on Vista/Server2008 or above according to docs.
Note that it will resolve a symlink if you give it one, which is part of finding the "final" path.
// http://www.pinvoke.net/default.aspx/shell32/GetFinalPathNameByHandle.html
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint GetFinalPathNameByHandle(SafeFileHandle hFile, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszFilePath, uint cchFilePath, uint dwFlags);
private const uint FILE_NAME_NORMALIZED = 0x0;
static string GetFinalPathNameByHandle(SafeFileHandle fileHandle)
{
StringBuilder outPath = new StringBuilder(1024);
var size = GetFinalPathNameByHandle(fileHandle, outPath, (uint)outPath.Capacity, FILE_NAME_NORMALIZED);
if (size == 0 || size > outPath.Capacity)
throw new Win32Exception(Marshal.GetLastWin32Error());
// may be prefixed with \\?\, which we don't want
if (outPath[0] == '\\' && outPath[1] == '\\' && outPath[2] == '?' && outPath[3] == '\\')
return outPath.ToString(4, outPath.Length - 4);
return outPath.ToString();
}
// http://www.pinvoke.net/default.aspx/kernel32.createfile
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern SafeFileHandle CreateFile(
[MarshalAs(UnmanagedType.LPTStr)] string filename,
[MarshalAs(UnmanagedType.U4)] FileAccess access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
IntPtr templateFile);
private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
public static string GetFinalPathName(string dirtyPath)
{
// use 0 for access so we can avoid error on our metadata-only query (see dwDesiredAccess docs on CreateFile)
// use FILE_FLAG_BACKUP_SEMANTICS for attributes so we can operate on directories (see Directories in remarks section for CreateFile docs)
using (var directoryHandle = CreateFile(
dirtyPath, 0, FileShare.ReadWrite | FileShare.Delete, IntPtr.Zero, FileMode.Open,
(FileAttributes)FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero))
{
if (directoryHandle.IsInvalid)
throw new Win32Exception(Marshal.GetLastWin32Error());
return GetFinalPathNameByHandle(directoryHandle);
}
}
I tried to avoid dll imports so the best way for me was to use System.Linq and the System.IO.Directory class.
For your example
Real path is: d:\src\File.txt
The user give me: D:\src\file.txt
Code for this:
using System.Linq;
public static class PathUtils
{
public static string RealPath(string inputPath)
{
return Directory.GetFiles(Path.GetDirectoryName(inputPath))
.FirstOrDefault(p => String.Equals(Path.GetFileName(p),
Path.GetFileName(inputPath), StringComparison.OrdinalIgnoreCase));
}
}
var p = PathUtils.RealPath(#"D:\src\file.txt");
Method should return the path "d:\src\File.txt" or "D:\src\File.txt".
Here is how I do it. Originally, I used to depend on GetFinalPathNameByHandle which is very good, but unfortunately, some custom file systems don't support it (of course NTFS does). I also tried NtQueryObject with ObjectNameInformation but again, they don't necessarily report the original file name.
So here is another "manual" way:
public static string GetRealPath(string fullPath)
{
if (fullPath == null)
return null; // invalid
var pos = fullPath.LastIndexOf(Path.DirectorySeparatorChar);
if (pos < 0 || pos == (fullPath.Length - 1))
return fullPath.ToUpperInvariant(); // drive letter
var dirPath = fullPath.Substring(0, pos);
var realPath = GetRealPath(dirPath); // go recursive, we want the final full path
if (realPath == null)
return null; // doesn't exist
var dir = new DirectoryInfo(realPath);
if (!dir.Exists)
return null; // doesn't exist
var fileName = fullPath.Substring(pos + 1);
if (fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) // avoid wildcard calls
return null;
return dir.EnumerateFileSystemInfos(fileName).FirstOrDefault()?.FullName; // may return null
}
On Windows, paths are case-insensitive. So both paths are equally real.
If you want to get some kind of a path with canonical capitalization (i. e. how Windows thinks it should be capitalized), you can call FindFirstFile() with the path as a mask, then take the full name of the found file. If the path is invalid, then you won't get a canonical name, natually.
how can I create Virtual Hard Drive (like Z:) that store it's files on physical hard drive (Like C:\Files).
Here is C# code to do this directly:
using System;
using System.Text;
using System.ComponentModel;
using System.Runtime.InteropServices;
static class Subst {
public static void MapDrive(char letter, string path) {
if (!DefineDosDevice(0, devName(letter), path))
throw new Win32Exception();
}
public static void UnmapDrive(char letter) {
if (!DefineDosDevice(2, devName(letter), null))
throw new Win32Exception();
}
public static string GetDriveMapping(char letter) {
var sb = new StringBuilder(259);
if (QueryDosDevice(devName(letter), sb, sb.Capacity) == 0) {
// Return empty string if the drive is not mapped
int err = Marshal.GetLastWin32Error();
if (err == 2) return "";
throw new Win32Exception();
}
return sb.ToString().Substring(4);
}
private static string devName(char letter) {
return new string(char.ToUpper(letter), 1) + ":";
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool DefineDosDevice(int flags, string devname, string path);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int QueryDosDevice(string devname, StringBuilder buffer, int bufSize);
}
Sample usage:
Subst.MapDrive('z', #"c:\temp");
Console.WriteLine(Subst.GetDriveMapping('z'));
Subst.UnmapDrive('z');
You can use subst command.
Use System.Diagnostic.Process to run the subst.exe with desired parameters.
Here is the command syntax:
Syntax
Associates a path with a drive letter.
SUBST [drive1: [drive2:]path]
SUBST drive1: /D
drive1: Specifies a virtual drive to
which you want to assign a path.
[drive2:]path Specifies a physical
drive and path you want to assign to a
virtual drive.
/D Deletes a
substituted (virtual) drive.
Type SUBST with no parameters to display a
list of current virtual drives.list of current virtual drives.
Do it the exact same way you would map a network drive, but point it to a folder on the current machine. The only thing you have to do special is use a UNC path for the local folder.
Here is a helper class
Not sure how to do this in C# but this should help you:
Ive just tested this and works perfect
On my computer have 1 hard drive devised into 2, C: & D:, going into D: i have a folder called Backup, if you right click the folder and click the Share Tab, you will see Network Path.. On my pc it looks like \\Robert-home\backup
I then proceeded to CMD and executed the following command
NET USE Z: \\Robert-home\backup
Witch successfully map the contents of D:\backup to Z:
Im sure you can complete such a task within C#..
I usually use this method at work for client support to transfer and backup files before new consoles are issued to them.