确定已挂载的TrueCrypt卷的驱动器字母

7
在将TrueCrypt容器挂载到驱动器号后,是否可以在批处理文件中确定该驱动器号是从哪个容器挂载而来的,或者容器被挂载到了哪个驱动器号?
在批处理文件中,我想将指定的TrueCrypt容器挂载到指定的驱动器号。如果容器已经挂载或者驱动器号不可用,则TrueCrypt会出现错误,因此我希望仅在未将指定的容器挂载到指定的驱动器号时才运行TrueCrypt,也就是只有当操作尚未完成时才运行。
任何建议将不胜感激。
编辑
奖励摘要:简单来说,假设您已将卷C:\Vol1.tc和C:\Vol2.tc挂载到驱动器X和Y上。如何使用批处理文件或C#代码编程地告诉您C:\Vol1.tc已挂载到驱动器X,C:\Vol2.tc已挂载到驱动器Y?
6个回答

9
一种方法是直接询问Truecrypt驱动程序本身。这可以通过DeviceIoControl函数实现。事实上,这正是TrueCrypt GUI所做的。
请注意,在c++中更容易完成这项任务。 在这里您会找到一篇好文章
思路是调用DeviceIoControl函数,请求TC_IOCTL_GET_MOUNTED_VOLUMES,您将获得一个结构体,其中包含所有已挂载卷的路径和驱动器号。实际上,它是一个由26个元素组成的数组(每个可能的驱动器号一个),称为wszVolume,其中包含已挂载的TrueCrypt卷的路径。
希望以下示例代码能帮助您解决问题。
C#示例代码:
class Program
{
    static void Main(string[] args)
    {
        uint size = (uint)Marshal.SizeOf(typeof(MOUNT_LIST_STRUCT));
        IntPtr buffer = Marshal.AllocHGlobal((int)size);
        uint bytesReturned;
        IntPtr _hdev = CreateFile("\\\\.\\TrueCrypt", FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
        bool bResult = DeviceIoControl(_hdev, TC_IOCTL_GET_MOUNTED_VOLUMES, buffer, size, buffer, size, out bytesReturned, IntPtr.Zero);
        MOUNT_LIST_STRUCT mount = new MOUNT_LIST_STRUCT();
        Marshal.PtrToStructure(buffer, mount);
        Marshal.FreeHGlobal(buffer);

        for (int i = 0; i < 26; i++)
            Console.WriteLine("{0}: => {1}", (char)('A' + i), mount.wszVolume[i]);
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    class MOUNT_LIST_STRUCT
    {
        public readonly UInt32 ulMountedDrives; /* Bitfield of all mounted drive letters */
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)]
        public readonly MOUNT_LIST_STRUCT_VOLUME_NAME[] wszVolume;  /* Volume names of mounted volumes */
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)]
        public readonly UInt64[] diskLength;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)]
        public readonly int[] ea;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)]
        public readonly int[] volumeType;   /* Volume type (e.g. PROP_VOL_TYPE_OUTER, PROP_VOL_TYPE_OUTER_VOL_WRITE_PREVENTED, etc.) */
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    struct MOUNT_LIST_STRUCT_VOLUME_NAME
    {
        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I2, SizeConst = 260)]
        public readonly char[] wszVolume;   /* Volume names of mounted volumes */

        public override string ToString()
        {
            return (new String(wszVolume)).TrimEnd('\0');
        }
    }

    public static int CTL_CODE(int DeviceType, int Function, int Method, int Access)
    {
        return (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2)
          | (Method));
    }
    private static readonly uint TC_IOCTL_GET_MOUNTED_VOLUMES = (uint)CTL_CODE(0x00000022, 0x800 + (6), 0, 0);

    [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
    static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode,
    IntPtr lpInBuffer, uint nInBufferSize,
    IntPtr lpOutBuffer, uint nOutBufferSize,
    out uint lpBytesReturned, IntPtr lpOverlapped);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
         [MarshalAs(UnmanagedType.LPTStr)] string filename,
         [MarshalAs(UnmanagedType.U4)] FileAccess access,
         [MarshalAs(UnmanagedType.U4)] FileShare share,
         IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero
         [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
         [MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
         IntPtr templateFile);
}

1
这是我迄今为止所拥有的:
我正在创建一个用C#编写的自定义应用程序,它能告知用户哪些卷被挂载/卸载,如果它们被挂载,我需要告诉用户这些驱动器的位置。为了知道一个卷是否被挂载,我有以下类:
注意我使用了handle.exe程序,该程序可以从以下链接http://technet.microsoft.com/en-us/sysinternals/bb896655.aspxhttp://download.sysinternals.com/files/Handle.zip下载。
另外,我认为您必须以管理员身份运行该程序。
class TrueCryptHelp
{
    // I have that program on the working directory
    // it can be downloaded from http://technet.microsoft.com/en-us/sysinternals/bb896655.aspx
    const string HandleExeLocation = "handle.exe"; 
    static string systemProcessFiles;
    static DateTime dateFilesLockedInfo = new DateTime();
    static string SystemProcessFiles
    {
        get
        {
            if ((DateTime.Now - dateFilesLockedInfo).TotalSeconds > 2)
            {
                Process p = new Process();
                var psi = new ProcessStartInfo();
                psi.RedirectStandardOutput = true;
                psi.UseShellExecute = false;
                psi.FileName = HandleExeLocation;
                p.StartInfo = psi;
                p.Start();

                var output = p.StandardOutput.ReadToEnd();

                systemProcessFiles = string.Empty;

                foreach (Match m in Regex.Matches(output ?? "", @"(?sx) -{20}  [^-]  .+?  -{20}"))
                {
                    if (Regex.Match(m.Value ?? "", @"(?xi) -{10}  [\s\r\n]+  System \s pid").Success)
                    {
                        if (Regex.Match(m.Value ?? "", @"(?xi)  \) \s+ \\clfs \s* (\r|\n)").Success)
                        {
                            systemProcessFiles = m.Value.ToLower();
                            break;
                        }
                    }
                }

            }

            dateFilesLockedInfo = DateTime.Now;

            return systemProcessFiles;
        }
    }

    public static bool IsVolumeMounted(string volumeLocation)
    {
        //DriveInfo d = new System.IO.DriveInfo(volume.DriveLetter);
        //if (d == null)
        //return false;

        //if (d.DriveType != System.IO.DriveType.Fixed)
        //return false;

        //if ((d.DriveFormat ?? "").ToLower().Contains("fat") == false)
        //return false;

        if (SystemProcessFiles.Contains(volumeLocation.ToLower()))
        {
            return true;
        }
        else
        {
            return false;
        }

    }
}

那么,如果我想知道位于 C:\Users\Tono\Desktop\v1.tc 的卷是否已挂载,我将调用以下方法:

var isVolMounted = TrueCryptHelp.IsVolumeMounted(@"A:\Users\Tono\Desktop\v1.tc");

现在我错过了回答问题的机会!通过我发布的类,我能知道位于C:\Users\etc...的卷已经被挂载,但它被挂载到了哪个驱动器盘符下?

这不是一个论坛,收集的信息应该以注释的形式记录。 - user1440897
1
真的很抱歉...只是想发布我所拥有的。 - Tono Nam

1

为了扩展对VeraCrypt的回答,这是从Gerard Walace的帖子修改而来:

在MSFT_Helpers.cs中使用TrueCrypt和VeraCrypt @ https://github.com/BananaAcid/Selfcontained-C-Sharp-WPF-compatible-utility-classes

这里是一个简单的使用示例:http://github.com/BananaAcid/VeraCrypt-Cmd

public static class VcGetMounts
{

    public static async Task<Dictionary<char, string>> getMounted()
    {
        return await Task.Run<Dictionary<char, string>>(() =>
        {
            var ret = new Dictionary<char, string>();

            uint size = (uint)Marshal.SizeOf(typeof(MOUNT_LIST_STRUCT));
            IntPtr buffer = Marshal.AllocHGlobal((int)size);
            uint bytesReturned;
            IntPtr _hdev = CreateFile("\\\\.\\VeraCrypt", FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
            bool bResult = DeviceIoControl(_hdev, TC_IOCTL_GET_MOUNTED_VOLUMES, buffer, size, buffer, size, out bytesReturned, IntPtr.Zero);
            // IMPORTANT! Otherwise, the struct fills up with random bytes from memory, if no VeraCrypt is available
            if (!bResult) return ret;
            MOUNT_LIST_STRUCT mount = new MOUNT_LIST_STRUCT();
            Marshal.PtrToStructure(buffer, mount);
            Marshal.FreeHGlobal(buffer);

            for (int i = 0; i < 26; i++)
                if (mount.wszVolume[i].ToString().Length > 0)
                    ret.Add((char)('A' + i), mount.wszVolume[i].ToString());

            return ret;
        });
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    class MOUNT_LIST_STRUCT
    {
        public readonly UInt32 ulMountedDrives; /* Bitfield of all mounted drive letters */
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)]
        public readonly MOUNT_LIST_STRUCT_VOLUME_NAME[] wszVolume;  /* Volume names of mounted volumes */
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)]
        public readonly MOUNT_LIST_STRUCT_VOLUME_LABEL[] wszLabel;  /* Volume labels of mounted volumes */
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)]
        public readonly MOUNT_LIST_STRUCT_VOLUME_ID[] volumeID;  /* Volume labels of mounted volumes */
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)]
        public readonly UInt64[] diskLength;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)]
        public readonly int[] ea;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)]
        public readonly int[] volumeType;   /* Volume type (e.g. PROP_VOL_TYPE_OUTER, PROP_VOL_TYPE_OUTER_VOL_WRITE_PREVENTED, etc.) */
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)]
        public readonly bool[] truecryptMode;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    struct MOUNT_LIST_STRUCT_VOLUME_NAME
    {
        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I2, SizeConst = 260)]
        public readonly char[] wszVolume;   /* Volume names of mounted volumes */

        public override string ToString()
        {
            return (new String(wszVolume)).TrimEnd('\0');
        }
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    struct MOUNT_LIST_STRUCT_VOLUME_ID
    {
        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I2, SizeConst = 32)]
        public readonly char[] volumeID;   /* Volume ids of mounted volumes */

        public override string ToString()
        {
            return (new String(volumeID)).TrimEnd('\0');
        }
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    struct MOUNT_LIST_STRUCT_VOLUME_LABEL
    {
        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I2, SizeConst = 33)]
        public readonly char[] wszLabel;   /* Volume labels of mounted volumes */

        public override string ToString()
        {
            return (new String(wszLabel)).TrimEnd('\0');
        }
    }

    public static int CTL_CODE(int DeviceType, int Function, int Method, int Access)
    {
        return (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2)
          | (Method));
    }
    private static readonly uint TC_IOCTL_GET_MOUNTED_VOLUMES = (uint)CTL_CODE(0x00000022, 0x800 + (6), 0, 0);

    [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
    static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode,
    IntPtr lpInBuffer, uint nInBufferSize,
    IntPtr lpOutBuffer, uint nOutBufferSize,
    out uint lpBytesReturned, IntPtr lpOverlapped);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
         [MarshalAs(UnmanagedType.LPTStr)] string filename,
         [MarshalAs(UnmanagedType.U4)] FileAccess access,
         [MarshalAs(UnmanagedType.U4)] FileShare share,
         IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero
         [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
         [MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
         IntPtr templateFile);
}

谢谢你!我可以问一下你的许可,以便仅个人使用重新利用其中一些代码吗? - Liquid Core
太棒了!大部分代码可能会放入我正在编写的一个.NET Core库中,该库执行许多常见和不太常见的任务,这些任务是我在日常编码中需要完成的(例如文件系统管理/文件创建/读取、驱动器管理、网络检查、转换等)。这样,我只需将我的DLL放入项目中,就可以避免每次编写乏味的代码。 - Liquid Core
我已经在一个简单的 Windows 窗体中尝试了 VeraCrypt 代码,挂载了一个 VeraCrypt 驱动器,但是这个窗体在2分钟后仍然没有响应。我认为下面的某些东西没有正常工作。我错过了什么吗? - Liquid Core
调用 Dictionary<char, string> infosVc = await VcGetMounts.getMounted(); 无响应?通常是因为您正在等待一个长时间运行的任务... 您是否将其挂载在路径上,而不是驱动器字母上?我没有测试过。顺便说一句 - 您可以使用相同的昵称在 Skype 上联系我。 - BananaAcid
这应该是即时的。 - BananaAcid
显示剩余7条评论

0

我可能过于简化了,但是挂载驱动器的驱动器标签是唯一的吗?如果是这样,您可以使用简单的查找,例如:

private string GetDriveLetter(string volumeLabel)
{
    string driveLetter = "";

    DriveInfo[] dis = DriveInfo.GetDrives();
    foreach (DriveInfo di in dis)
    {
        var dt = di.DriveType;
        if (dt == DriveType.Fixed || dt == DriveType.Removable)
        {
            if (di.VolumeLabel == volumeLabel)
            {
                driveLetter = di.Name.Substring(0, 1).ToUpper();
                break;
            }
        }
    }

    return driveLetter;
}

0

只是为了确保我理解正确:您想通过脚本确保TrueCrypt将某个卷挂载到某个驱动器字母,并且如果该卷已经被挂载,它不会尝试执行任何操作。

可能有几个潜在的解决方案。我将尝试突出一些选项;如果有任何值得进一步追求的,请让我知道,我会继续研究并更新更多细节。

选项1:收藏夹和自动挂载

TrueCrypt支持{{link1:“收藏卷”}}。

从页面上看 - 当:

  • 您有一个始终需要挂载到特定驱动器字母的卷。
  • 您有一个容器位于USB闪存驱动器或外部USB硬盘上,需要在连接到计算机时自动挂载卷。
  • 您需要在登录操作系统时自动挂载卷。
  • 您始终需要将某个卷作为只读或可移动介质挂载。

一些值得注意的事情:

  • 每个喜爱的卷都可以应用一个特殊标签--可能很方便
  • 您可以运行"TrueCrypt.exe /a favorites /quit"来自动挂载您喜欢的卷,然后退出。您可能需要测试一下,因为truecrypt收藏夹页面指出:如果已经挂载,将为其打开资源管理器窗口。

选项2:仅检查驱动器号

如果您总是将卷挂载到某个驱动器号上,那么您可以合理地假设没有其他东西会占用该驱动器号。

如果这是一个合理的假设,您可以使用PowerShell检查驱动器:

$DriveLetterToCheck = "s:"
$DriveLetterMounted = Test-Path $DriveLetterToCheck #true if it exists
if(!$DriveLetterMounted)
{
  # Run your TrueCrypt mount command
}

如果我想到更好的方法,我会更新的。如果我走在正确的道路上,请告诉我。


提前澄清一下:我知道要求批处理脚本,但我认为 PowerShell 也很可能以某种方式可用。 - SeanKilleen

0

我对通过查询驱动器号或相反方式确定卷名感到不确定。根据您的需求,可能可以接受的解决方法是查询卷上的文件锁定。如果该卷被锁定,则很有可能被挂载。如果您想更加高级,可以使用 for /f 检查是否由 System 进程特别锁定。

应该提到的是,在调用 truecrypt.exe 时结合使用 /quit background 和 /silent 将在驱动器已经挂载的情况下静默失败。不确定您是否只关心错误显示。不确定您是否注意到了 命令行参考

这里有一个简单的 cmd 脚本,仅在卷还没有挂载时才安装它,从而防止 truecrypt.exe 触发错误。它做了一些幼稚的假设,但应该给出一个思路。

@echo off
setlocal enableextensions enabledelayedexpansion

:: Dependencies: Truecrypt obviously, and sysinternals handles.exe (live.sysinternals.com).

:: To use command line arguments instead of static assignments like these, reference %1 %2 from within script..
set driveletter=x
set yourvolume=c:\temp\your_volume.vol
set yourpassword=your_password
set truecrypt_loc=c:\program files\truecrypt\truecrypt.exe
set handle_loc=c:\temp\handle.exe

:: - check if volume already mounted
"%handle_loc%" -a "%yourvolume%" /accepteula >nul
if %errorlevel% EQU 0 echo This volume is already mounted. && goto :EOF

:: - check if drive letter is in use
if exist %driveletter% echo This drive is already mounted. && goto :EOF

:: - mount tc volume to a specified drive letter
:: silent flag suppresses errors being displayed to user
"%truecrypt_loc%" /letter %driveletter% /password "%yourpassword%" /volume "%yourvolume%" /mountoption rm /quit background /silent

:: - check if drive letter is in use
if exist %driveletter%:\ echo Drive was mounted successfully as %driveletter%: && goto :EOF
echo Drive could not be mounted.

请注意,由于某些TrueCrypt版本中可能存在的错误,从GUI卸载会导致驱动器字母在未来的挂载中不可用。一种解决方法是使用命令行卸载:truecrypt.exe /q /dx(其中x是驱动器字母)。

谢谢你的回复。我已经知道一个卷是否被挂载了。我只是不知道这个卷挂载在哪个驱动器盘符上。根据我发布的答案,我现在能够知道了。或者也许我没有仔细阅读你的答案,我会再次阅读它的;) - Tono Nam
我在问题上发布了一个编辑。非常感谢您的帮助! - Tono Nam
总之,如果我有两个挂载的卷,如何用批处理文件或代码告诉我每个卷挂载到哪个驱动器号? - Tono Nam
@Tono Nam - 您可以选择挂载它的驱动器字母来避免这个问题,而不是使用 /auto。或者您的应用程序无法处理挂载吗? - fzzylogic
它确实处理挂载,但如果有人手动在不同的驱动器字母中挂载卷,我想知道。用户想知道哪些卷已经挂载到了哪个驱动器字母。80%的时间他使用我的程序挂载卷,在这种情况下,我知道卷被挂载到了哪个驱动器字母,但当用户手动挂载驱动器时,我就会遇到麻烦。也许我不应该让用户打开TrueCrypt.exe。 - Tono Nam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接