由 SUBST (Windows 10) 创建的驱动器未报告 QueryDosDevice() 的完整路径

Drive created by SUBST (Windows 10) Not Reporting Full Path to QueryDosDevice()

编辑 - 看起来我找到了问题

对不起。显然这都是我的错。有关详细信息,请参阅下面我自己写的答案。


我正在对一个 SUBST-ed 驱动器进行测试,该驱动器包含在一个更大的方法中,该方法测试一个字符串以确保它(至少,可能)是一个有效路径。经过一些验证后,"encapsulating" 方法将字符串转换为 UNC 或绝对路径,具体取决于传入的路径,到其他地方的 return。

举几个例子:

到目前为止,其中大部分似乎都运行良好并且符合预期,但是我遇到了一个关于 Windows 10 中的 SUBST 命令创建的驱动器的问题(至少 - 我目前还没有 运行 在另一个 OS 下进行任何测试,因为我现在没有另一个 ).

老实说,我对 SUBST 命令没有太多经验,也不经常使用它,所以,一开始,我什至无法正确显示驱动器在 Windows。阅读 Microsoft 社区页面上的讨论后(Windows 10 issue "Subst" command doesn't work), I was finally able to get the drive set up "properly" (don't use an elevated command prompt, BTW), but the code I'm using to test for a SUBST-ed drive - converted to VB.NET from this answer - 仍未正确解析完整路径。

这是我正在使用的转换后的代码(我打算稍后再做一些 "tweaking",但这是当前状态):

<DllImport("kernel32.dll", SetLastError:=True)>
Private Shared Function QueryDosDevice(ByVal lpDeviceName As String, ByVal lpTargetPath As System.Text.StringBuilder, ByVal ucchMax As Integer) As UInteger
End Function

Private Shared Function IsSubstPath(ByVal pathToTest As String, <Out> ByRef realPath As String) As Boolean
    Dim PathInformation As System.Text.StringBuilder = New System.Text.StringBuilder(260)
    Dim DriveLetter As String = Nothing
    Dim WinApiResult As UInteger = 0

    realPath = Nothing

    Try
        ' Get the drive letter of the path
        DriveLetter = IO.Path.GetPathRoot(pathToTest).Replace("\", "")
    Catch ex As ArgumentException
        Return False
    End Try

    WinApiResult = QueryDosDevice(DriveLetter, PathInformation, 260)

    If WinApiResult = 0 Then
        Dim LastWinError As Integer = Marshal.GetLastWin32Error()

        Return False
    End If

    ' If the drive is SUBST'ed, the result will be in the format of "\??\C:\realPath\".
    If PathInformation.ToString().StartsWith("\??\") Then
        Dim RealRoot As String = PathInformation.ToString().Remove(0, 4)

        RealRoot += If(PathInformation.ToString().EndsWith("\"), "", "\")
        realPath = IO.Path.Combine(RealRoot, pathToTest.Replace(IO.Path.GetPathRoot(pathToTest), ""))

        Return True
    End If

    realPath = pathToTest
    Return False
End Function

我这样称呼我使用 SUBST H: D:\substtest:

创建的驱动器
Dim TestFile As New IO.FileInfo("H:84\CPI.TXT")
Dim SubstPath As String = String.Empty
Dim FullPath As String = String.Empty

If IsSubstPath(FullPath, SubstPath) Then
    FullPath = SubstPath
End If

我的期望是 IsSubstPath() 方法 应该 return D:\substtest84\CPI.TXT 通过 realPath 变量。执行 SUBST(没有附加参数)在命令提示符 (H:\: => D:\substtest) 中正确显示映射。在调试时检查 TestFile 对象显示它是 Exists() 属性 returns True,所以文件系统显然知道它在那里。

此时,每次我执行代码时,QueryDosDevice() 方法调用 return 的值为 0,尽管我从 Marshal.GetLastWin32Error() 中得到不同的结果在我继续尝试使它正常工作时打电话。

在我的机器上安装 SUBST-ed 驱动器 "properly" 后,我的第一次尝试导致了 Marshal.GetLastWin32Error() returning 错误代码 1008 - ERROR_NO_TOKEN (“试图引用不存在的令牌”)。

进一步阅读链接的 MS 社区线程表明 运行ning SUBST twice - once in一个普通的命令提示符,以及 again 在提升的命令提示符中 - should 使驱动器可供常规登录用户以及任何提升的用户使用用户操作。我在提升的命令提示符中重新 运行 SUBST 并使用与上面相同的测试代码再次尝试。这一次,Marshal.GetLastWin32Error() returned 错误代码 6 - ERROR_INVALID_HANDLE ("The handle is invalid.").

认为此特定操作 可能 取决于系统上实际存在的 file/path(与 .NET IO.FileInfoIO.DirectoryInfo 对象),我手动创建了特定的子文件夹和文件来表示我在代码中测试的内容(H:84\CPI.TXT)并再次尝试(再次使用与上面相同的代码):

再一次,QueryDosDevice() 未能正确解析真实路径(returned 0),但这次 Marshal.GetLastWin32Error() 方法 return ed 的值为 0 - ERROR_SUCCESS ("The operation completed successfully.")。考虑到,也许代码中有一些 "flaw" 可能无意中跳过了一个步骤或其他东西,我检查了 PathInformation 变量 - Text.StringBuilder 对象保存 QueryDosDevice() - 处于中断模式,但是,唉,它也是空的。

注意:我也尝试使用目录而不是文件,但是 H:84\ 导致 Marshal.GetLastWin32Error() return 值为 0H:84 的结果为 6。根据之前的测试,这一切都是有道理的,但它仍然会导致一个空的 PathInformation 变量(失败)。

阅读 Interwebz 的所有内容,似乎很多人都遇到了 Windows 10 下 SUBST-ed 驱动器的各种问题,所以我现在想知道是否 这就是 这些意外结果的原因。还有其他人遇到过这些问题吗?如果遇到过,您是否能够用代码解决它们?

万一它很重要(我想它肯定会),这里有一些额外的细节:

如果我遗漏了什么,或者您需要进一步说明,请在评论中告诉我,我会根据需要更新问题。

DOUBLE/TRIPLE/QUADRUPLE 检查转换后的代码

看起来这一切都归结为我没有足够关注的一行代码(上面代码块中的第 14 行):

DriveLetter = IO.Path.GetPathRoot(PathToTest).Replace("\", "")

这里的问题是 Path.GetPathRoot() 方法 returns 格式为 H:\ 的盘符 - 只有一个 反斜杠 (\),所以 .Replace() 方法没有找到任何要替换的东西,并传递了一个 "invalid" 参数值(H:\ 而不是 H:)。如果尾随反斜杠存在,QueryDosDevice() 方法显然会失败,因此我进行了快速代码编辑:

DriveLetter = IO.Path.GetPathRoot(PathToTest).Replace("\", "") ' <--Replace ANY/ALL backslashes

我再次用我的 SUBST H: D:\substtest 驱动器和现有 file/directory 结构进行了测试。这次,QueryDosDevice() 方法返回了 19 的值,并正确解析了真实路径为 D:\substtest84\CPI.TXT.

然后,我删除了我为测试创建的 subfolder/file 并再次尝试。同样,它正确返回了真实路径 D:\substtest84\CPI.TXT。所以,显然这一切都归结为我忽略了在我将代码从 C# 转换为 VB.NET 期间引入的 "typo"。我很抱歉。转换后代码的完整修正版本

<DllImport("kernel32.dll", SetLastError:=True)>
Private Shared Function QueryDosDevice(ByVal lpDeviceName As String, ByVal lpTargetPath As System.Text.StringBuilder, ByVal ucchMax As Integer) As UInteger
End Function

Private Shared Function IsSubstPath(ByVal pathToTest As String, <Out> ByRef realPath As String) As Boolean
    Dim PathInformation As System.Text.StringBuilder = New System.Text.StringBuilder(260)
    Dim DriveLetter As String = Nothing
    Dim WinApiResult As UInteger = 0

    realPath = Nothing

    Try
        ' Get the drive letter of the path without the trailing backslash
        DriveLetter = IO.Path.GetPathRoot(pathToTest).Replace("\", "")
    Catch ex As ArgumentException
        Return False
    End Try

    WinApiResult = QueryDosDevice(DriveLetter, PathInformation, 260)

    If WinApiResult = 0 Then
        Dim LastWinError As Integer = Marshal.GetLastWin32Error()

        Return False
    End If

    ' If the drive is SUBST'ed, the result will be in the format of "\??\C:\realPath\".
    If PathInformation.ToString().StartsWith("\??\") Then
        Dim RealRoot As String = PathInformation.ToString().Remove(0, 4)

        RealRoot += If(PathInformation.ToString().EndsWith("\"), "", "\")
        realPath = IO.Path.Combine(RealRoot, pathToTest.Replace(IO.Path.GetPathRoot(pathToTest), ""))

        Return True
    End If

    realPath = pathToTest
    Return False
End Function

正如我在问题中所说,我打算对这种方法做一些 "tweaking",但是这种 确实 有效(现在)。