The Problem
Two machines display the runtime error: IO Exception The device is not ready
. This only happens for 2 out of 8 machines I've tested it on.
These are server versions from SBS 2003 to Windows XP, Vista, 7,8,8.1, Server 2012. It's a broad range.
The two machines in question are:
Please note: I installed Windows XP fresh on a VM, installed .NET framework 4.0 and the program ran without error.
My Investigation and Testing
To start things of my application is targeted to .NET framework 4.0 and all referenced external .DLLS are included in the application start folder.
From research I determined that the error was related to drive access. In my application there is two instances where I specifically query the system drive of the device. Once to grab disk space and the other to grab the serial.
So I created two programs, one has the function I use to grab diskspace and the other has the function I use to grab the HDD Serial.
I ran both programs on the work machines and low and behold a message box was displayed with free disk space and the HDD serial (no surprise there.)
I tried it on the above machines that display IO errors and I receive (for both applications) programname.exe is not a valid Win32 application.
^ That's weird right?
Here are the two functions in question.
Public Shared Function getHardwareID() As String
Dim drive As String = "C"
Dim disk As ManagementObject = _
New ManagementObject _
("win32_logicaldisk.deviceid=""" + drive + ":""")
disk.Get()
Return disk("VolumeSerialNumber").ToString()
End Function
Public Shared Function getFreeDiskSpace() As String
Dim freespacekb = My.Computer.FileSystem.Drives.Item(0).AvailableFreeSpace.ToString
freespacekb = Format(freespacekb / 1024 / 1024 / 1024, "#0.00") _
& " GB Free"
Return freespacekb.ToString
End Function
Yes "C" is the drive letter for both machines.
EDIT:
I targeted one of the IO Tests to .NET Framework 4.0 Client Profile and it ran! Although with an exception, see paste bin below.
Since these are old machines, there are several things which may be at play. I have an XP machine (for Civ3) and it too failed with an IO Exception on one of the calls . I extrapolated for the other.
For the VolumeSerial, it tries WMI first, then polls the API if that fails. For Freespace, I got rid of all the VisualBasic calls and replaced it with NET calls to DriveInfo
and then a call to GetDiskFreeSpaceEx
if NET cant deliver.
The Code (I suggest a class for all this):
Imports
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Text
NativeMethods:
<DllImport("Kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function GetVolumeInformation(ByVal RootPathName As String,
ByVal VolumeNameBuffer As System.Text.StringBuilder,
ByVal VolumeNameSize As UInt32,
ByRef VolumeSerialNumber As UInt32,
ByRef MaximumComponentLength As UInt32,
ByRef FileSystemFlags As UInt32, _
ByVal FileSystemNameBuffer As System.Text.StringBuilder,
ByVal FileSystemNameSize As UInt32
) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function GetDiskFreeSpaceEx(lpDirectoryName As String,
ByRef lpFreeBytesAvailable As ULong,
ByRef lpTotalNumberOfBytes As ULong,
ByRef lpTotalNumberOfFreeBytes As ULong
) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<Flags>
Private Enum FileSystemFeature As UInteger
CaseSensitiveSearch = 1
CasePreservedNames = 2
UnicodeOnDisk = 4
PersistentACLS = 8
FileCompression = &H10
VolumeQuotas = &H20
SupportsSparseFiles = &H40
SupportsReparsePoints = &H80
VolumeIsCompressed = &H8000
SupportsObjectIDs = &H10000
SupportsEncryption = &H20000
NamedStreams = &H40000
ReadOnlyVolume = &H80000
SequentialWriteOnce = &H100000
SupportsTransactions = &H200000
End Enum
If both alternatives fail for DriveSpace, it returns -1, an obviously illegal value. I did this instead of throwing an exception. Formatting the return should really be separate from getting the return which would make evaluating the return easier, but I left it the way you had it.
I changed the calls to require a drive letter to poll to help eliminate some errors like trying to poll a nonexistant drive. The WMI versions could easily be modified to work on first fixed disk or even the 'Windows' drive, depending on what this is for.
Public Shared Function getFreeDiskSpace(drvName As String) As String
' since this fails on just some machines
' AND the machines mentioned are old, they may well have floppy drives
' failure signal
Dim freespace As Double = -1
Dim myFreeBytesAvailable As ULong
Dim myTotalNumberOfBytes As ULong
Dim myTotalNumberOfFreeBytes As ULong
If String.IsNullOrEmpty(drvName) Then
Return freespace.ToString
End If
' try the NET method
Dim di As DriveInfo() = DriveInfo.GetDrives
Try
For Each drv In di
' look for desired drive in list
If drv.Name.ToLowerInvariant.StartsWith(drvName.ToLowerInvariant(0)) Then
' apply your desired logic...
' check only fixed drives for this use?
' at least check if they are ready
'If drv.DriveType = DriveType.Fixed
' If drv.IsReady
' min test
If drv.DriveType = drv.IsReady Then
freespace = drv.TotalFreeSpace
End If
Exit For
End If
Next
Catch ex As Exception
End Try
' no answer yet, ask the API; should always work on valid drives
If freespace = -1 Then
' format drv letter to name
Dim drvpath As String = String.Format("{0}:\", drvName.ToLowerInvariant(0))
' call API
If GetDiskFreeSpaceEx(drvpath, myFreeBytesAvailable,
myTotalNumberOfBytes, myTotalNumberOfFreeBytes) Then
freespace = myFreeBytesAvailable
End If
End If
' format bytes
If freespace > -1 Then
freespace = freespace / 1024 / 1024 / 1024
End If
' ret string
Return freespace.ToString("#00.00 Gb Free")
End Function
The Volume serial is broken into 2 procs. The main public one:
' again, call with drive letter "C"
Public Shared Function getHardwareID(drvLtr As String) As String
Dim volSerial As String = ""
Dim drive As String = (drvLtr.ToLower)(0)
Try
Dim disk As ManagementObject = _
New ManagementObject("win32_logicaldisk.deviceid=""" + drive + ":""")
disk.Get()
volSerial = disk("VolumeSerialNumber").ToString()
Catch ex As Exception
' call private API version for help
volSerial = GetVolumeSerialFromAPI(drive)
Finally
End Try
Return volSerial
End Function
Private helper when WMI cant do the job:
Private Shared Function GetVolumeSerialFromAPI(driveLetter As String) As String
' format drive letter
Dim myDrvLtr As String = (driveLetter(0) & ":\").ToLower
' allocate space
Dim volname As New StringBuilder(261)
Dim fsName As New StringBuilder(261)
Dim sernum, maxlen As UInt32
' out_Flags
Dim flags As FileSystemFeature
If GetVolumeInformation(myDrvLtr, volname, volname.Capacity,
sernum, maxlen, flags,
fsName, fsName.Capacity) Then
' format to Hex like WMI
Return sernum.ToString("X")
Else
Return ""
End If
End Function
I suspect the underlying issue is old floppy drives at Drives.Item(0)
. The code did not check if they were FixedDrives nor if that drive was ready. This is part of the reason this one requires a DriveLetter. You could easily change it to get the VolumeSerial for the First Fixed disk or Windows Drive depending on what this is for.
Four procedures is a little bit overkill, the 2 API version should always work, but since we are talking about pretty old systems, a failsafe doesn't seem like a bad idea. Plus, as I said I could only reproduce one of the errors, so a multi-pronged solution seems prudent.