When I use System.IO.DriveInfo.GetDrives()
and look at the .VolumeLabel
property 开发者_JS百科of one of the drives, I see "PATRIOT XT", which is indeed the drive's volume label.
If I open "My Computer", instead I see "TrueCrypt Traveler Disk", and I can't seem to find any way to programmatically retrieve that value as none of the DriveInfo
properties hold that value. I also tried querying the information via WMI's Win32_LogicalDisk
, but no properties contained that value there either.
So any idea what the label My Computer uses is called, and more importantly, how to programmatically retrieve it?
EDIT: To be clear, here's the code I'm using, followed by what it outputs, followed by what I see in My Computer (which is what I want to duplicate):
foreach (DriveInfo DI in DriveInfo.GetDrives())
richTextBox1.AppendText(
(
DI.IsReady ?
(DI.VolumeLabel.Length == 0 ? DI.DriveType.ToString() : DI.VolumeLabel) :
DI.DriveType.ToString()
)
+
" (" + DI.Name.Replace("\\", "") + ")"
+ Environment.NewLine
);
Removable (A:) Fixed (C:) CDRom (D:) PATRIOT XT (E:) Backup (Y:) Data (Z:)
My Computer details view displays:
Floppy Disk Drive (A:) Local Disk (C:) DVD RW Drive (D:) TrueCrypt Traveler Disk (E:) Backup (Y:) Data (Z:)
Unfortunately, to get this information without hacks and weird tricks, you need to use the P/Invoke technique. There are 2 options:
- Get the real label set by user or system. This could be "New volume", "Install (\Server)", "Contoso Pro Installation disk 4" and so on.
- Get the label exactly as it is shown in Explorer (My computer / This PC window). This is the same as (1) but it follows user preferences set in Folder Options dialog, e.g. "Hide drive letter". Example: "New volume (Q:)"
To get the information as explained in option (1), you'll have to use the following code:
public const string SHELL = "shell32.dll";
[DllImport(SHELL, CharSet = CharSet.Unicode)]
public static extern uint SHParseDisplayName(string pszName, IntPtr zero, [Out] out IntPtr ppidl, uint sfgaoIn, [Out] out uint psfgaoOut);
[DllImport(SHELL, CharSet = CharSet.Unicode)]
public static extern uint SHGetNameFromIDList(IntPtr pidl, SIGDN sigdnName, [Out] out String ppszName);
public enum SIGDN : uint
{
NORMALDISPLAY = 0x00000000,
PARENTRELATIVEPARSING = 0x80018001,
DESKTOPABSOLUTEPARSING = 0x80028000,
PARENTRELATIVEEDITING = 0x80031001,
DESKTOPABSOLUTEEDITING = 0x8004c000,
FILESYSPATH = 0x80058000,
URL = 0x80068000,
PARENTRELATIVEFORADDRESSBAR = 0x8007c001,
PARENTRELATIVE = 0x80080001
}
//var x = GetDriveLabel(@"C:\")
public string GetDriveLabel(string driveNameAsLetterColonBackslash)
{
IntPtr pidl;
uint dummy;
string name;
if (SHParseDisplayName(driveNameAsLetterColonBackslash, IntPtr.Zero, out pidl, 0, out dummy) == 0
&& SHGetNameFromIDList(pidl, SIGDN.PARENTRELATIVEEDITING, out name) == 0
&& name != null)
{
return name;
}
return null;
}
For option (2), replace SIGDN.PARENTRELATIVEEDITING
with SIGDN.PARENTRELATIVE
or SIGDN.NORMALDISPLAY
.
Note: for option 2, there's also 1-call method using ShGetFileInfo()
, but it calls these methods anyway, and is less flexible, so I do not post it here.
Note 2: keep in mind, the signature of SHGetNameFromIDList()
is optimized in this example. In case the drive label is used only temporary (especially if it is re-read from time to time) this example introduces small memory leak. To avoid it, declare last parameter as out IntPtr
, and then use something like
var tmp = Marshal.PtrToStringUni(ppszName);
Marshal.FreeCoTaskMem(ppszName);
Note 3: this works over Windows shell, so it will return what user expects, regardless of the source of this label - volume label, user edit, Autorun.inf file or anything else.
Thanks for tip about autorun.inf. Here is the C# fragment which I created to retrieve the label.
private string GetDriveLabelFromAutorunInf(string drivename)
{
try
{
string filepathAutorunInf = Path.Combine(drivename, "autorun.Inf");
string stringInputLine = "";
if (File.Exists(filepathAutorunInf))
{
StreamReader streamReader = new StreamReader(filepathAutorunInf);
while ((stringInputLine = streamReader.ReadLine()) != null)
{
if (stringInputLine.StartsWith("label="))
return stringInputLine.Substring(startIndex:6);
}
return "";
}
else return "";
}
#region generic catch exception, display message box, and terminate
catch (Exception exception)
{
System.Diagnostics.StackTrace trace = new System.Diagnostics.StackTrace(exception, true);
MessageBox.Show(string.Format("{0} Exception:\n{1}\n{2}\n\n{3}\n\nMethod={4} Line={5} Column={6}",
trace.GetFrame(0).GetMethod().Module,
exception.Message,
exception.StackTrace,
exception.ToString(),
trace.GetFrame(0).GetMethod().Name,
trace.GetFrame(0).GetFileLineNumber(),
trace.GetFrame(0).GetFileColumnNumber()),
"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
Environment.Exit(1);
return ""; // to keep compiler happy
}
#endregion
}
Looks like My Computer looks at the autorun.inf and uses the label= value from the [autorun] section.
Still not quite sure where the "DVD RW Drive" and "Floppy Disk Drive" labels come from, but I guess they may be hardcoded based on the drive type.
I hope the following will help you:
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
static extern bool GetVolumeInformation(string Volume,
StringBuilder VolumeName, uint VolumeNameSize,
out uint SerialNumber, out uint SerialNumberLength, out uint flags,
StringBuilder fs, uint fs_size);
private void Form1_Load(object sender, EventArgs e)
{
uint serialNum, serialNumLength, flags;
StringBuilder volumename = new StringBuilder(256);
StringBuilder fstype = new StringBuilder(256);
bool ok = false;
Cursor.Current = Cursors.WaitCursor;
foreach (string drives in Environment.GetLogicalDrives())
{
ok = GetVolumeInformation(drives, volumename, (uint)volumename.Capacity - 1, out serialNum,
out serialNumLength, out flags, fstype, (uint)fstype.Capacity - 1);
if (ok)
{
lblVolume.Text = lblVolume.Text + "\n Volume Information of " + drives + "\n";
lblVolume.Text = lblVolume.Text + "\nSerialNumber of is..... " + serialNum.ToString() + " \n";
if (volumename != null)
{
lblVolume.Text = lblVolume.Text + "VolumeName is..... " + volumename.ToString() + " \n";
}
if (fstype != null)
{
lblVolume.Text = lblVolume.Text + "FileType is..... " + fstype.ToString() + " \n";
}
}
ok = false;
}
Cursor.Current = Cursors.Default;
}
I haven't tried this myself, but in the registry, look for
HKLM/Software/Microsoft/Windows/CurrentVersion/Explorer/DriveIcons/[Drive-Letter]/
then read the
DefaultLabel
key. Also WARNING! writing invalid keys/values to the registry can severely damage your system! Be sure you're certain of what you're doing before proceeding. Here is a resource to help you with accessing the registry programmatically.
This looks like it could be a solution.
It's located in the autorun.inf folder. My Volume Label for my flash drive is simply 16G, but by putting an autorun.inf file with the following text [autorun] label=My 16 gigabyte Flash Drive
and then using attrib to +s +h +r the file, it doesn't show up unless I have show hidden files AND show system files under folder options/view enabled.
To programmatically located this via C#, I honestly haven't tried to open the autorun.inf, but it should be straight forward, check if File.Exists(Drive:\autorun.inf) ignoring the fact that it's +s+h+r (just in case someone has set it), then open it readonly and parse the label= line. If in fact, the file is present, use the autorun label instead of the Volume Label.
I can still change use the autorun.inf label= tag even in Windows 7 to modify label.
Recently I had the same problem that I have been able to solve. Here is how to obtain the labels as they appear in the Windows Explorer:
- Add
C:\Windows\System32\shell32.dll
as a reference. - Add
using Shell32;
- Instantiate the shell object:
dynamic shellObject = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
- Get the disk data:
var driveData = (Folder2)ShellObject.NameSpace(driveInfo.Name);
- The parameter
driveData.Name
will contain the label (For example: "Local Disk (C:)").
And below is the complete code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using Shell32;
namespace VolumeLabels
{
static class Drives
{
[DebuggerDisplay("Name: '{Name,nq}', Path: '{Path,nq}'")]
public struct DriveNameInfo
{
public string Name { get; }
public string Path { get; }
public DriveNameInfo(string name, string path)
{
Name = name;
Path = path;
}
public override string ToString()
{
return Name;
}
}
private static dynamic _shellObject;
private static dynamic ShellObject => _shellObject ?? (_shellObject = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application")));
public static IEnumerable<DriveNameInfo> Get()
{
foreach (var driveInfo in DriveInfo.GetDrives())
{
var driveData = (Folder2)ShellObject.NameSpace(driveInfo.Name);
if (driveData == null)
yield break;
var driveDataSelf = driveData.Self;
yield return new DriveNameInfo(driveDataSelf.Name, driveDataSelf.Path);
}
}
}
class Program
{
static void Main(string[] args)
{
foreach (var driveNameInfo in Drives.Get())
Console.WriteLine(driveNameInfo);
Console.ReadKey(true);
}
}
}
精彩评论