[Mono-dev] DriveInfo implementation

Max de Lavenne max at tfbc.com
Mon Dec 3 20:19:29 EST 2007


Hey,

I can help here. I had to do this a few years ago for windows. This is very
complete (we use it in production). Some of this code has been inspired from
snippets in the internet. I can't remember where from though.

You will find a Computer class that does a whole bunch of things, along with
all the P/Invoke and enums with it.

Best Regards

Max

	/// <summary>
	/// Lists the different type of drives
	/// </summary>
	public enum DriveType {
		/// <summary>
		/// The drive type cannot be determined.
		/// </summary>
		DRIVE_UNKNOWN     = 0,
		/// <summary>
		/// The root path is invalid. For example, no volume is
mounted at the path.
		/// </summary>
		DRIVE_NO_ROOT_DIR = 1,
		/// <summary>
		/// The disk can be removed from the drive.
		/// </summary>
		DRIVE_REMOVABLE	  = 2,
		/// <summary>
		/// The disk cannot be removed from the drive.
		/// </summary>
		DRIVE_FIXED       = 3,
		/// <summary>
		/// The drive is a remote (network) drive.
		/// </summary>
		DRIVE_REMOTE      = 4,
		/// <summary>
		/// The drive is a CD-ROM drive.
		/// </summary>
		DRIVE_CDROM       = 5,
		/// <summary>
		/// The drive is a RAM disk.
		/// </summary>
		DRIVE_RAMDISK     = 6
	}

	/// <summary>
	/// This class contains information about this computer
	/// </summary>
	public class Computer
	{
		/// <summary>
		/// Return the manufacturer hard drive serial number
		/// </summary>
		/// <param name="drive">Drive to fetch serial number
from</param>
		/// <returns>Hard drive serial number</returns>
		public static string GetHardDriveSerial(char drive) {
			// call into Win32
			uint serNum = 0;
			uint maxCompLen = 0;
			StringBuilder VolLabel = new StringBuilder(256); //
Label
			UInt32 VolFlags = new UInt32();
			StringBuilder FSName = new StringBuilder(256); //
File System Name
			string strDriveLetter = drive + ":\\"; // fix up the
passed-in drive letter for the API call
			long Ret = GetVolumeInformation(strDriveLetter,
VolLabel, (UInt32)VolLabel.Capacity, ref serNum, ref maxCompLen, ref
VolFlags, FSName, (UInt32)FSName.Capacity);

			if ( Ret == 0 ) {
				return null;	// not found
			}

			return Convert.ToString(serNum);
		}

		[DllImport("kernel32.dll")]
		private static extern long GetVolumeInformation(
			string PathName,
			StringBuilder VolumeNameBuffer,
			UInt32 VolumeNameSize,
			ref UInt32 VolumeSerialNumber,
			ref UInt32 MaximumComponentLength,
			ref UInt32 FileSystemFlags,
			StringBuilder FileSystemNameBuffer,
			UInt32 FileSystemNameSize);


		/// <summary>
		/// Return a list of logical harddrives
		/// </summary>
		/// <returns>list of string ("c:\\","d:\\")</returns>
		public static string [] GetFixedHardDrives() {
			string [] drives = GetDrives();
			ArrayList array = new ArrayList(drives.Length);

			for (int i = 0; i < drives.Length; i++ ) {
				// drive found. List only the hard drives
(no cdrom, no network)
				if ( GetDriveType(drives[i]) ==
DriveType.DRIVE_FIXED ) {
					// hdd => store
					array.Add(drives[i]);
				}
			}

			// build up result array
			string [] logical_drives = new string[array.Count];
			array.CopyTo(logical_drives,0);

			return logical_drives;
		}

		/// <summary>
		/// Return a complete list of used drives in this machine 
		/// (without necessarily all the used network drives)
		/// </summary>
		/// <returns>list of string ("a:\","c:\\","d:\\")</returns>
		public static string [] GetDrives() {
			ArrayList array = new ArrayList(17);

			// call in the Win32 API
			int drives = GetLogicalDrives();
			int test = 1;
			string rootPath;
			for (int i = 0; i < 16; i++, test<<=1 ) {
				if ( (drives & test) > 0 ) {
					rootPath = (char)('A'+i) + ":\\";
					array.Add(rootPath);
				}
			}

			// build up result array
			string [] logical_drives = new string[array.Count];
			array.CopyTo(logical_drives,0);

			return logical_drives;
		}

		/// <summary>
		/// Get the logical disk drives as a 16 bit bitmask
		/// </summary>
		[DllImport("kernel32.dll")] 
		private static extern int GetLogicalDrives();

		/// <summary>
		/// Get the type of a drive
		/// </summary>
		/// <param name="rootPathName">rootPath is on the form:
"c:\\"</param>
		[DllImport("kernel32.dll")] 
		private static extern DriveType GetDriveType(string
rootPathName);

		/// <summary>
		/// Gets the list of all unused drives
		/// </summary>
		public static string [] GetUnusedDrives() {
			string [] shares = GetNetworkDrives();
			string [] drives = GetDrives();

			ArrayList items = new ArrayList();
			for (int i = 0; i <= (int)('Z'-'A'); i++ ) {
				string drive = (char)('A'+i)+":\\";
				bool found = false;
				for (int j = 0; j < drives.Length; j++ ) {
					if ( drives[j].StartsWith(drive) ) {
						found = true;
						break;
					}
					continue;
				}
				if ( found ) continue;
				for (int j = 0; j < shares.Length;j++ ) {
					if ( shares[j].StartsWith(drive) ) {
						found = true;
						break;
					}
				}
				if ( found ) continue;

				items.Add(drive);
			}

			string [] list = new string[items.Count];
			items.CopyTo(list);
			return list;
		}

		/// <summary>
		/// Prepare a system call for enumerating the network drives
		/// </summary>
		[DllImport("mpr.dll", CharSet=CharSet.Auto)]
		private static extern int WNetOpenEnum(
			RESOURCE_SCOPE dwScope, 
			RESOURCE_TYPE dwType,
			RESOURCE_USAGE dwUsage, 
			[MarshalAs(UnmanagedType.AsAny)][In] Object
lpNetResource,
			out IntPtr lphEnum);

		/// <summary>
		/// Enumerate all the network resouces currently being used
		/// </summary>
		[DllImport("mpr.dll", CharSet=CharSet.Auto)]
		private static extern int WNetEnumResource(
			IntPtr hEnum, 
			ref int lpcCount,
			IntPtr lpBuffer, 
			ref int lpBufferSize 
			);

		/// <summary>
		/// Release resources used when enumerating the network
drives
		/// </summary>
		[DllImport("mpr.dll", CharSet=CharSet.Auto)]
		private static extern int WNetCloseEnum( IntPtr hEnum );

		/// <summary>
		/// Scope of the enumeration
		/// </summary>
		private enum RESOURCE_SCOPE {
			/// <summary>
			/// Enumerate all currently connected resources. 
			/// The function ignores the dwUsage parameter. 
			/// For more information, see the following Remarks
section. 
			/// </summary>
			RESOURCE_CONNECTED = 0x00000001,
			/// <summary>
			/// Enumerate only resources in the network context
of the caller. 
			/// Specify this value for a Network Neighborhood
view. 
			/// The function ignores the dwUsage parameter. 
			/// </summary>
			RESOURCE_GLOBALNET = 0x00000002,
			/// <summary>
			/// Enumerate all resources on the network
			/// </summary>
			RESOURCE_REMEMBERED = 0x00000003,
			/// <summary>
			/// Enumerate all remembered (persistent)
connections. 
			/// The function ignores the dwUsage parameter. 
			/// </summary>
			RESOURCE_RECENT= 0x00000004,
			/// <summary>
			/// All connected and recond connections
			/// </summary>
			RESOURCE_CONTEXT= 0x00000005
		}

		/// <summary>
		/// Resource types to be enumerated
		/// </summary>
		private enum RESOURCE_TYPE {
			/// <summary>
			/// All resources. 
			/// This value cannot be combined with
RESOURCETYPE_DISK or RESOURCETYPE_PRINT.
			/// </summary>
			RESOURCETYPE_ANY= 0x00000000,
			/// <summary>
			/// All disk resources
			/// </summary>
			RESOURCETYPE_DISK= 0x00000001,
			/// <summary>
			/// All print resources
			/// </summary>
			RESOURCETYPE_PRINT = 0x00000002,
			/// <summary>
			/// Reserved by windows
			/// </summary>
			RESOURCETYPE_RESERVED = 0x00000008,
		}

		/// <summary>
		/// Resource usage type to be enumerated
		/// </summary>
		private enum RESOURCE_USAGE {
			/// <summary>
			/// All resources
			/// </summary>
			RESOURCEUSAGE_ALL = 0,
			/// <summary>
			/// All connectable resources
			/// </summary>
			RESOURCEUSAGE_CONNECTABLE =0x00000001,
			/// <summary>
			/// All container resources
			/// </summary>
			RESOURCEUSAGE_CONTAINER=0x00000002,
			/// <summary>
			/// need doc.
			/// </summary>
			RESOURCEUSAGE_NOLOCALDEVICE =0x00000004,
			/// <summary>
			/// need doc.
			/// </summary>
			RESOURCEUSAGE_SIBLING=0x00000008,
			/// <summary>
			/// Setting this value forces WNetOpenEnum to fail
if the user is not authenticated. 
			/// The function fails even if the network allows
enumeration without authentication
			/// </summary>
			RESOURCEUSAGE_ATTACHED=0x00000010,
			/// <summary>
			/// All resources that are connectable, container
and attached,
			/// </summary>
			RESOURCEUSAGE_ALL_LOCAL =(RESOURCEUSAGE_CONNECTABLE
| RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED),
		}

//		private enum RESOURCE_DISPLAYTYPE {
//			RESOURCEDISPLAYTYPE_GENERIC= 0x00000000,
//			RESOURCEDISPLAYTYPE_DOMAIN= 0x00000001,
//			RESOURCEDISPLAYTYPE_SERVER= 0x00000002,
//			RESOURCEDISPLAYTYPE_SHARE= 0x00000003,
//			RESOURCEDISPLAYTYPE_FILE = 0x00000004,
//			RESOURCEDISPLAYTYPE_GROUP= 0x00000005,
//			RESOURCEDISPLAYTYPE_NETWORK= 0x00000006,
//			RESOURCEDISPLAYTYPE_ROOT = 0x00000007,
//			RESOURCEDISPLAYTYPE_SHAREADMIN = 0x00000008,
//			RESOURCEDISPLAYTYPE_DIRECTORY = 0x00000009,
//			RESOURCEDISPLAYTYPE_TREE = 0x0000000A,
//			RESOURCEDISPLAYTYPE_NDSCONTAINER = 0x0000000B
//		}

		/// <summary>
		/// The NETRESOURCE structure contains information about a
network resource. 
		/// The structure is returned during enumeration of network
resources.
		/// NETRESOURCE is also specified when making or querying a
network 
		/// connection with calls to various Windows Networking
functions.
		/// </summary>
		[StructLayout(LayoutKind.Sequential, Pack=1)]
		private struct NETRESOURCE {
			/// <summary>
			/// Scope of the enumeration
			/// </summary>
			public RESOURCE_SCOPE dwScope;
			/// <summary>
			/// Set of bit flags identifying the type of
resource
			/// </summary>
			public RESOURCE_TYPE dwType;
			/// <summary>
			/// Display options for the network object in a
network browsing user interface
			/// </summary>
			public int dwDisplayType;	//
RESOURCE_DISPLAYTYPE
			/// <summary>
			/// Set of bit flags describing how the resource can
be used.
			/// Note that this member can be specified only
			/// if the dwScope member is equal to
RESOURCE_GLOBALNET
			/// </summary>
			public RESOURCE_USAGE dwUsage;
			/// <summary>
			/// If the dwScope member is equal to
RESOURCE_CONNECTED or RESOURCE_REMEMBERED,
			/// this member is a pointer to a null-terminated
character string 
			/// that specifies the name of a local device. 
			/// This member is NULL if the connection does not
use a device. 
			/// </summary>
	
[MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)]
			public string lpLocalName;
			/// <summary>
			/// If the entry is a network resource, this member
is a pointer to 
			/// a null-terminated character string that
specifies the remote network name.<br></br>
			/// <br></br>
			/// If the entry is a current or persistent
connection, 
			/// lpRemoteName points to the network name
associated with the name pointed to by the lpLocalName member.<br></br>
			/// <br></br>
			/// The string can be MAX_PATH characters in length,

			/// and it must follow the network provider's naming
conventions
			/// </summary>
	
[MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)] 
			public string lpRemoteName;
			/// <summary>
			/// Pointer to a null-terminated string that
contains a comment supplied by the network provider
			/// </summary>
	
[MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)] 
			public string lpComment;
			/// <summary>
			/// Pointer to a null-terminated string that
contains the name of the provider 
			/// that owns the resource. This member can be NULL
if the provider name is unknown. 
			/// To retrieve the provider name, you can call the
WNetGetProviderName function
			/// </summary>
	
[MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)] 
			public string lpProvider;
		}

		/// <summary>
		/// Gets a list of all network drives currently connected
		/// in the form "M: \\server\share"
		/// </summary>
		public static string [] GetNetworkDrives() {
			return GetNetworkDrives(null);
		}

		/// <summary>
		/// Gets a list of all network drives currently connected
		/// in the form "M: \\server\share"
		/// </summary>
		private static string [] GetNetworkDrives(object rootObject)
{
			ArrayList shares = new ArrayList();

			try {
				int iRet;
				IntPtr enumHandle;

				// prepare the call to win32
				iRet =WNetOpenEnum( 
					RESOURCE_SCOPE.RESOURCE_CONNECTED, 
					RESOURCE_TYPE.RESOURCETYPE_DISK,
					RESOURCE_USAGE.RESOURCEUSAGE_ALL, 
					rootObject, 
					out enumHandle );

				if( iRet != 0 ) { 
					return new string[0]; 
				}

				// ok, list all entries
				int bufferLen = 16384;
				IntPtr ptrBuffer = Marshal.AllocHGlobal(
bufferLen );

				while ( true ) {
					int entries = -1;
					bufferLen = 16384;

					// list as many as we can in one go
					iRet =WNetEnumResource( enumHandle,
ref entries, ptrBuffer, ref bufferLen );
					if( (iRet != 0) || (entries < 1) ) {
						break;
					}

					// found a buch
					Int32 ptr = ptrBuffer.ToInt32();

					// list them all
					for( int i = 0; i < entries; i++ ) {
						NETRESOURCE nr =
(NETRESOURCE)Marshal.PtrToStructure( new IntPtr(ptr), typeof(NETRESOURCE) );
						if(
RESOURCE_USAGE.RESOURCEUSAGE_CONTAINER ==
							(nr.dwUsage	&
RESOURCE_USAGE.RESOURCEUSAGE_CONTAINER) ) {
							// call recursively
to get all entries in a container
	
shares.AddRange(GetNetworkDrives(nr));
						}

						ptr += Marshal.SizeOf( nr );

						string share =
string.Format("{0}\\ {1}",nr.lpLocalName,nr.lpRemoteName);
						shares.Add(share);
					}
				}

				Marshal.FreeHGlobal( ptrBuffer );

				// release resources
				WNetCloseEnum( enumHandle );
			} catch(Exception ) {
				
			}

			// build up result array
			string [] shares_list = new string[shares.Count];
			shares.CopyTo(shares_list,0);

			return shares_list;
		}

		/// <summary>
		/// Force a reboot of the computer
		/// </summary>
		public static void Reboot() {
			DoExitWin( EWX.REBOOT | EWX.FORCEIFHUNG );
		}

		/// <summary>
		/// Force a shutdown of the computer
		/// </summary>
		public static void ShutDown() {
			DoExitWin( EWX.POWEROFF | EWX.FORCE );
		}

		/// <summary>
		/// retrieves a pseudo handle for the current process
		/// </summary>
		[DllImport("kernel32.dll", ExactSpelling=true) ]
		private static extern IntPtr GetCurrentProcess();

		/// <summary>
		/// opens the access token associated with a process
		/// </summary>
		[DllImport("advapi32.dll", ExactSpelling=true,
SetLastError=true) ]
		private static extern bool OpenProcessToken( IntPtr h, int
acc, ref IntPtr phtok );

		/// <summary>
		/// Retrieves the locally unique identifier (LUID) used on a
specified system
		/// to locally represent the specified privilege name.
		/// </summary>
		[DllImport("advapi32.dll", SetLastError=true) ]
		private static extern bool LookupPrivilegeValue( string
host, string name, ref long pluid );

		/// <summary>
		/// enables or disables privileges in the specified access
token. 
		/// Enabling or disabling privileges in an access token
requires TOKEN_ADJUST_PRIVILEGES access
		/// </summary>
		[DllImport("advapi32.dll", ExactSpelling=true,
SetLastError=true) ]
		private static extern bool AdjustTokenPrivileges(
			IntPtr htok, bool disall, ref TokPriv1Luid newst, 
			int len, IntPtr prev, IntPtr relen );

		/// <summary>
		/// The ExitWindowsEx function either logs off the current
user, shuts down the system,
		/// or shuts down and restarts the system.<br></br>
		/// It sends the WM_QUERYENDSESSION message to all
applications to determine if they can be terminated.
		/// </summary>
		[DllImport("user32.dll", ExactSpelling=true,
SetLastError=true) ]
		private static extern bool ExitWindowsEx( int flg, int rea
);

		/// <summary>
		/// Contains information about a set of privileges for an
access token.
		/// </summary>
		[StructLayout(LayoutKind.Sequential, Pack=1)]
			private struct TokPriv1Luid {
			public int Count;
			public long Luid;
			public int Attr;
		}

		[Flags]
		private enum EWX {
			/// <summary>
			/// Shuts down all processes running in the logon
session of the process that 
			/// called the ExitWindowsEx function. Then it logs
the user off.<br></br>
			/// This flag can be used only by processes running
in an interactive user's logon session.
			/// </summary>
			LOGOFF = 0x00000000,
			/// <summary>
			/// Shuts down the system to a point at which it is
safe to turn off the power. 
			/// All file buffers have been flushed to disk, and
all running processes have stopped.<br></br>
			/// The calling process must have the
SE_SHUTDOWN_NAME privilege.<br></br>
			/// Specifying this flag will not turn off the power
even if the system supports the power-off feature.
			/// You must specify EWX_POWEROFF to do this.
			/// </summary>
			SHUTDOWN = 0x00000001,
			/// <summary>
			/// Shuts down the system and then restarts the
system. 
			/// The calling process must have the
SE_SHUTDOWN_NAME privilege.
			/// </summary>
			REBOOT = 0x00000002,
			/// <summary>
			/// Forces processes to terminate. 
			/// When this flag is set, the system does not send
the WM_QUERYENDSESSION 
			/// and WM_ENDSESSION messages. This can cause the
applications to lose data. 
			/// Therefore, you should only use this flag in an
emergency.<br></br>
			/// Starting with Windows XP, these messages will
always be sent.
			/// </summary>
			FORCE = 0x00000004,
			/// <summary>
			/// Shuts down the system and turns off the power.
The system must support the power-off feature. 
			/// The calling process must have the
SE_SHUTDOWN_NAME privilege.
			/// </summary>
			POWEROFF = 0x00000008,
			/// <summary>
			/// Forces processes to terminate if they do not
respond to the 
			/// WM_QUERYENDSESSION or WM_ENDSESSION message
within the timeout interval
			/// </summary>
			FORCEIFHUNG = 0x00000010
		}

		/// <summary>
		/// Exit windows a way or another, as indicated by the flags
		/// </summary>
		private static void DoExitWin( EWX flag ) {
			// some flags 
			int SE_PRIVILEGE_ENABLED = 0x00000002;
            int TOKEN_QUERY = 0x00000008;
			int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
			string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";

			// lets go
			bool ok;
			TokPriv1Luid tp;

			// get the current process handle
			IntPtr hproc = GetCurrentProcess();

			// get access tokens
			IntPtr htok = IntPtr.Zero;
			ok = OpenProcessToken( hproc,
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok );

			tp.Count = 1;
			tp.Luid = 0;
			tp.Attr = SE_PRIVILEGE_ENABLED;

			// lookup to see if we're allowed to shutdown this
computer
			ok = LookupPrivilegeValue( null, SE_SHUTDOWN_NAME,
ref tp.Luid );
			ok = AdjustTokenPrivileges( htok, false, ref tp, 0,
IntPtr.Zero, IntPtr.Zero );

			// perform operation
			ok = ExitWindowsEx( (int)flag, 0 );
		}
	}


-----Original Message-----
From: mono-devel-list-bounces at lists.ximian.com
[mailto:mono-devel-list-bounces at lists.ximian.com] On Behalf Of Miguel de
Icaza
Sent: Monday, December 03, 2007 5:04 PM
To: Javier Martín
Cc: mono-devel-list at lists.ximian.com
Subject: Re: [Mono-dev] DriveInfo implementation

Hello,

> I would like to help in the implementation of the System.IO.DriveInfo 
> class, which as of now is semi-functional in Linux and little more 
> than a stub in Windows. However, after thinking a bit about it, I've 
> come to the conclusion that the methods that discover the volumes in 
> the system (*GetDrives) require P/Invoke at the very least (windows), 
> and possibly even unmanaged code (linux).

Correct, for Windows we should use P/Invokes.

For Linux, the current "trivial" implementation is enough, a more complete
implementation probably should talk with DBus to Hal, but am unsure about
that.

For Unix, a full solution probably needs to use Mono.Posix to get the file
system information (notice that information about actual devices is hard to
obtain in Linux, unless we use something like Hal).

> The point of this message is asking for directions and rules on this 
> matter. Is unmanaged code (at all) allowed? Can I create a portable 
> "interface" (not necessarily a .NET interface) and then a separated, 
> system-dependant implementation? How are those platform-dependant 
> switches managed in the Mono autoconf files? Etcetera.

I believe that for the Windows case, you could get away with P/Invoke, we
have used glue in the past, see the mono/support directory, it contains
plenty of portability glue.

> I would appreciate any pointers on the matter.
> 
> Habbit
> _______________________________________________
> Mono-devel-list mailing list
> Mono-devel-list at lists.ximian.com
> http://lists.ximian.com/mailman/listinfo/mono-devel-list
_______________________________________________
Mono-devel-list mailing list
Mono-devel-list at lists.ximian.com
http://lists.ximian.com/mailman/listinfo/mono-devel-list




More information about the Mono-devel-list mailing list