7

After a TrueCrypt container has been mounted to a drive letter, is it possible to determine in a batch file which container the drive letter was mounted from, or which drive letter the container was mounted to?

In a batch file, I want to mount a specified TrueCrypt container to a specified drive letter. TrueCrypt errors if the container is already mounted or if the drive letter isn't available, so I want to run TrueCrypt only if the specified container hasn't already been mounted to the specified drive letter, that is, only if the action hasn't been completed already.

Any suggestions would be appreciated.

Edit

Bounty Summary In short imagine you have volumes C:\Vol1.tc and C:\Vol2.tc mounted to drives X and Y. How can you tel that C:\Vol1.tc is mounted to drive X and C:\Vol2.tc to drive Y programaticaly with a batch file or C# code?

Tono Nam
  • 34,064
  • 78
  • 298
  • 470
Matthew White
  • 363
  • 3
  • 12

6 Answers6

9

A way to do that would be to directly ask to Truecrypt driver himself. This could be achieved with the DeviceIoControl function. In fact, that's exactly what the TrueCrypt GUI is doing.

Note that it's easier to do that in c++. You'll find a good article here.

The idea is to call the DeviceIoControl function, asking for the TC_IOCTL_GET_MOUNTED_VOLUMES You will obtain a structure, with all of the mounted volumes path and drive letters. In fact it's a 26 elements array (one for each possible drive letter), called wszVolume, which contain the path of the truecrypt volume which is mounted on.

Hope the following sample will help you find how to do in your case.

Sample Code in 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);
}
Gerard Walace
  • 921
  • 7
  • 10
1

This is what I have so far:

I am creating a custom app written in c# that informs the user what volumes are mounted/dismounted and if they are mounted I need to inform the user on what drive. In order to know if a volume is mounted I have the following class:

Note I make use of handle.exe that program can be downloaded at http://technet.microsoft.com/en-us/sysinternals/bb896655.aspx or also from http://download.sysinternals.com/files/Handle.zip

Also I belive you must be running the program as an administrator

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;
        }

    }
}

then if I want to know if the volume located at C:\Users\Tono\Desktop\v1.tc is mounted I will call the method as:

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

Now I am missing to answer the question! With the class I posted I am able to know that the volume located at C:\Users\etc... is mounted but into what drive letter!?

Tono Nam
  • 34,064
  • 78
  • 298
  • 470
1

just to extend the answer to VeraCrypt, modified from Gerard Walace's post:

TrueCrypt and VeraCrypt usage in MSFT_Helpers.cs @ https://github.com/BananaAcid/Selfcontained-C-Sharp-WPF-compatible-utility-classes

Simple usage example here: 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);
}
BananaAcid
  • 3,221
  • 35
  • 38
  • Very interesting, thanks! Marshalling is so hard and ugly thoug, I'll never get into it – Liquid Core Apr 04 '18 at 11:56
  • @LiquidCore : I have this an more related classes in git repo, with additional stuff I used in a project (might help you out): https://github.com/BananaAcid/Selfcontained-C-Sharp-WPF-compatible-utility-classes – BananaAcid Apr 08 '18 at 21:01
  • @LiquidCore specifically Utils MSFT_Helpers.cs – BananaAcid Apr 08 '18 at 21:09
  • Thanks man!!! Can I ask you the permission to reuse some of that code for personal only use? – Liquid Core Apr 09 '18 at 07:46
  • @LiquidCore : Fell free, its all MIT licensed. AND: I would love to see, what you did with it. Btw: the project I realized with it: https://epm.bananaacid.de/ – BananaAcid Apr 09 '18 at 15:41
  • Awesome work. Most of the code will probably go into one .NET Core library I'm doing that does a lot of common and less common taks that I need to do in everyday coding (file system management/file creation,/read, drive management, network checks, conversions and so on). That way, I can just put my DLL in my projects and avoid to write boring code everytime. – Liquid Core Apr 10 '18 at 10:23
  • I've tried the veracrypt code in a simple windows form with a VeraCrypt drive mounted, but the winform just remains unresponsive even after 2 minutes. I think something is not working down there. Am I missing something? – Liquid Core Apr 11 '18 at 12:00
  • Calling it: `Dictionary infosVc = await VcGetMounts.getMounted();` unresponsive? usually because you are waiting for a long running Task... Did you mount it on a path, instead of a drive letter? I did not test this. BTW - you might contact me on skype - using the same nickname – BananaAcid Apr 11 '18 at 18:02
  • i did it with var infosVc = await.... etc so C# would manage the type. I waited like 2 minutes. It seems a lot to be a disk unmount task. Normally it takes 10 seconds – Liquid Core Apr 12 '18 at 13:43
  • This should be instant. – BananaAcid Apr 19 '18 at 18:05
  • Well, it's not working for some reason. I will check the drive thing with my own code – Liquid Core Apr 20 '18 at 12:18
  • Did you mount a partition? Did you install a recent VeraCrypt? The very class used in my code is at github. I would look into the code of yours if you want. – BananaAcid Apr 20 '18 at 21:26
0

I'm unsure about determining volume name by querying the drive letter or visa-versa. A workaround that may be acceptable depending on your needs is to query for file locks on the volume. If the volume is locked, chances are good its mounted. You could get more fancy if you wanted and check that it's locked by the System process specifically, using for /f.

I should mentioned that using /quit background in conjunction with /silent when calling truecrypt.exe will just fail silently if the drive is already mounted. Not sure if it's just error display you're concerned with. Not sure if you've noticed the command line reference.

Here is a simple cmd script to mount a volume only if it's not already mounted, thereby preventing errors being triggered by truecrypt.exe. It makes a number of naive assumptions, but should give an idea.

@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.

Note that due to what may be a bug in some versions of TrueCrypt, unmounting from the GUI causes the drive letter to be unavailable on future mounts. One workaround is to unmount from the command line using: truecrypt.exe /q /dx (where x is the drive letter).

fzzylogic
  • 2,183
  • 1
  • 19
  • 25
  • Thanks for your response. I already know if a volume is mounted or not. I am just missing to know on what drive letter that volume is mounted. With the answer I posted I am able to know that. Or maybe I am missing to read your answer more carefully I will do that again ;) – Tono Nam Aug 01 '13 at 13:59
  • I posted an edit on the question. Thanks a lot for the help ! – Tono Nam Aug 01 '13 at 14:04
  • In summary if I have two volumes mounted how can I tell with a batch file or code to what drive letter each volume is mounted? – Tono Nam Aug 01 '13 at 14:08
  • @Tono Nam - You can avoid that problem by choosing what drive letter to mount it to instead of using /auto. Or does your app not handle mounting? – fzzylogic Aug 01 '13 at 14:12
  • It does handle mounting but if someone manually mounts a volume in a different drive letter I will like to know that. The user wants to know what volumes are mounted and into what drive letter. 80% of the time he mounts the volumes with my program in that case that is great I know into what drive letter the volume is mounted but when the user mounts the drive manualy that is when I have trouble. Perhaps I should not enable users to open TrueCrypt.exe – Tono Nam Aug 01 '13 at 17:50
0

I may be oversimplifying, but are the drive labels on the mounted drives unique? If so you could use a simple lookup such as:

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;
}
n4m16
  • 121
  • 8
0

Just to make sure I've got this right: you want to make sure that TrueCrypt mounts a certain volume to a certain drive letter via a script, and that it will not attempt to do anything if the volume is already mounted.

Maybe Several Potential Solutions. I'll try to highlight a few options below; let me know if any are worth pursuing further and I'll continue to research and update with more details.

Option 1: Favorites and Auto-Mount

TrueCrypt supports "favorite volumes".

From the page -- Favorite volumes are used when:

  • You have a volume that always needs to be mounted to a particular drive letter.
  • You have a volume that needs to be automatically mounted when its host device gets connected to the computer (for example, a container located on a USB flash drive or external USB hard drive).
  • You have a volume that needs to be automatically mounted when you log on to the operating system.
  • You have a volume that always needs to be mounted as read-only or removable medium.

Some things worth noting:

  • A special label can be applied to each favorite volume -- could be handy
  • You could run "TrueCrypt.exe /a favorites /quit" to auto-mount your favorite volumes and then quit. You may want to test this, as the truecrypt favorites page notes that If it is already mounted, an Explorer window is opened for it.

Option 2: Check Drive Letter Only

If you always mount the volume to a certain drive letter, you may be able to make a reasonable assumption that nothing else is going to be sitting on that drive letter.

If that's a reasonable assumption, you can always just check the drive with PowerShell:

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

I'll update if I think of something better. Let me know if I'm on the right track.

SeanKilleen
  • 8,809
  • 17
  • 80
  • 133
  • To clarify in advance: I know batch script was asked for, but am making a (reasonable I think) assumption that PowerShell is likely available in some capacity, too. – SeanKilleen Aug 03 '13 at 05:40