Get more out of the file systemApplies to:
Summary:
About a year ago I wrote an initial article about how to interact with files and folders in .NET. This article elaborates on the topics discussed there. Specifically, I want to go further this time when it comes to different ways for accessing and managing disk drives (floppy, hard disk, network or other) in your computer system.
Unsolved
A number of scenarios were left unmentioned in my initial article on filemanagent in .NET. I showed how to build a list of available diskdrives, but not how to get more information about those drives. How can you check the drive type (floppy, CD-ROM,...) or how do you determine the amount of available space? In this follow-up article, three possibilities are shown to obtain advanced information about storage media, along with pros and cons for each.
Catching up
The System.IO namespace does not contain a Drive object to gather specific information for storage media. While System.IO.Directory has a static method named GetLogicalDrives, it returns a string array. More than a simple list with drive-letters is not possible using this approach.
In order to get more information about a drive, you can import the Microsoft Scripting library into your project. Make a reference to the COM component “Microsoft Scripting Library” and indicate that you want to use the Scripting namespace:
Now you have the Scripting.Drive object at your disposal, which provides significantly more information than the static System.IO.Directory.GetLogicalDrives() method:
![]()
A comprehensive list of available drives with their properties can be obtained as follows:
![]()
The Scripting library is a COM object. This means that various convertions need to take place to send objects and variables back and forth between COM and .NET (marshaling). Furthermore, there is no guarantee that the library will still be supported in .NET in the future. The Scripting library still imposes implicit restrictions: you can only access the features that are exposed to you. When a new API call is added to Windows, you will not have access to it until Microsoft comes out with a new version of the library. At that time, you will be able to update the library (as well as perform the update on each computer system that runs your software).
Application Programmer’s Interface – API
There is an obvious alternative to the Scripting library. After all, the library only operates as a broker sending back and forth messages between the client using it and the underlying Windows Application Programmer’s Interface – API. The reason why we started off using the library is that it is easy to use and that some programming languages have difficulties directly calling upon the API. This is no longer the case in .NET. With the [DllImport] attribute you can access virtually every part of the Windows API (Kernel, GDI and User). To refurbish the above code using API-calls, you need a number of functions: GetLogicalDrives(), GetVolumeInformation(), GetFreeDiskspaceEx() en GetDriveType(). These are all exposed through kernel32.dll.
In order to use these functions in your project, you need to reference them in your code using the DllImport attribute, which is defined in the System.Runtime.InteropServices namespace.
Now you are all set to import the required API-calls. Each one needs to be specified seperately:
The following list helps converting API to .NET datatypes:
More information about interacting with the Windows API from within .NET (P/Invoke) can be found at http://msdn.microsoft.com/msdnmag/issues/03/07/NET/.
In order to use the StringBuilder class, you will also need the System.Text namespace:
To get the same output as in our first example we write the following code:
Access to the Windows API is very fast. Keep in mind that the Scripting library eventually also calls upon the API, probably even using the same functions. By calling upon the API directly, you actually cut out the middle man (the library), which explains the improved performance. Even when you use regular .NET Framework code, the Windows API is called eventually from within the CLR (the CLR offers one big advantage though: if your program runs under the CLR, it will work on any system with a CLR).
The solution proposed here has disadvantages as well. The code is more complicated. A little research is needed to search de API documentation to find the right calls and the right .dll file. Since the API is called directly, your program is bound to the operating systems that support it. The use of functions such as GetDiskFreeSpaceEx() suggests a potential future problem: The different between GetDiskFreeSpace() and GetDiskFreeSpaceEx() is that the first one only work with storage devices up to 2 GB. The API-calls with the “Ex” suffix where implemented when 16-bit fields were no longer sufficient to return correct information. It is possible that in the future 32-bit or even 64-bit integers will no longer suffice either.
Some of these issues can be solved by bundling all functions that interface directly with the API into a wrapper function or class. This does not make your code any simpler, but it does provide a friendlier interface to interact with it. It also allows beginning programmers to use advanced operating system features as a “black box”. You might write different versions of the class for different operating systems. When the calls at some point are no longer relevant (32 bit > 64 bit > 128 bit...), all you need to do is alter the code in one central location.
A Drive class could look as follows: First, a static method to obtain a list of all available drives.
The class constructor is provided with one argument: the letter of the drive that needs to be investigated. A private default constructor is also added. This prevents that a Drive object can be created without specifying a drive letter. If a default constructor is not explicitely set to private, the compiler will automatically supply a default public constructor, something that is undesirable in this case:
A number of instance properties allow the programmer to obtain specifics about a drive:
Windows Management Instrumentation – WMI
There is a third way to get to extended drive information. Windows Management Instrumentation – WMI – offers an object-oriented interface to the Windows operating system. Every aspect of the operating system is represented by an object that can be accessed and queried in the same uniform way. For this, WMI has a standard set of methods and classes to retrieve information. They are available to the programmer through the System.Management namespace, which can be used by your project after adding a reference to the System.Management library:
![]()
Now the namespace is available to reference from your code:
WMI consists of a set of classes that represent system-objects. In order to manage storage media, you need the Win32_LogicalDisk class:
This results in a collection of all available drives on your system:
Both instructions may be combined in one statement:
You can also request the properties of a specific drive more directly:
You can download the WMI Tools from the Microsoft website. These allow you to interactively browse through the available WMI classes on your computer. You can examine the properties of each one. As is clear from the following screenshot, there is a lot more information available through WMI than we obtained earlier through the Scripting library. There is also more information than we retrieved using the API, since we only used a few calls.
![]()
Whether or not a drive is ready should be verifiable using either the fields Availability, Status or StatusInfo. In practice however, these fields often have the value <empty> (or null), regardless of whether a CD-ROM drive has a CD loaded. As an alternative you can check the Size property. If this property has a value, the drive is ready. If it has the <empty> value, it is not ready and you should not try to read from or write to the device. You can substitute the Size property with others such as FreeSpace or FileSystem. These will also be equal to <empty>, allowing you to check for availability through error handling:
The reason this works is because an exception is thrown when the Convert class tries to convert an <empty> value to a number.
We have only listed a limited number of WMI properties so that it is easy to compare with the Scripting and API code. It is clear that other WMI properties for storage media can be accessed and displayed in the same way.
It is possible to write a similar wrapper-class for our WMI approach as we did for the API. However, as you require more properties, you will start to notice that your code eventually differs little from working with the System.Management object directly. Unless you want to use complicated logic with WMI data (which is not the case here), it is just as easy to work with the objects already provided through the .NET Framework.
The “W” in WMI stands for Windows, which means that the System.Management namespace may very well be specifically tied to the Windows Operating System and stay that way in the future.
Through WMI, potential problems are easier to handle in comparison with the API. An exception will be thrown if a non-existing class is requested. This can be caught and dealt with accordingly:
Decisions
Three methods were shown to obtain advanced properties of storage media. Which method you implement in your project depends on the environment in which your software is developed and in which it will be used. No situation seems to justify the use of the Scripting library anymore. In .NET the library can be considered a legacy application offering only limited functionality compared to the Windows API and the extended information exposed through WMI.
Choosing between the API and WMI is less easy. The API offers maximum performance, at the expense of transparancy and becoming dependent to an operating system. These apparent disadvantages can be minimized by building wrapper-classes around your critical code.
In contrast, WMI and the System.Management namespace give you a framework which opens up almost every aspect of Windows to you directly, without having to know every API-call for every single variable. This is particularly useful when working with large amounts of data from different sources. For large-scale projects with the qualified specialist people however it may still be worthwhile converting original WMI-code to API-calls.
About the author![]()
In 1989, Yves Sucaet started working with computers and GW Basic. In 1997 this led to a first real job at Alcatel e-COM. Afterwards, he spent some time in New Zealand to build generic e-business solutions. When this was finished, he started working at a Fortune 500 company. Currently he is linked to Troy State University in the USA as an assistant and is researching on bio-computing. You can reach him at yves.sucaet@usa.net.
|