vb.net - 某些 XP 系统获取 WMI 磁盘信息时出现 IO 异常

标签 vb.net ioerror

问题

两台机器显示运行时错误:IO Exception The device is not read。我测试过的 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,所有引用的外部 .DLL 都包含在应用程序启动文件夹中。

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

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

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

我在上述显示 IO 错误的计算机上进行了尝试,并且收到了(对于两个应用程序) programname.exe 不是有效的 Win32 应用程序。

^ 这很奇怪吧?

这是有问题的两个函数。

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,并且它运行了!尽管有异常(exception),请参阅下面的粘贴箱。

http://pastebin.com/FRngUeBN

最佳答案

由于这些都是旧机器,因此可能有几个因素在起作用。我有一台 XP 机器(适用于 Civ3),它也因其中一个调用出现 IO 异常而失败。我推断了另一个。

对于 VolumeSerial,它首先尝试 WMI,如果失败则轮询 API。对于 Freespace,我摆脱了所有 VisualBasic 调用,并将其替换为对 DriveInfo 的 NET 调用,然后在 NET 无法交付时调用 GetDiskFreeSpaceEx

代码(我建议为所有这些创建一个类):

导入

Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Text

native 方法:

<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)助手:

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) 处的旧软盘驱动器。该代码不会检查它们是否是固定驱动器,也不会检查该驱动器是否准备就绪。这是需要驱动器盘符的部分原因。您可以轻松更改它以获取第一个固定磁盘或 Windows 驱动器的 VolumeSerial,具体取决于其用途。

四个过程有点过分了,2 API 版本应该总是可以工作,但由于我们谈论的是相当旧的系统,所以故障保护似乎不是一个坏主意。 另外,正如我所说,我只能重现其中一个错误,因此多管齐下的解决方案似乎是谨慎的。

关于vb.net - 某些 XP 系统获取 WMI 磁盘信息时出现 IO 异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24809487/

相关文章:

.net - 从 x86 应用程序获取 x64 进程主模块位置?

python - 在 PIL 中打开 base64 字符串时出现奇怪的 IOError

postgresql - PostgreSQL启动错误: “Could not flush dirty data: Input/output error”

Python 的 "open()"为 "file not found"引发不同的错误 - 如何处理这两个异常?

.net - ‘WPF Ribbon Application’ 没有项目模板

vb.net - ArrayList 搜索 .net

vb.net - 当控件的内容与其可见边界重叠时显示滚动条

asp.net-mvc - 在 ASP.NET MVC 中的 Controller 内生成 URL

使用 cPickle 将大型数组写入网络驱动器时出现 Python "IOError: [Errno 22] Invalid argument"

python - 在 Python 中写入具有 sudo 权限的文件