I have to admit I was truly shocked when TrueCrypt went offline. This was a staple of the internet, something we all took for granted. It was a way to access important technology without it being behind a pay wall. You could secure information with high confidence, and, it’s probably the reason Microsoft’s BitLocker is mostly free. I say mostly, because it comes with the OS, which costs money. But even still, if you’ve already got a copy of Windows, then you might as well use all of it! And BitLocker is there and at least supported.
My main problem with BitLocker, like many things in the Microsoft world, the phrase “some assembly required” is definitely applicable.
One of the nice things about TrueCrypt was the fact that it could, at login, have an interface where you could enter a password and mount an encrypted file as a volume accessible via a drive letter on your computer.
I set out to see if I could get BitLocker to perform this same functionality. Interestingly enough on the TrueCrypt home page (at least for now) is the exact instructions for creating a new BitLocker encrypted VHD. This VHD (or VHDX) will serve as our analogy to a TrueCrypt encrypted container. We’ll use some PowerShell and a Scheduled Task in conjunction with our VHDX to bring it to life. I’ll be starting from the point that you already know how to make a VHD (or VHDX), and enable BitLocker on that virtual drive. If you need help with that, you can head over to the aforementioned TrueCrypt home page and see how that’s done.
Goals
- Run at login
- Mount some encrypted VHDs or VHDXs
- Prompt me for a password if needed
- Start a service or services afterward
A foreword about BitLocker availability
VHDs and BitLocker are not always available depending on the version of Windows you are using. I use Windows 8.1 Professional and I have it available. I know that some versions of Windows 7 cannot make BitLocker drives, but CAN read them. For the purposes this article, the following code should work on Windows Server 2012 and 2012 R2, and Windows 8.x. You may need to enable the BitLocker feature in Windows and ensure command line management is available. Additionally, I used PowerShell 4.0 and am not certain about how far back this example would work – probably no less than PowerShell 3.0. To get on the same page, I recommend downloading Windows Management Framework 4.0 (WinRM 4.0)
Why I needed encryption
In my specific use-case, I had a SQL database that needed securing when the machine is offline, but available for testing while I was logged in. I found it cumbersome on reboots to always have to navigate to the folder and double-click the VHD, then deal with the “catch me if you can” GUI pop-up that allowed me to enter the password. I use LastPass to store my passwords, and it was terribly cumbersome trying to get that password and enter it into the pop-up that disappears when you change focus. I ended up with a locked volume that I had to manually unlock each time. 🙁 Once that drive was available, my database was accessible but unavailable until I manually started the SQL service. This was way too much clicking around for me.
So we need some code that can mount a list of drives and then start a service (in my case the SQL Service). I encountered an interesting problem though. It seems that there is a bug in Microsoft’s Mount-DiskImage PowerShell cmdlet. This is a guess here, but I’m thinking it mounts the virtual disk with user level permissions and thus I found that SQL did not have proper permissions to the volume if mounted in that way. After some searching and chin-scratching I came up with the idea of using the old “START” command. The word “START” in the PowerShell console is actually aliased for the “Start-Process” cmdlet. But this works like a champ and mounts the volume in the proper way for use with the SQL Service.
The command that didn’t allow the VHD(x) to work correctly with SQL:
Mount-DiskImage -ImagePath $Drive.Path -StorageType VHDx -Access ReadWrite -PassThru
The command that works:
Start $Drive.Path
Step 1: Create Config.XML to store our settings
Before we get to the code, to make it easier to use and manage, I externalized the drive mappings and the services we want to start to XML as follows:
<?xml version="1.0" ?> <Config> <Drives> <!-- Add as many drive letters and paths as you wish here, you can use the following template --> <!-- You can enable or disable drives with the Active = "True/False" attribute --> <Drive Letter = "G:" Path = "C:\somefolder\SomeEncryptedVHDX.vhdx" Active = "True" /> <Drive Letter = "D:" Path = "D:\OtherFolder\Test.vhdx" Active = "True" /> </Drives> <Services> <!-- Add as many Windows Services here, you can use the following as a template --> <!-- You can enable or disable service starting per service with the Active = "True/False" attribute --> <Service Name = "MSSQL$SQLEXPRESS" Active = "True" /> <Service Name = "Spooler" Active = "False" /> </Services> </Config>
Create a file called CONFIG.XML and paste that content in there. Make your custom entries to the drive letters and paths as well as if you wish to have a Windows service start afterwards. You notice, you can store many entries in here and set the Active flag to either True or False as you see fit. This allows you to have items available that you can enable or disable, but still storing that information easily in the file.
Step 2: Create MountDrives.ps1
And now for the PowerShell code. I recommend you name it MountDrives.ps1 (any name will do though). One of the prerequisites for this code is that it run as an Administrative PowerShell prompt. You’ll notice the first function in the code checks for that. Next, you’ll see some standard variables I like to set with all my scripts – including a remarked out reference to how I perform my logging.
param () Function Test-Admin () { $currentPrincipal = New-Object Security.Principal.WindowsPrincipal( [Security.Principal.WindowsIdentity]::GetCurrent() ) If ($currentPrincipal.IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator )) { Return $true } Else { Return $false } } #Set Variables cls $Temp = $(Get-Content env:Temp) $ComputerName = $(Get-Content env:ComputerName) $ScriptName = $MyInvocation.MyCommand.Name $Path = split-path $myInvocation.MyCommand.Path -Parent #$Log = "$Path\$((get-childitem $($MyInvocation.MyCommand.Path)).baseName).Log" Write-Host "ScriptName: $ScriptName" Write-Host " Path: $Path" #Write-Host " Log: $Log" CD $Path Try { [XML] $Config = gc .\Config.xml } Catch { Throw "Unable to import Config.xml" Exit } If (Test-Admin) { Write-Host "Admin detected" } Else { Write-Host -ForegroundColor Yellow "Please restart this script with Administrative privileges" Exit } $Drives = $Config.Config.Drives.Drive | Where {$_.Active -eq $True} $Services = $Config.Config.Services.Service | Where {$_.Active -eq $True} foreach ($Drive in $Drives) { Write-Host "$($Drive.Letter) = $($Drive.Path)" if (Get-BitLockerVolume | where {$_.MountPoint -eq $Drive.Letter}) { Write-Host -ForegroundColor Green "Drive Already Mounted" } Else { Start $Drive.Path Start-Sleep -Seconds 2 #Mount-DiskImage -ImagePath $Drive.Path -StorageType VHDx -Access ReadWrite -PassThru if (Get-BitLockerVolume | where {$_.MountPoint -eq $Drive.Letter}) { Write-Host -ForegroundColor Green "Mount Successful" } Else { Write-Host -ForegroundColor Yellow "Mount Failed, please check the path $($Drive.Path) or the drive letter specified" Sleep 15 Exit 1 } } While ((Get-BitLockerVolume | Where {$_.MountPoint -eq $Drive.Letter}).LockStatus -eq "Locked") { if (!$SecurePassword) { $SecurePassword = Read-Host -Prompt "Enter Password" -AsSecureString } Unlock-Bitlocker -MountPoint $Drive.Letter -Password $SecurePassword } Write-Host -ForegroundColor Green "Drive is Unlocked" } Foreach ($Service in $Services) { Start-Sleep 2 Start-Service -Name $Service.Name Get-Service -Name $Service.Name } Start-Sleep 4
Step 3: Create a Task in Task Scheduler to start the code at log on
Now that you’ve got both files side-by-side in a folder, both config.xml and mountdrives.ps1, we need to create a task in task scheduler to get this to start at login.
Open up Task Scheduler and create a new Task. In the General Tab, set the task to run as the account you use to login. Select the radio button that says “Run only when user is logged on” and “Run with highest privileges”. In the “Triggers” tab, add a new Trigger and set it to Begin the task “At log on”. In the “Actions” tab, set the Action to “Start a program” and enter “PowerShell” in the program/script dialog box. In the same tab, set the “Add arguments” contents to “.\MountDrives.ps1” and the “Start in (optional)” contents to the path where you stored the script like this “C:\Util\MountDrivesFolder”. Don’t use quotes in any of the data entry, I only used them to highlight what you need to enter.
Hopefully this helps someone. Even if you don’t want to use the code as I’ve done, there’s some generally good information you can glean from this code to work with externally stored data in an XML, or how one might work with BitLocker.
photo credit: IMG_3383 via photopin (license)