某些XP系统上的IO异常获取WMI磁盘信息

时间:2014-07-17 17:02:56

标签: vb.net ioerror

问题

两台机器显示运行时错误:IO Exception The device is not ready。这只发生在我测试过的8台机器中的2台。

这些是从SBS 2003到Windows XP,Vista,7,8,8.1,Server 2012的服务器版本。它的范围很广。

有问题的两台机器是:

  • Windows Server 2003 SP2 (已安装.NET Framework V4 / V4.0)
  • Windows XP SP3 (已安装.NET Framework V4 / V4.0

请注意:我在虚拟机上安装了Windows XP,安装了.NET framework 4.0,程序运行正常。

我的调查和测试

开始我的应用程序的目标是.NET framework 4.0,所有引用的外部.DLLS都包含在应用程序启动文件夹中。

从研究中我确定错误与驱动器访问有关。在我的应用程序中有两个实例,我专门查询设备的系统驱动器。一旦抓住磁盘空间而另一个抓住串口。

所以我创建了两个程序,一个具有我用来获取磁盘空间的功能,另一个具有我用来获取HDD Serial的功能。

我在工作机器上运行了两个程序,然后看到一个消息框显示有可用磁盘空间和HDD串行(这并不奇怪。)

我在显示IO错误的上述机器上尝试过,我收到(对于这两个应用程序) programname.exe is not a valid Win32 application.

^这很奇怪吧?

以下是有关的两个功能。

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

是" C"是两台机器的驱动器号。

修改

我将其中一个IO测试定位到.NET Framework 4.0 Client Profile并运行了!虽然有例外,请参阅下面的粘贴箱。

http://pastebin.com/FRngUeBN

1 个答案:

答案 0 :(得分:4)

由于这些是旧机器,因此有几件事情可以发挥作用。我有一台XP机器(用于Civ3),它也失败了,其中一个调用的IO异常。我为另一个推断。

对于VolumeSerial,它首先尝试WMI,然后在API失败时轮询API。对于Freespace,我删除了所有VisualBasic调用,并将其替换为对DriveInfo的NET调用,然后在NET无法传递的情况下调用GetDiskFreeSpaceEx

守则(我建议所有这一类):

进口

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

如果DriveSpace的两个选项都失败了,它会返回-1,这显然是非法的值。我这样做而不是抛出异常。 格式化返回应该与获取返回分开,这将使评估返回更容易,但我保留了它的方式。

我更改了调用以要求驱动器盘符轮询以帮助消除一些错误,例如尝试轮询不存在的驱动器。可以轻松修改WMI版本以在第一个固定磁盘甚至“Windows”驱动器上运行,具体取决于它的用途。

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

卷序列分为2个过程。主要公众:

' 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

WMI无法完成工作时的私人助手:

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

怀疑基础问题是Drives.Item(0)处的旧软盘驱动器。代码没有检查它们是否是FixedDrives,也没有检查该驱动器是否准备就绪。这是此需要DriveLetter的部分原因。您可以轻松更改它以获取第一个固定磁盘或Windows驱动器的VolumeSerial,具体取决于它的用途。

四个程序有点过分,2 API版本应该始终有效,但由于我们讨论的是相当旧的系统,故障保护看起来并不是一个坏主意。 另外,正如我所说,我只能重现其中一个错误,所以一个多管齐下的解决方案似乎是谨慎的。