Inventorying HDDs Remotely on Windows

Resource management and IT inventory is the task of many system administrators and IT specialists. Larger businesses or corporations with thousands of computers hire experts to get the job done. In the case of small-to-medium-sized firms, however, where only a couple hundred computers are involved, it is more cost-effective for these tasks to be handled by the IT staff. In this article we’ll look into a solution that does this remotely.

Before we begin, let’s also describe our scenario. We’ll explain the easier, most obvious approach that seemingly could work, but when applied, doesn’t live up to the promise. IT inventory is about storing, in some sort of centralized environment, all of your information and data regarding hardware components. Right now, we’ll target disk drives. Therefore, we want to query for the manufacturer’s name, model number, size, firmware, serial number.

Anyone having experience with scripting or programming on Windows platforms might yell already that WMI is the obvious solution. Windows Management Interface, abbreviated WMI, provides a model through which you can retrieve management information of various hardware components since it is a set of extensions to the driver model. Querying the WMI data for HDDs is possible with just a few lines of code.

Therefore, once you face a situation with requirements like ours, you will start to look for third party utilities or available API to write a script or a tiny, nifty utility to get the job done. There are numerous solutions that you can find, which are built on top of WMI. Unfortunately, in a real-world situation, these all fail miserably. Out of hundreds of PCs within a domain, WMI turned out to be of no use.

If you doubt the practicality of WMI functions, then here’s the data you’ll need to try it out yourself. The hard drive model number is found within the Win32_DiskDrive class, while the serial number is in Win32_PhysicalMedia. And there’s a published article featuring source code written in C# based on WMI. Been there, done that. That solution did not suffice for our needs at all. So we looked further.

On the next page we will explain the approach of a solution that works.

{mospagebreak title=The Approach}

Soon after realizing that almost every snippet of information that could be found about inventorying disk drives is based on WMI and knowing that this just doesn’t work appropriately, we found consolation in the following article published on CodeProject: link. The author of that article explains that “other” solutions don’t work because they’re all based on WMI, and in order to really query the necessary info, you need low-level APIs.

That article is based on the source code of a utility found here. The thing is, the author redesigned and built an entire project in C# on that promising solution (C++). In its essence, the application sends low-level commands to the disk drives, which are answered appropriately, through the DeviceIoControl API. Downloading and trying out the final product within that CodeProject article, the results were positive: it works!

All right, half of the battle is won by now. However, what we want to accomplish is basically querying the disk drives of hundreds of computers contained within a domain. Doing this remotely requires a console-based interface as well. Therefore, once again, using that working application as a foundation, there was a need to develop a console-based tool that could later be deployed and executed on PCs.

The code requires .NET Framework 2.0, but this won’t become an obstacle, since nowadays every workstation machine has this, starting from Windows XP SP2. The author of that CodeProject article makes the information available via a .NET collection. And everything that’s required is “put together” into a dynamic link library (.dll).

All of the above made it quite simple for us to build a console-based application that implements that dynamic link library, queries the data we need and shapes it into a form that we could process seamlessly. Executing on remote computers can be done with PsExec—part of PsTools. It’s a nifty tool that allows remote execution of command-line processes. Surely, system administrators are already more than familiar with it since we’re using it every so often to accomplish quick tasks remotely.

The programming language we’ve chosen for our solution was also C#. Thus, we’ve made a new project, called it QueryHDD, that we’re going to build on the DriveInfoEx.dll dynamic link library file. Keep in mind that we want to keep our application simple. It should not be bloated at all; it needs to be console-based, and preferably launched without any extra arguments.

The following namespaces should more than suffice:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Text;

using System.Threading;

using System.IO;<

using System.Net;

using IOEx;

We’ve mentioned earlier that the utility requires .NET Framework 2.0. This should not cause problems, but we need to implement error-handling regarding this. Thus, we’re simply checking for its existence at the beginning of the execution; if it cannot be found, then an error message is displayed. Otherwise, the application moves on. The error message is received by the admin that executed the script remotely. So it’s silent.

bool hasDotNet = false;

 

if( File.Exists(

"C:WINDOWSMicrosoft.NETFrameworkv2.0.50727csc.exe") ||

File.Exists(

"C:WINNTMicrosoft.NETFrameworkv2.0.50727csc.exe"))

{

hasDotNet = true; }

After this we move on only if the variable hasDotNet is true. On the next page we will actually format the shape of the output into a clean and understandable log.

{mospagebreak title=The Solution}

First, let’s fire up the DriveInfoEx collection. Query for the necessary information, such as the current user that executes the script, computer name, and its local IP address.

DriveListEx m_list;

m_list = new DriveListEx();

m_list.Load();

string currUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name.ToString();

string currComp = Environment.MachineName;

IPHostEntry ipEntry = Dns.GetHostEntry(currComp);

IPAddress[] addr = ipEntry.AddressList;.AddressList;

All right, it’s time to make a silly assumption: what if the computer does not have any disk drives? Okay, this is more than likely only if the application doesn’t work for some reason on that machine and the detection fails. But we also need to cover this situation. Here’s a quick remark that comes up should this case occur.

if (m_list.Count < 1)

{ // nothing to do if there are no disk drives detected

Console.WriteLine("Could not find any disk drive attached to the system.");

Console.WriteLine("Nothing left to do. Done."); }

It’s time to report the data we gathered earlier regarding the computer name and all that. Please style the output the way you want. This is just a sample that should suit certain situations. And it also looks clean.

Console.WriteLine("Running the script on the following machine: {0} @ {1}", currUser, currComp);

Console.WriteLine("IP Address of the machine: {0}", addr[0].ToString());

Console.WriteLine(" ");

Console.WriteLine("Querying Hard Disk Drive Information…");

Console.WriteLine(" ");

Thread.Sleep(500); // sleep to ensure nothing goes wrong – not necessary

And here’s the real deal. Check it out. It prints out the data we need from the DriveInfoEx collection we initialized earlier: disk size, buffer size, model number, drive type, firmware, serial number, and whether the script executed successfully (this is good for confirmation).

Console.WriteLine("Found {0} disk drive(s) in the system.", m_list.Count);

Console.WriteLine(" ");

 

for (int i = 0; i < m_list.Count; i++)

{

Console.WriteLine("Information of the disk drive number {0} [{1}]", i+1, currComp);

Console.WriteLine("————————————–");

Console.WriteLine("Entire size: {0} bytes, meaning {1} GB", m_list[i].DriveSize/10, m_list[i].DriveSize/1000000000);

Console.WriteLine("Buffer size: {0} bytes, meaning {1} MB", m_list[i].BufferSize, m_list[i].BufferSize/1000000);

Console.WriteLine("Model number: {0}", m_list[i].ModelNumber);

Console.WriteLine("Drive type: {0}", m_list[i].DriveType);

Console.WriteLine("Firmware: {0}", m_list[i].RevisionNumber);

Console.WriteLine("Serial number: {0}n", m_list[i].SerialNumber);

}

 

Console.WriteLine("Successful execution on {0} [{1}]. Done.", currComp, addr[0].ToString());

Here’s a sample output of the script remotely executed on JEFFPC. It is important that the name of the computer as well as its IP address is pointed out in numerous places in the output, because when the script is executed on hundreds of computers and the output is verbosely logged in a text file, you can easily process the data later on. Of course, we advise customizing the output; this is just one sample that fit our needs.

Running the script on the following machine: ourfirmadmin @ JEFFPC

IP Address of the machine: 10.10.100.25

Querying Hard Disk Drive Information…

 

Found 1 disk drive(s) in the system.

 

Information of the disk drive number 1 [JEFFPC]

————————————–

Entire size: 16004188569 bytes, meaning 160 GB

Buffer size: 8388608 bytes, meaning 8 MB

Model number: Maxtor 6V160F0

Drive type: Fixed

Firmware: VA123456

Serial number: V30A2MCG

 

Successful execution on JEFFPC [10.10.100.25]. Done.

We’ve left out some snippets of the entire source code, such as the Program part at the beginning, the main function headline, its closure (those brackets, you know), and such. However, if you don’t have C# coding experience, then you can also download the entire project in archived format on the final page.

{mospagebreak title=Final Thoughts}

The previous pages lead you through the steps required to develop a quick console-based application that retrieves the necessary information to inventory disk drives. It is built on a dynamic link library that is freely available and can be used as a foundation. Now it’s time to explain in a few easy steps how to execute the script remotely.

At the beginning, as a preliminary step, we need to deploy the DriveInfoEx.dll on the target client machines. The most obvious place to copy it is into the System32 folder inside the %windir%, so that it’s located near every other DLL. This can be done with a quick batch file that is then executed via PsExec on all client machines. It assumes that the source .dll is reachable for the public via a server within the domain.

@echo off

copy /Y "ourfirm.netdfsTempDriveInfoEx.dll" %WINDIR%system32

echo done

Once this step is done, all of the client machines should have this DLL. Therefore, we can remotely execute with PsExec the application we developed in C# on all of the machines. This can be done with the * wildcard to run on every computer in a domain or using a text file if just a few computers are targeted. For more information regarding PsExec just read its help. It has plenty of features for every system administrator.

psexec @mycomps.txt -u ourfirmadmin -c -n 10 QueryHDD.exe > logged.txt

In the example above, mycomps.txt contains a list of computers on which the script ought to be executed; the –u is followed by the domain admin account inside the so-called “ourfirm” domain; –c means it copies the file prior execution to the target PC; the –n argument specifies a ten second timeout (this is plenty); and the > argument logs the output into a text file.

Keep in mind that nothing will appear on the screen once you press enter on the above command to launch PsExec. But you still need to enter your domain administrator password, even though you won’t be able to see it being asked—this is because the output is logged into the specified text file, and not on the screen. So don’t worry, that’s just the way it works.

You can download this project in archived format by clicking on the button below.

QueryHDD_source.zip

Lastly, I’d like to thank Decebal Mihailescu for his publication over at CodeProject.

In closing, I’d like to invite you to join our experienced community of technology professionals on all areas of IT&C starting from software and hardware up to consumer electronics at Dev Hardware Forums. As well, be sure to check out the community of our sister site at Dev Shed Forums. We are friendly and we’ll do our best to help you.

[gp-comments width="770" linklove="off" ]