Creating a scheduled task and a local policy for BitLocker to back up the recovery key to Active Directory

I’m working with MDT to load Windows 7 on laptops that need BitLocker.  I have the TPM configured so that it’s enabled, activated, and owned; now I want to back up the recovery key to Active Directory.  I have the Join Domain step near the end of the task sequence (with no reboot) so the domain logon message doesn’t interfere with software installs, so I figured I could create a local policy to backup to Active Directory and when the Enable BitLocker step executed, it would automatically backup the key to AD.  That didn’t work (I guess it needs a reboot?), so I created a scheduled task that runs on system startup to back it up to AD.

I configured the local policy on a workstation using the settings from  I then used the LocalGPO from the Security Compliance Manager available here to backup the settings (thanks to for pointing me in this direction).  I also created the scheduled task that runs the following on boot:

cmd.exe /c for /f “tokens=2” %i in (‘manage-bde.exe -protectors -get C:’) do manage-bde.exe -protectors -adbackup C: -ID %i

(I know this loops through a few lines that error out, but the end result is the key is backed up.)

I exported the scheduled task as an XML and added it to the %scripts% folder (actually a “CUSTOM” subfolder).

Right before the Join Domain step, I have the following:

20180613 BitLocker AD Backup

“Configure Local Policy for BitLocker” runs an application that just uses the files created by LocalGPO:

cscript.exe GPOPack.wsf /silent

and the “Create task to backup BitLocker key to Active Directory” step is a “Run Command Line” that runs schtasks.exe and uses the XML file of the scheduled task:

schtasks.exe /create /tn “Backup Bitlocker Key to Active Directory” /xml “%SCRIPTROOT%\CUSTOM\Backup Bitlocker Key to Active Directory.xml”

After joining the domain with no reboot, the Enable BitLocker step runs and starts encrypting the disk.  The workstation does a final reboot at the end of the task sequence and the scheduled task backs up the key to AD.


Enabling, Activating, and Owning the TPM for the Enable BitLocker step in MDT

Configuring a task sequence to enable Bitlocker on Windows 7 with two model laptops:

Dell Latitude E5400
HP ProBook 640 G2

As these need to be wiped clean, and I like to start with a clean slate, I have the following steps defined for helpdesk to perform before beginning the task sequence:

Prepare Dell Latitude E5400
  BIOS Setup
        TPM Security
        Clear the TPM
  Exit (Reboot)

  BIOS Setup
        TPM Security
        Uncheck “TPM Security”
  Exit, saving changes. (Reboot)

Prepare HP ProBook 640 G2
ESC – for Startup Menu
  F10 – for BIOS Setup
  Security tab
    TPM Embedded Security
      TPM Device – Hidden
      TPM State – unchecked
      Clear TPM – On next boot
      TPM Activation Policy – Allow user to reject
      Save – Yes

“TPM Ppi” screen
F1 to accept

At this point, the TPM is disabled, inactive, and unowned.

In the task sequence, I check to see if the workstation is a laptop by using the IsLaptop variable.
01 - Configure TPM

If it is, I then use the ZTICheckforTPM.wsf script ( to populate the TPMEnabled and TPMActivated variables. I also updated the script to populate the TPMOwned variable.
02 - Generate TPM Variables
03 - Check TPM Variables
I found the results were inconsistent when trying to enable and activate the TPM in one step on the Dell system, so I broke it out into two (might have been a learing curve, but in any event, here it is).  I just defined two applications using the CCTK.EXE:

Application 1: Dell 1 – TPM On
cctk.exe –setuppwd=dell3
cctk.exe –tpm=on –valsetuppwd=dell3
cctk.exe –setuppwd= –valsetuppwd=dell3

Application 2: Dell 2 – TPM Activate
cctk.exe –setuppwd=dell3
cctk.exe –tpmactivation=activate –valsetuppwd=dell3
cctk.exe –setuppwd= –valsetuppwd=dell3

For the HP, the BIOSConfigUtility worked fine, so the HP TPM – Enable and Activate step application runs:
BiosConfigUtility64.exe /set:TPMSetup.txt

where TPMSetup.txt is:

BIOSConfig 1.0
TPM Device
TPM Activation Policy
F1 to Boot
Allow user to reject
*No prompts

The Take Ownership of TPM runs:

manage-bde.exe -tpm -takeownership NeedsAPassword4321

This now works consistently with the built-in Enable Bitlocker step:
04 - Enable BitLocker

Bitlocker is now enabled.  Now to work with the AD folks to get the recovery key into Active Directory…


Enabling Bitlocker through MDT 2013

Win32_Tpm class


Check TPM Status from the Command Line (Enabled | Activated | Owned)

Useful Links for Configuring MDT

Enable video resolution auto-detection

To have screen resolution autodetection on, set the screen resolution to a really low value in CustomSettings.ini. This will force Windows setup to autodetect the resolution:



Customizing IT Organization

In the [Default] section of CustomSettings.ini, add _SMSTSOrgName.

Set the Computer Name to the Serial Number

Set OSDComputername=%SerialNumber% in CustomSettings.ini.

Skip “Specify credentials for connecting to network shares”

Set them in Bootstrap.ini:



Quick and Dirty – Testing customsettings.ini variables in MDT
Quick and Dirty – Testing customsettings.ini variables in MDT

Preserving Computer Name with MDT

MDT and Bitlocker

Renaming a Domain Joined Machine in the Task Sequence

Moving MDT Domain Join to the end of the Task Sequence

How to pass credentials to rename command?

# Capture once and store to file
$passwd = Read-Host "Enter password" -AsSecureString
$encpwd = ConvertFrom-SecureString $passwd
$encpwd > $path\password.bin
# Later pull this in and restore to a secure string
$encpwd = Get-Content $path\password.bin
$passwd = ConvertTo-SecureString $encpwd
# Extract a plain text password from secure string
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($passwd)
$str = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)

Useful links for configuring an IBM x3650 M4 (7915 AC1) with RAID 10 for MDT and multicasting

IBM MegaRAID BIOS Config Utility RAID-10 Configuration

MDT – How I build my reference images (User Question)

Multicasting with MDT

Finding the source tables/views used in query-based Collection membership rules

We have workstations consolidating to one domain.  Query based rules that might reference Organization Units and/or Active Directory groups will have to be changed.  I wanted a query that would list out the tables and views used in query-based membership rules.

CREATE TABLE #SourceTables (
, QueryID INT
, SourceTable NVARCHAR(MAX)
, OriginalQuery NVARCHAR(MAX)
 @CollectionID NVARCHAR(MAX)
, @QueryID INT
, @QueryExpression NVARCHAR(MAX)
, @OriginalQuery NVARCHAR(MAX)
, @SourceTable NVARCHAR(MAX)
, @TrimPos INT
DECLARE CollectionRuleQuery CURSOR FOR
SELECT c.CollectionID, c.[Name], RuleName, QueryID, QueryExpression FROM v_CollectionRuleQuery crq
INNER JOIN v_Collection c ON c.CollectionID = crq.CollectionID WHERE c.CollectionID NOT LIKE 'SMS%'
OPEN CollectionRuleQuery
FETCH NEXT FROM CollectionRuleQuery INTO @CollectionID, @Name, @RuleName, @QueryID, @QueryExpression
SELECT @OriginalQuery = @QueryExpression
-- table or view name will be preceded by either " from " or " join "
WHILE CHARINDEX (' from ', @QueryExpression) > 0 OR CHARINDEX (' join ', @QueryExpression) > 0
-- We know at this point either a "from" or "join" exists in the string
-- So move the start of @QueryExpression to first character of the table or view name
-- If there's no "join", it must be "from"
IF CHARINDEX (' join ', @QueryExpression) = 0
SELECT @QueryExpression = SUBSTRING (@QueryExpression, CHARINDEX (' from ', @QueryExpression) + 6, LEN (@QueryExpression));
-- If there's no "from", it must be "join"
ELSE IF CHARINDEX (' from ', @QueryExpression) = 0
SELECT @QueryExpression = SUBSTRING (@QueryExpression, CHARINDEX (' join ', @QueryExpression) + 6, LEN (@QueryExpression));
-- If both exist, find out what one comes first
ELSE IF CHARINDEX (' from ', @QueryExpression) < CHARINDEX (' join ', @QueryExpression)
SELECT @QueryExpression = SUBSTRING (@QueryExpression, CHARINDEX (' from ', @QueryExpression) + 6, LEN (@QueryExpression));
ELSE SELECT @QueryExpression = SUBSTRING (@QueryExpression, CHARINDEX (' join ', @QueryExpression) + 6, LEN (@QueryExpression));
-- To find the end of the table or view name, there are four possibilities,
-- ends with space, open or close parenthesis, or the end of the string
-- also want to start at position 2, since there might be some leading parenthesis to ignore before the name begins
SELECT @TrimPos = PATINDEX ('%[ )(]%', SUBSTRING (@QueryExpression, 2, LEN (@QueryExpression))) + 1
SELECT @SourceTable = SUBSTRING (@QueryExpression, 1, @TrimPos - 1)
-- Eliminate any lingering opening parenthesis that might preceed the table or view name
IF @SourceTable LIKE '(%' SELECT @SourceTable = SUBSTRING (@SourceTable, 2, LEN (@SourceTable))
INSERT INTO #SourceTables SELECT @CollectionID, @Name, @RuleName, @QueryID, @SourceTable, @OriginalQuery;
SELECT @QueryExpression = SUBSTRING (@QueryExpression, @TrimPos, LEN (@QueryExpression));
FETCH NEXT FROM CollectionRuleQuery INTO @CollectionID, @Name, @RuleName, @QueryID, @QueryExpression
CLOSE CollectionRuleQuery;
DEALLOCATE CollectionRuleQuery;
SELECT * FROM #SourceTables
DROP TABLE #SourceTables

This lists the collections, rule names, and the tables/views queried:
20180326 Collection Tables