none
USMT against a bitlocker partition when starting from PXE

    Question

  • Hoping someone can tell me if there is any special mojo that can make this scenerio work. 

    My situation:
    I enable Bitlocker encryption as part of my Task Sequence dpeloyment of Windows 7.  I am now diving into USMT (hardlink hopefully) and I quickly realize that i need to "unlock" the bitlocker drive before I can USMT it.  Makes sense.  The disabling Bitlocker as you all know works great when the TS is executed from within Windows.  Where it breaks down for me is that I would like to automate this process even when starting from Windows PE PXE boot
    I thought I would be slick and create a vbscript that grabs the enryption key from AD and passes it to manage-bde to unlock the drive.  This works GREAT if you know the computer name (which I was specifying directly in my testing).

    The obvious conundrum here is that I cannot retreive the computer name say from the local registry hive, to then pass to the script that queries AD and gets the recovery password since..the drive is locked!

    I was really hoping I could query SMBIOS on the machine and take some bit of data and coorelate that to an attribute on the AD object to get the name but have come up empty.

    My thought was that I could (although not really wanting to) add a front end the TS that asked for the computername, then runs the script to unlock the Bitlocker partition. 

    Anyone else have any tricks up their sleeve?

    Thanks!!

    Tuesday, November 15, 2011 11:33 PM

Answers

All replies

  • Wednesday, November 16, 2011 7:09 AM
  • you didn't mention that you were using MBAM but if you were you could use this scenario - http://www.windows-noob.com/forums/index.php?/topic/4173-how-can-i-retrieve-my-bitlocker-recovery-key-from-mbam-in-windows-pe/ maybe it will give you some ideas ?


    My Step by Step ConfigMgr Guides
    I'm on Twitter > ncbrady
    Wednesday, November 16, 2011 7:57 AM
    Moderator
  • Niall, you might have struck gold for me here.  :-)  Theoretically, using the "-protectors -get" command, it appears that the "Numerical Password ID" that is displayed cooresponds to the "RecoveryGuid" returned from my AD script, which I should be able to look for and then retieve the matching Recovery Password.  Off to test!  Thanks a million.  i will report back my findings.  (not using MBAM btw)

     

    Thanks!

    Wednesday, November 16, 2011 2:39 PM
  • great stuff i look forward to seeing what you do

    My Step by Step ConfigMgr Guides
    I'm on Twitter > ncbrady
    Wednesday, November 16, 2011 3:40 PM
    Moderator
  • It's working!  doing some cleanup and tweaks and then I will post my script.  :-)  thanks again for the push there Niall
    Wednesday, November 16, 2011 7:19 PM
  • Here's version 1 of the script.  Still have some tweaking to do but the meat and potatoes is there to give an idea of the process:
    Careful of word wrap.  If you use this script you are doing so at your own risk!
    Once I get it all finalized and into my build process I will create a new Blog post on my site with the final version and how I am using etc.

    Thanks again Niall!

    '===============================================================================
    '
    ' Checks if a volume is encrypted with Bitlocker, if so grabs the
    ' local recovery guid, searches AD for corresponding object and retrieves the
    ' recovery password from the computer object.  It passes this key to manage-bde
    ' to unlock the partition.
    '
    ' USAGE:  	Executed during intial phase of OSD TS for unlocking bitlockered
    ' 			partition whithin WindowsPE (PXE Deployments)
    '
    ' Created: 
    '	v1.0 - 11/16/2011 - William Bracken
    '  
    '
    '===============================================================================
    
    
    Option Explicit 
    Const ADS_SECURE_AUTHENTICATION = "&H1" 
    Const ADS_SERVER_BIND = "&H200" 
    Const ScriptVersion = "1.0"
    Const ForReading = 1, ForWriting = 2, ForAppending = 8
    Dim objWshShell, colGroups, group, objDSO, objRecordSet, dnFound, objConnection, objCommand
    Dim emval, em, psval, ps, drv,arrComputers,strComputer,objWMIService,volumes,volume
    Dim strPathToComputer, objFveInfos, objFveInfo, strName, strRecoveryGuidOctet, strRecoveryGuid
    Dim objShell,objWshScriptExec,objStdOut,strLine, look, strTempString,a, strBitlockerPassword
    Dim objSysInfo, DCArray(2),continue, x, strTempSerial, f
    Dim strRecoveryPassword, stroperatingSystem, objRecordSet2
    Dim strComputerName, objComputer, strRegistryKey, strComputerDistinguishedName 
    Dim objRootDSE, strDNSDomain, adoCommand, adoConnection, adoRecordset, objNS 
    Dim strDomainControllerFQDN, strAccountUserName, strAccountPassword,shell 
    Dim fs:				Set fs = CreateObject("Scripting.FileSystemObject")
    Dim objADSysInfo:	Set objSysInfo = CreateObject("ADSystemInfo")
    
    
    ' ===================================================
    '	Define Variables
    ' ===================================================
    strAccountUserName = "REPLACEME" 'Injected from WiseScript
    strAccountPassword = "REPLACEME" 'Injected from WiseScript
    
    
    
    '=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    '  BEGIN
    '-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    ' Check if drive is encrupted and unlocked already.  Exit if so.
    CheckifEncrypted
    
    'if encrypted, this function initiates the entire process
    CallConnectAD
    
    ' ===================================================
    '	FUNCTION: CallConnectAD
    ' ===================================================
    Function CallConnectAD
    WScript.Echo "* Starting Function: CallConnectAD"
    	DCArray(0) = "DC1.yourcompany.com"
    	DCArray(1) = "DC2.yourcompany.com"
    	DCArray(2) = "DC3.yourcompany.com"
    	continue = 1
    	For Each strDomainControllerFQDN In DCArray
    		If continue = 1 Then
    			'  Authenticate to AD
    			ConnectAD(strDomainControllerFQDN)
    			WScript.Echo Err.Number
    			If Err.Number = 0 Then
    				'  Call function to get computer list from AD
    				GetADComputerList(strDomainControllerFQDN)
    				continue = 0
    			Else
    				continue = 1
    			End If
    		End If 
    	Next
    End Function
    
    
    ' ===================================================
    '	FUNCTION: ConnectAD
    ' ===================================================
    Function ConnectAD(strDomainControllerFQDN)
    WScript.Echo "* Starting Function: ConnectAD(" & strDomainControllerFQDN & ")"
    	Set objNS = GetObject("LDAP:") 
    	Set objRootDSE = objNS.OpenDSObject("LDAP://" & strDomainControllerFQDN & "/RootDSE", strAccountUserName, strAccountPassword, ADS_SERVER_BIND Or ADS_SECURE_AUTHENTICATION) 
    	strDNSDomain = objRootDSE.Get("defaultNamingContext") 
    	Set adoCommand = CreateObject("ADODB.Command") 
    	Set adoConnection = CreateObject("ADODB.Connection") 
    	adoConnection.Provider = "ADsDSOObject" 
    	adoConnection.Properties("User ID") = strAccountUserName 
    	adoConnection.Properties("Password") = strAccountPassword 
    	adoConnection.Properties("Encrypt Password") = True 
    	adoConnection.Properties("ADSI Flag") = ADS_SERVER_BIND Or ADS_SECURE_AUTHENTICATION 
    	adoConnection.Open "Active Directory Provider" 
    	adoCommand.ActiveConnection = adoConnection 
    	adoCommand.Properties("Page Size") = 100 
    	adoCommand.Properties("Timeout") = 30 
    	adoCommand.Properties("Cache Results") = False 
    End Function
    
    
    ' ===================================================
    '	FUNCTION: CheckifEncrypted
    ' ===================================================
    Function CheckifEncrypted
    	arrComputers = Array(".")
    	For Each strComputer In arrComputers
      		Set objWMIService = GetObject("winmgmts:\\" & strComputer _
                     	& "\root\CIMV2\Security\MicrosoftVolumeEncryption")
      		Set volumes = objWMIService.InstancesOf("Win32_EncryptableVolume")
    	  	For each volume In volumes
          		emval = volume.GetEncryptionMethod(em)
          		psval = volume.GetProtectionStatus(ps) 
          		drv = volume.DriveLetter
          		'Volume              Encryption        Protection
    			'Status                Method             Status
    			'----------------------------------------------
    			'LOCKED                 1                    2
    			'UNLOCKED             	1                    1
    			'----------------------------------------------
    			'Not BITLOCKED     		0                    0
    
          		If ps = 2 Then
          			WScript.Echo "Partition " & drv & " is locked"
          			'  Drive is locked so get local GUID to match against AD
          			GetLocalBitlockerPassword(drv)
          		Else
          			'  Drive is NOT locked so exit
          			WScript.Echo "Partition " & drv & " is NOT locked"
          			WScript.Quit
          		End If 
      		Next
    	Next
    End Function
    
    
    ' ===================================================
    '	FUNCTION: GetLocalBitlockerPassword
    ' ===================================================
    Function GetLocalBitlockerPassword(drv)
    	Set objShell = CreateObject("WScript.Shell") 
     	Set objWshScriptExec = objShell.Exec("manage-bde.exe -protectors -get " & drv) 
     	Set objStdOut = objWshScriptExec.StdOut 
      	look = 0
      	Do While Not objStdOut.AtEndOfStream
      		While look = 0
    	    	strLine = objStdOut.ReadLine
        		If InStr(strLine,"ID:") Then
    	    		look = 1
    	    		strTempString = Split(strLine,":")
    	    		For Each x In strTempString
    	    			If InStr(x,"{") Then
    	    				strBitlockerPassword = Trim(x)
    	    			End if
    	    			
    	    		Next
            	Exit do
            	End If
            Wend
        loop	 
    End Function
    
    
    ' ===================================================
    '	FUNCTION: GetADComputerList
    ' ===================================================
    Function GetADComputerList(strDomainControllerFQDN)
    	Const ADS_SCOPE_SUBTREE = 2
    	adoCommand.CommandText = _
    	    "Select Name, operatingSystem from 'LDAP://" & strDomainControllerFQDN & "/DC=yourcompany,DC=com' " _
            	& "Where objectClass='computer'"  
    	adoCommand.Properties("Page Size") = 1000
    	adoCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 
    	Set objRecordSet2 = adoCommand.Execute
    	objRecordSet2.MoveFirst
    	Do Until objRecordSet2.EOF
    		strComputerName = objRecordSet2.Fields("Name").Value
    		stroperatingSystem = objRecordSet2.Fields("operatingSystem").Value
    		If stroperatingSystem = "Windows 7 Enterprise" Then
    			'  get recovery key for computer
    			GetBitLockerKey strDomainControllerFQDN,strDNSDomain,strComputerName
    		ElseIf InStr(stroperatingSystem,"Vista") Then
    			'  get recovery key for computer
    			GetBitLockerKey strDomainControllerFQDN,strDNSDomain,strComputerName
    		End if
    	    objRecordSet2.MoveNext
    	Loop
    End Function
    
    
    ' ===================================================
    '	FUNCTION: HexByte
    ' ===================================================
    Function HexByte(b)
          HexByte = Right("0" & Hex(b), 2)
    End Function 
    
    
    ' ===================================================
    '	FUNCTION: ConvertOctetGuidToHexString
    ' ===================================================
    Function ConvertOctetGuidToHexString(ByteArray)
      Dim Binary, S
      Binary = CStr(ByteArray)
      On Error Resume Next
      S = "{"
      S = S & HexByte(AscB(MidB(Binary, 4, 1)))
      S = S & HexByte(AscB(MidB(Binary, 3, 1)))
      S = S & HexByte(AscB(MidB(Binary, 2, 1)))
      S = S & HexByte(AscB(MidB(Binary, 1, 1)))
      S = S & "-"  
      S = S & HexByte(AscB(MidB(Binary, 6, 1)))
      S = S & HexByte(AscB(MidB(Binary, 5, 1)))
      S = S & "-"  
      S = S & HexByte(AscB(MidB(Binary, 8, 1)))
      S = S & HexByte(AscB(MidB(Binary, 7, 1)))
      S = S & "-"  
      S = S & HexByte(AscB(MidB(Binary, 9, 1)))
      S = S & HexByte(AscB(MidB(Binary, 10, 1)))
      S = S & "-"  
      S = S & HexByte(AscB(MidB(Binary, 11, 1)))
      S = S & HexByte(AscB(MidB(Binary, 12, 1)))
      S = S & HexByte(AscB(MidB(Binary, 13, 1)))
      S = S & HexByte(AscB(MidB(Binary, 14, 1)))
      S = S & HexByte(AscB(MidB(Binary, 15, 1)))
      S = S & HexByte(AscB(MidB(Binary, 16, 1)))
      S = S & "}"
      On Error GoTo 0
      ConvertOctetGuidToHexString = S
    End Function 
    
    
    ' ===================================================
    '	FUNCTION: GetStrPathToComputer
    ' ===================================================
    Function GetStrPathToComputer(strDomainControllerFQDN,strDNSDomain,strComputerName)
    	adoCommand.CommandText = "Select DistinguishedName from 'LDAP://" & strDomainControllerFQDN & "/" & strDNSDomain & "' Where objectCategory='Computer' and Name='" & strComputerName & "'" 
        Set objRecordSet = adoCommand.Execute 
        If objRecordSet.EOF Then
          WScript.echo "The computer name '" & strComputerName & "' cannot be found."
          WScript.Quit 1
        End If
        ' Found object matching name
        Do Until objRecordSet.EOF 
          dnFound = objRecordSet.Fields("distinguishedName")
          GetStrPathToComputer = "LDAP://" & strDomainControllerFQDN & "/" & dnFound
          objRecordSet.MoveNext
        Loop 
        ' Clean up. 
        Set objConnection = Nothing 
        Set objCommand = Nothing 
        Set objRecordSet = Nothing 
    End Function
    
    
    ' ===================================================
    '	FUNCTION: GetBitLockerKey
    ' ===================================================
    Function GetBitLockerKey(strDomainControllerFQDN,strDNSDomain,strComputerName)
    	Set objDSO = GetObject("LDAP:")	
    	strPathToComputer = GetStrPathToComputer(strDomainControllerFQDN,strDNSDomain,strComputerName)
    	
    	Const ADS_SECURE_AUTHENTICATION = 1
    	Const ADS_USE_SEALING = 64 '0x40
    	Const ADS_USE_SIGNING = 128 '0x80
    	' Get all the recovery information child objects of the computer object
    	Set objFveInfos = objDSO.OpenDSObject(strPathToComputer, strAccountUserName, strAccountPassword, _
    	ADS_SECURE_AUTHENTICATION + ADS_USE_SEALING + ADS_USE_SIGNING)
    	objFveInfos.Filter = Array("msFVE-RecoveryInformation")
    
    	' Iterate through each recovery information object 
    	For Each objFveInfo in objFveInfos
       		strName = objFveInfo.Get("name")
       		strRecoveryGuidOctet = objFveInfo.Get("msFVE-RecoveryGuid")
       		strRecoveryGuid = ConvertOctetGuidToHexString(strRecoveryGuidOctet)
       		strRecoveryPassword = objFveInfo.Get("msFVE-RecoveryPassword")
       		'  If a match is found, get the recovery key
       		If strRecoveryGuid = strBitlockerPassword Then
    	   		WScript.echo  
       			WScript.echo "name: " + strName 
       			WScript.echo "msFVE-RecoveryGuid: " + strRecoveryGuid
       			WScript.echo "msFVE-RecoveryPassword: " + strRecoveryPassword
       			'  Run function to unlock partition
       			UnlockBitlockerPartition strRecoveryPassword
       		End if
    	   	If len(strRecoveryGuid) <> 38 Then
          		WScript.echo "WARNING: '" & strRecoveryGuid & "' does not appear to be a valid GUID."
       		End If
    	Next
    End Function
    
    
    ' ===================================================
    '	FUNCTION: UnlockBitlockerPartition
    ' ===================================================
    Function UnlockBitlockerPartition (strRecoveryPassword)
    	Set objShell = CreateObject("WScript.Shell") 
     	Set objWshScriptExec = objShell.Exec("manage-bde.exe -unlock " & drv & " -rp " & strRecoveryPassword) 
     	Set objStdOut = objWshScriptExec.StdOut 
      	While Not objStdOut.AtEndOfStream
        	strLine = objStdOut.ReadLine
    		WScript.Echo strLine
        Wend
    End Function
    
    
    WScript.Quit
    

    Thursday, November 17, 2011 4:28 PM
  • BTW - If anyone has any suggestions on how to streamline this script I am certainly open to ideas and suggestions. ;-)
    Thursday, November 17, 2011 4:38 PM
  • So, running up against something here..

    First test:  Script runs, unlocks the partition and disables the protectors.  I am then telling the task sequence to apply the WIM to the freshly unlocked partition but it fails stating not enough free space on the newly unocked partition.

    Second test:  Script runs, unlocks the partition and disables the protectors.  I was thinking maybe it required a reboot before the TS could address the unlocked partition.  It tries to download the WinPE image to the C: partition (which is the 300MB system reserved partition) and fails once again saying not enough free sapce to download and stage the PE image.

    Guessing its still a result of the original issue with PE not being able to see the drive as writable(?) so its picks the drive with the most free space which would be C: if it can't address the bitlocker partition.

    Am I missing something simple here?

    Thanks!


    Friday, November 18, 2011 6:55 PM
  • yup... all you need to do is make sure your bitlocker partition is 1gb in size, that gives it plenty of room for bitlocker and to stage a winpe boot image,

    next use a diskpart script to swap c: (probably winpe bitlocker) with d: (probably the real c:\windows.... drive), but before you reboot to take the effect xcopy the _smststask... dir from one drive to the next..

    you are probably thinking this sounds crazy, but i can tell you it works just fine, we use that exact scenario and i can share the reassign script if you want, oh and it works. great :)



    My Step by Step ConfigMgr Guides
    I'm on Twitter > ncbrady
    Friday, November 18, 2011 10:07 PM
    Moderator
  • Yeah, I am looking at a diskpart script (currently only manually testing as I type) that would "shrink" the OS partition, and then use that space to "expand" the System Reserve partition as I am certainly going to run up against laptops that have a 300MB System Reserve partition.

    Also, sorry for being dense here but moving the data from one parition to the other, that just tricks the TS basically to come back up and run from the "OS" drive since the variable SMSTSLocalDataDrive would be set to C:?

    Had another though that I would love some feedback on.  ;-)

    What about adding my unlock partition script above as a pre-execution hook that unlocks the parition prior to selecting the Task Sequence?  In theory, the drive should then be available to the TS in an unlocked state and thus the parition with the most available free space, without requiring a reboot (assuming the variable SMSTSLocalDataDrive is not set until the TS launches).  IF that works, seems that may be the easiest/cleanest method?

    Friday, November 18, 2011 10:30 PM
  • i'll try and explain, when you pxe boot into winpe and you try to refresh a computer that is bitlockered, if it has a bitlocker partition and if C:\ is encyrpyted then the bitlocker partition will be the largest available ntfs partition and the task sequence engine will use it and think it is the drive to do OS deployment on, of course it will fail as the partition is too small,

    so you remove encryption from within PE on the bitlockered partition and now you have two writable partitions, the bitlocker one, and the OS one, but the task sequence engine is still locked onto the bitlockered partition, even if you reboot now it will stick with the bitlocker partition

    the only way to change it's mind that i've found is to reassign the drive letters, xcopy the _smsts... folder from one drive to the other, delete the bitlockered _smsts.. and reboot, then it resumes from the true C:\ and everything is fine after that point,

    if you have another way of doing it then i'm all ears but i know this method works just fine.

    cheers

    niall



    My Step by Step ConfigMgr Guides
    I'm on Twitter > ncbrady
    Saturday, November 19, 2011 10:11 PM
    Moderator
  • and of course i mean 'the only way ... within WinPE', i'm fully aware that removing encryption within Windows will make this work just fine,

    My Step by Step ConfigMgr Guides
    I'm on Twitter > ncbrady
    Saturday, November 19, 2011 10:14 PM
    Moderator
  • Thanks Niall.  I actually discovered a much simpler method for achieving this.  :-)

     

    So the TS comes up and cannot see the bitlockered partition thus it sets the working partition to the 300MB (in my case) "C:" partition.  I have (a revised version) of my unlock script that checks for/unlocks the partition and then executes a diskpart script to remove the drive letter from the small volume (in my case C:):  

     

    sel disk 0

    sel vol REPLACE (Note: REPLACE is injected with the actual volume number prior to execution based on the findings of the parent script, in this case its vol 1)

    remove letter=REPLACE (Note: REPLACE is injected with the actual volume number prior to execution based on the findings of the parent script, in this case its letter C:)

    exit

    This immediately makes the TS start using the newly unlocked partition (guessing its smart enough that if the partition disappears it checks for another available partition and can then see the unlocked partition).

    I then added another task that runs a diskpart script to re-assign the C: letter to the reserve partition and make it active (prior to applying the OS):

     

    sel disk 0

    sel vol REPLACE (Note: REPLACE is injected with the actual volume number prior to execution based on the findings of the parent script, in this case its vol 1)

    assign letter=REPLACE (Note: REPLACE is injected with the actual volume number prior to execution based on the findings of the parent script, in this case its letter C:)

    active

    exit

     

    Long story short, this allows me to unlock the partition from within PE and then continue the process against the unlocked partition, avoiding a reboot.  J

    May sound crazy but test for yourself...  It works!  J

     

    Now off to tweak my Task Sequence to make sure things run only when needed!  Thanks for all your assistance.  Hope this info helps others!

    Tuesday, November 22, 2011 10:58 PM
  • William,

    Any chance you'd share the updated script that runs diskpart? Awesome piece of work there btw.

    Monday, November 28, 2011 3:59 PM
  • Also, when I run the script I get:

    "ActiveX comonent can't create object: 'ADSystemInfo'."

    Do I need to update my SCCM's WinPE image with some sort of AD addons before this works?

    Thanks!

    Monday, November 28, 2011 5:19 PM
  • John, no problem.  There are a series of files/tasks that go into my current process and I am still tweaking but here's the gist.  Bare with me as there are a lot of moving parts and this thread is going to be long winded.  :-)

    First task in my Task Sequence (PXE Boot) runs a WiseScript exe that does the following:
    1.  Copies the vbscript "UnlockPartition.vbs" to %TEMP%, then injects the service account that has rights to read bitlocker recovery information. (compiled into the exe to protect the account/pass).  This is the main script that checks for a bitlocker partition and if found, queries AD for the recovery password to unlock and disable protectors as well set TS Variables to be used later.
         UnlockPartition.vbs:

    '===============================================================================
    '
    ' Checks if a volume is encrypted with Bitlocker, if so grabs the
    ' local recovery guid, searches AD for corresponding object and retrieves the
    ' recovery password from the computer object.  It passes this key to manage-bde
    ' to unlock the partition.
    '
    ' USAGE:  	Executed during intial phase of OSD TS for unlocking bitlockered
    ' 			partition whithin WindowsPE (PXE Deployments)
    '
    ' Created: 
    '	v1.0 - 11/16/2011 - William Bracken
    '  
    '
    '===============================================================================
    
    
    Option Explicit 
    Const ADS_SECURE_AUTHENTICATION = "&H1" 
    Const ADS_SERVER_BIND = "&H200" 
    Const ScriptVersion = "2.0"
    Const ForReading = 1, ForWriting = 2, ForAppending = 8
    
    Dim objWshShell, colGroups, group, objDSO, objRecordSet, dnFound, objConnection, objCommand
    Dim emval, em, psval, ps, drv,arrComputers,strComputer,objWMIService,objWMI, volumes,volume
    Dim strPathToComputer, objFveInfos, objFveInfo, strName, strRecoveryGuidOctet, strRecoveryGuid
    Dim objShell,objWshScriptExec,objStdOut,strLine, look, strTempString,a, strBitlockerPassword
    Dim objSysInfo, DCArray(2),continue, x, strTempSerial, f, strVariableValue, strVariableName
    Dim strRecoveryPassword, stroperatingSystem, objRecordSet2, env, osd
    Dim strComputerName, objComputer, strRegistryKey, strComputerDistinguishedName 
    Dim objRootDSE, strDNSDomain, adoCommand, adoConnection, adoRecordset, objNS 
    Dim strDomainControllerFQDN, strAccountUserName, strAccountPassword,shell 
    Dim fs:				Set fs = CreateObject("Scripting.FileSystemObject")
    Dim objADSysInfo:	Set objSysInfo = CreateObject("ADSystemInfo")
    Dim strLogFile,strmsg
    
    ' ===================================================
    '	Define Variables
    ' ===================================================
    strAccountUserName = "REPLACEME" 'Injected from WiseScript
    strAccountPassword = "REPLACEME" 'Injected from WiseScript
    
    'Start Logging
    COLLECTMSG "SCRIPT VERSION", ScriptVersion, "X:\Windows\Temp\UnlockPartition.log"
    COLLECTMSG "START",Null, "X:\Windows\Temp\UnlockPartition.log"
    COLLECTMSG "CHECKBITLOCKER", drv, "X:\Windows\Temp\UnlockPartition.log"
    
    '=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    '  BEGIN
    '-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    ' Call functions
    CheckifEncrypted
    
    'if encrypted, this function initiates the entire process
    CallConnectAD
    
    
    
    ' ===================================================
    '	FUNCTION: CallConnectAD
    ' ===================================================
    Function CallConnectAD
    WScript.Echo "* Starting Function: CallConnectAD"
    	DCArray(0) = "DC1.yourdomain.com"
    	DCArray(1) = "DC2.yourdomain.com"
    	DCArray(2) = "DC3.yourdomain.com"
    	continue = 1
    	For Each strDomainControllerFQDN In DCArray
    	COLLECTMSG "CALLCONNECTAD", strDomainControllerFQDN, "X:\Windows\Temp\UnlockPartition.log"
    		If continue = 1 then
    			ConnectAD(strDomainControllerFQDN)
    			COLLECTMSG "CONNECTAD", Err.Number, "X:\Windows\Temp\UnlockPartition.log"
    			WScript.Echo Err.Number
    			If Err.Number = 0 Then
    				GetADComputerList(strDomainControllerFQDN)
    				continue = 0
    			Else
    				continue = 1
    			End If
    		End If 
    	Next
    End Function
    
    
    ' ===================================================
    '	FUNCTION: ConnectAD
    ' ===================================================
    Function ConnectAD(strDomainControllerFQDN)
    COLLECTMSG "CONNECTAD", "Starting Function", "X:\Windows\Temp\UnlockPartition.log"
    WScript.Echo "* Starting Function: ConnectAD(" & strDomainControllerFQDN & ")"
    	Set objNS = GetObject("LDAP:") 
    	Set objRootDSE = objNS.OpenDSObject("LDAP://" & strDomainControllerFQDN & "/RootDSE", strAccountUserName, strAccountPassword, ADS_SERVER_BIND Or ADS_SECURE_AUTHENTICATION) 
    	strDNSDomain = objRootDSE.Get("defaultNamingContext") 
    	Set adoCommand = CreateObject("ADODB.Command") 
    	Set adoConnection = CreateObject("ADODB.Connection") 
    	adoConnection.Provider = "ADsDSOObject" 
    	adoConnection.Properties("User ID") = strAccountUserName 
    	adoConnection.Properties("Password") = strAccountPassword 
    	adoConnection.Properties("Encrypt Password") = True 
    	adoConnection.Properties("ADSI Flag") = ADS_SERVER_BIND Or ADS_SECURE_AUTHENTICATION 
    	adoConnection.Open "Active Directory Provider" 
    	adoCommand.ActiveConnection = adoConnection 
    	adoCommand.Properties("Page Size") = 100 
    	adoCommand.Properties("Timeout") = 30 
    	adoCommand.Properties("Cache Results") = False 
    End Function
    
    
    ' ===================================================
    '	FUNCTION: CheckifEncrypted
    ' ===================================================
    Function CheckifEncrypted
    COLLECTMSG "CHECKIFENCRYPTED", "Starting Function", "X:\Windows\Temp\UnlockPartition.log"
    	arrComputers = Array(".")
    	For Each strComputer In arrComputers
      		Set objWMIService = GetObject("winmgmts:\\" & strComputer _
                     	& "\root\CIMV2\Security\MicrosoftVolumeEncryption")
      		Set volumes = objWMIService.InstancesOf("Win32_EncryptableVolume")
    	  	For each volume In volumes
          		emval = volume.GetEncryptionMethod(em)
          		psval = volume.GetProtectionStatus(ps) 
          		drv = volume.DriveLetter
          		COLLECTMSG "CHECKIFENCRYPTED", "VOLUME: " & drv, "X:\Windows\Temp\UnlockPartition.log"
          		WScript.Echo "VOLUME: " & drv
          		COLLECTMSG "CHECKBITLOCKER", drv, "X:\Windows\Temp\bitlockervolume.log"
          		'Volume              Encryption        Protection
    			'Status                Method             Status
    			'----------------------------------------------
    			'LOCKED                 1                    2
    			'UNLOCKED             	1                    1
    			'----------------------------------------------
    			'Not BITLOCKED     		0                    0
    			COLLECTMSG "CHECKIFENCRYPTED", "Encryption Method: " & em, "X:\Windows\Temp\UnlockPartition.log"
    			COLLECTMSG "CHECKIFENCRYPTED", "ProtectionStatus: " & ps, "X:\Windows\Temp\UnlockPartition.log"
          		If ps = 2 Then
          			WScript.Echo "Partition " & drv & " is Bitlockered AND locked"
          			strVariableName = "ISBITLOCKED"
          			strVariableValue = "2"
          			SetTSVariable strVariableName,strVariableValue
          			GetLocalBitlockerPassword(drv)
          		ElseIf ps = 1 Then
          			WScript.Echo "Partition " & drv & " is BitLockered but NOT locked"
          			strVariableName = "ISBITLOCKED"
          			strVariableValue = "1"
          			SetTSVariable strVariableName,strVariableValue
          			WScript.Quit
          		Elseif ps = 0 Then
          			If em = 1 Then
          				WScript.Echo "Partition " & drv & " is NOT BitLockered"
          				strVariableName = "ISBITLOCKED"
          				strVariableValue = "1"
          				SetTSVariable strVariableName,strVariableValue
          				WScript.Quit
          			Else
          				WScript.Echo "Partition " & drv & " is NOT BitLockered"
          				strVariableName = "ISBITLOCKED"
          				strVariableValue = "0"
          				SetTSVariable strVariableName,strVariableValue
          				WScript.Quit
          			End If
    
          		Else
          			WScript.Echo "Unable To determine status of" & drv
          			WScript.Quit
          		End if
      		Next
    	Next
    End Function
    
    
    ' ===================================================
    '	FUNCTION: GetLocalBitlockerPassword
    ' ===================================================
    Function GetLocalBitlockerPassword(drv)
    	Set objShell = CreateObject("WScript.Shell") 
     	Set objWshScriptExec = objShell.Exec("manage-bde.exe -protectors -get " & drv) 
     	Set objStdOut = objWshScriptExec.StdOut 
      	look = 0
      	Do While Not objStdOut.AtEndOfStream
      		While look = 0
    	    	strLine = objStdOut.ReadLine
        		If InStr(strLine,"ID:") Then
    	    		look = 1
    	    		strTempString = Split(strLine,":")
    	    		For Each x In strTempString
    	    			If InStr(x,"{") Then
    	    				strBitlockerPassword = Trim(x)
    	    			End if
    	    			
    	    		Next
            	Exit do
            	End If
            Wend
        loop	 
    End Function
    
    
    ' ===================================================
    '	FUNCTION: GetADComputerList
    ' ===================================================
    Function GetADComputerList(strDomainControllerFQDN)
    	Const ADS_SCOPE_SUBTREE = 2
    	adoCommand.CommandText = _
    	    "Select Name, operatingSystem from 'LDAP://" & strDomainControllerFQDN & "/DC=yourdomain,DC=com' " _
            	& "Where objectClass='computer'"  
    	adoCommand.Properties("Page Size") = 1000
    	adoCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 
    	Set objRecordSet2 = adoCommand.Execute
    	objRecordSet2.MoveFirst
    	
    	Do Until objRecordSet2.EOF
    		strComputerName = objRecordSet2.Fields("Name").Value
    		stroperatingSystem = objRecordSet2.Fields("operatingSystem").Value
    		If stroperatingSystem = "Windows 7 Enterprise" Then
    			GetBitLockerKey strDomainControllerFQDN,strDNSDomain,strComputerName
    		ElseIf InStr(stroperatingSystem,"Vista") Then
    			GetBitLockerKey strDomainControllerFQDN,strDNSDomain,strComputerName
    		End if
    	    objRecordSet2.MoveNext
    	Loop
    End Function
    
    
    ' =============================================================================
    ' Method:       COLLECTMSG
    ' Description:  Collections txt for logging to log file
    ' =============================================================================
    Sub COLLECTMSG(szLocation, errmsg, strLogFile)
    	on error resume next
    	Dim f
    	strmsg=errmsg
       	Set f = fs.OpenTextFile(strLogFile, ForAppending, True)
        strmsg =   now & " | " & szLocation & " | "& errmsg      
    	if err<>0 then 
    		strmsg=strmsg & vbCrLf _
    		& "Error | " & Hex(Err.Number) & " h   (" & CStr(Err.Number) & ") | Description |" & Err.Description
    	end if
    	f.Writeline strmsg
        f.Close 
    	strmsg=""
        Err.Clear
    End Sub
    
    
    ' ===================================================
    '	FUNCTION: HexByte
    ' ===================================================
    Function HexByte(b)
          HexByte = Right("0" & Hex(b), 2)
    End Function 
    
    
    ' ===================================================
    '	FUNCTION: ConvertOctetGuidToHexString
    ' ===================================================
    Function ConvertOctetGuidToHexString(ByteArray)
      Dim Binary, S
      Binary = CStr(ByteArray)
      On Error Resume Next
      S = "{"
      S = S & HexByte(AscB(MidB(Binary, 4, 1)))
      S = S & HexByte(AscB(MidB(Binary, 3, 1)))
      S = S & HexByte(AscB(MidB(Binary, 2, 1)))
      S = S & HexByte(AscB(MidB(Binary, 1, 1)))
      S = S & "-"  
      S = S & HexByte(AscB(MidB(Binary, 6, 1)))
      S = S & HexByte(AscB(MidB(Binary, 5, 1)))
      S = S & "-"  
      S = S & HexByte(AscB(MidB(Binary, 8, 1)))
      S = S & HexByte(AscB(MidB(Binary, 7, 1)))
      S = S & "-"  
      S = S & HexByte(AscB(MidB(Binary, 9, 1)))
      S = S & HexByte(AscB(MidB(Binary, 10, 1)))
      S = S & "-"  
      S = S & HexByte(AscB(MidB(Binary, 11, 1)))
      S = S & HexByte(AscB(MidB(Binary, 12, 1)))
      S = S & HexByte(AscB(MidB(Binary, 13, 1)))
      S = S & HexByte(AscB(MidB(Binary, 14, 1)))
      S = S & HexByte(AscB(MidB(Binary, 15, 1)))
      S = S & HexByte(AscB(MidB(Binary, 16, 1)))
      S = S & "}"
    
      On Error GoTo 0
      ConvertOctetGuidToHexString = S
    End Function 
    
    
    ' ===================================================
    '	FUNCTION: GetStrPathToComputer
    ' ===================================================
    Function GetStrPathToComputer(strDomainControllerFQDN,strDNSDomain,strComputerName)
    	adoCommand.CommandText = "Select DistinguishedName from 'LDAP://" & strDomainControllerFQDN & "/" & strDNSDomain & "' Where objectCategory='Computer' and Name='" & strComputerName & "'" 
    
        Set objRecordSet = adoCommand.Execute 
        If objRecordSet.EOF Then
          WScript.echo "The computer name '" & strComputerName & "' cannot be found."
          WScript.Quit 1
        End If
    
        ' Found object matching name
        Do Until objRecordSet.EOF 
          dnFound = objRecordSet.Fields("distinguishedName")
          GetStrPathToComputer = "LDAP://" & strDomainControllerFQDN & "/" & dnFound
          objRecordSet.MoveNext
        Loop 
    
        ' Clean up. 
        Set objConnection = Nothing 
        Set objCommand = Nothing 
        Set objRecordSet = Nothing 
    End Function
    
    
    ' ===================================================
    '	FUNCTION: GetBitLockerKey
    ' ===================================================
    Function GetBitLockerKey(strDomainControllerFQDN,strDNSDomain,strComputerName)
    	Set objDSO = GetObject("LDAP:")	
    	strPathToComputer = GetStrPathToComputer(strDomainControllerFQDN,strDNSDomain,strComputerName)
    	
    	Const ADS_SECURE_AUTHENTICATION = 1
    	Const ADS_USE_SEALING = 64 '0x40
    	Const ADS_USE_SIGNING = 128 '0x80
    
    	' --------------------------------------------------------------------------------
    	' Get all BitLocker recovery information from the Active Directory computer object
    	' -------------------------------------------------------------------------------
    	' Get all the recovery information child objects of the computer object
    	Set objFveInfos = objDSO.OpenDSObject(strPathToComputer, strAccountUserName, strAccountPassword, _
    	ADS_SECURE_AUTHENTICATION + ADS_USE_SEALING + ADS_USE_SIGNING)
    	objFveInfos.Filter = Array("msFVE-RecoveryInformation")
    
    	' Iterate through each recovery information object 
    	For Each objFveInfo in objFveInfos
    	
       		strName = objFveInfo.Get("name")
    			
       		strRecoveryGuidOctet = objFveInfo.Get("msFVE-RecoveryGuid")
       		strRecoveryGuid = ConvertOctetGuidToHexString(strRecoveryGuidOctet)
       		strRecoveryPassword = objFveInfo.Get("msFVE-RecoveryPassword")
       		If strRecoveryGuid = strBitlockerPassword Then
    	   		WScript.echo  
       			WScript.echo "name: " + strName 
       			WScript.echo "msFVE-RecoveryGuid: " + strRecoveryGuid
       			WScript.echo "msFVE-RecoveryPassword: " + strRecoveryPassword
       			UnlockBitlockerPartition strRecoveryPassword
       		End if
    	   	If len(strRecoveryGuid) <> 38 Then
          		WScript.echo "WARNING: '" & strRecoveryGuid & "' does not appear to be a valid GUID."
       		End If
    	Next
    End Function
    
    
    ' ===================================================
    '	FUNCTION: UnlockBitlockerPartition
    ' ===================================================
    Function UnlockBitlockerPartition (strRecoveryPassword)
    	Set objShell = CreateObject("WScript.Shell") 
     	Set objWshScriptExec = objShell.Exec("manage-bde.exe -unlock " & drv & " -rp " & strRecoveryPassword) 
     	Set objStdOut = objWshScriptExec.StdOut 
      	While Not objStdOut.AtEndOfStream
        	strLine = objStdOut.ReadLine
    		WScript.Echo strLine
        Wend
         Set objWshScriptExec = objShell.Exec("manage-bde.exe -protectors -disable " & drv ) 
     	Set objStdOut = objWshScriptExec.StdOut 
      	While Not objStdOut.AtEndOfStream
        	strLine = objStdOut.ReadLine
    		WScript.Echo strLine
        Wend
    End Function
    
    
    ' ===================================================
    '	FUNCTION: SetTSVariable
    ' ===================================================
    Function SetTSVariable (strVariableName, strVariableValue)
    	COLLECTMSG "SETTSVARIABLE", strVariableName & "=" & strVariableValue, "X:\Windows\Temp\UnlockPartition.log"
    	Set env = CreateObject("Microsoft.SMS.TSEnvironment") 
    	env(strVariableName) = strVariableValue
    	COLLECTMSG "SETTSVARIABLE", Err.Number, "X:\Windows\Temp\UnlockPartition.log"
    End Function
    
    
    WScript.Quit


    2.  Executes the vbscript then deletes it.
    3.  The script creates a log file in %TEMP% called bitlockervolume.log.  The log file is read in to WiseScript and parsed into a variable BITLOCKED_VOL (to be used later)
    4.  The script then copies a diskpart script (checkpart.txt) that checks the local drive for partition details and pipes out to a log "checkpart.log"
         Command:
         diskpart /s "%TEMP%\checkpart.txt" > "%TEMP%\checkpart.log"

         checkpart.txt:
         rem Diskpart
         sel disk 0
         detail disk

    5.  The script then parses through that log to get the list of volumes the drive currently has and stores it in an "array" (WiseScript array is basicallt a CRLF seperate list in a variable).  Each line of this variable is compared to the BITLOCKED_VOL variable above to see if there is already a "system reserved" partition (effectively checking to see if any volume exists that does NOT equal the volume of the bitlocked partition).
    6.  If a second volume does exist, the script copies another dispart script file to %TEMP% (removedriveletter.txt) and then injects the volume number and letter for the partition that was not the bitlocker partition and then executes it to remove the drive letter.
         Command:
         diskpart /s %TEMP%\removedrvletter.txt

         removedrvletter.txt:
         sel disk 0
         sel vol REPLACE
         remove letter=REPLACE
         exit

    At this point I now have an unlocked Bitlocker partition with the volume letter of D: that SCCM was unable to see upon start of the Task Sequence, and a "hidden" partition that SCCM used for the first Task in the Sequence that started all the above.  Now that SCCM can no longer see it, the next task that is executed gets redirected to the now unlocked D: drive.   This is assuming there was more than a single volume.  If there was only a single volume then this implies no partition was bitlocked and I now just have a single volume to check for free space for creating a "System Reserve" partition as welll as free space to perform a hardlink migration (using 50 MB currently as the threshold for USMT and 500 MB threshold for creating the System Reserved partition)

    7.  The next task in my TS executes the vbscript to check the disk for paritions and free space:
         "Run Command Line":
         cscript.exe CheckPartitions.vbs

    Option Explicit
    
    Const HARD_DISK = 3
    Const ForReading = 1, ForWriting = 2, ForAppending = 8
    Dim strComputer, objWMIService, objDisk, colDisks, freespace, drv, count, disksize
    Dim partlist, part, Cpartsize, Cfreespace, Dpartsize, Dfreespace, strLine, strShellDir
    Dim strVariableName, strVariableValue, env, objShell, objWshScriptExec, objStdOut
    
    strComputer = "."
    CountPartitions
    'createpartitions
    
    ' ===================================================
    '	FUNCTION: CountPartitions
    ' ===================================================
    Function CountPartitions
    	Set objWMIService = GetObject("winmgmts:" _
    	    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
    	Set colDisks = objWMIService.ExecQuery _
    	    ("Select * from Win32_LogicalDisk Where DriveType = " & HARD_DISK & "")
    	For Each objDisk in colDisks
    		If objDisk.DeviceID = "X:" Then
    		ElseIf objDisk.DeviceID = "C:" Then
    			count = (count + 1)
    			Cpartsize = (objDisk.size /1024\1024+1)
    			Cfreespace = (objDisk.freespace /1024\1024+1)
    		ElseIf objDisk.DeviceID = "D:" Then
    			count = (count + 1)
    			Dpartsize = (objDisk.size /1024\1024+1)
    			Dfreespace = (objDisk.freespace /1024\1024+1)
    		Else	
    		End if
    	Next
    	WScript.Echo "Total Partitions: " & count
    End Function
    
    If count = 2 Then
    	If Dfreespace < 50 Then
    		WScript.Echo "Not enough free space for hardlink on D: | " & Dfreespace
    		'set USMT SMP
    		SetTSVariable "USMTTYPE","SMP"
    	Else
    		WScript.Echo "Free space is sufficient for hardlink on D: | " & Dfreespace
    		'set USMT Hardlink D:
    		SetTSVariable "USMTTYPE","HARDLINK"
    	End If
    ElseIf count = 1 Then
    	If Cfreespace < 50 Then
    		WScript.Echo "Not enough free space for hardlink on C: | " & Dfreespace
    		SetTSVariable "USMTTYPE","SMP"
    		SetTSVariable "FORMATANDPART","YES"
    		' set USMT SMP
    		' partition and format C:/D: (done via Task Sequence "Partition and Format drive"
    	ElseIf Cfreespace >= 500 Then
    		WScript.Echo "Free space is sufficient for resizing C: | " & Dfreespace
    		CreatePartitions
    		' Shrink C:
    		' Create D:
    		' Swap drv letters
    		' make C: active
    		' set USMT hardlink D:
    		SetTSVariable "USMTTYPE","HARDLINK"
    	End If
    End If
    
    
    ' Set TS Variable
    Function SetTSVariable (strVariableName, strVariableValue)
    	Set env = CreateObject("Microsoft.SMS.TSEnvironment") 
    	env(strVariableName) = strVariableValue
    End Function
    
    Function CreatePartitions
    	Set objShell = CreateObject("WScript.Shell")
    	strShellDir = objShell.currentdirectory
    	wscript.echo strShellDir
      	Set objWshScriptExec = objShell.Exec("cmd.exe /c diskpart.exe /s createpars.txt") 
     	Set objStdOut = objWshScriptExec.StdOut 
      	While Not objStdOut.AtEndOfStream
        	strLine = objStdOut.ReadLine
    		WScript.Echo strLine
        Wend
    End Function
    

    If the script above determines that only a single partion exists it checks if there is enough free space to "divide" the partition.  If so it executes a diskpart script to shrink the partition and create the new system reserved partition.  If not enough space it sets a TS variable that is checked by the built in TS task of "format and partition".

    The diskpart script to divide the partition if applicable (createpars.txt):
    sel disk 0
    sel par 1
    shrink minimum=300
    create par pri
    format fs=ntfs
    sel par 1
    remove letter=C:
    assign letter=D:
    sel par 2
    assign letter=C:
    active
    exit

    8.  The 3rd (and final step for manipluating partitions) executes a diskpart script to add the drive letter back to the "hidden" partition IF the TS variable ISBITLOCKED=2.  (Set in the UnlockPartition.vbs script above)

    You may also have noticed that I am setting USMTTYPE TS variable based on my findings.  If the drive has enough space locally then USMTTYPE is set to HARDLINK.  If there was not enough free space or has to be repartitioned due to a single volume then USMTTYPE is set to SMP (state migration point).  The variables will be used later in the task sequence to determine what type of USMT to perform.

    As I read this back this sounds very confusing but hopefully the logic is enough to get what I am doing.  As stated, I am still tweaking this process and once fully working with USMT I will cleanup the scripts/process and hopefully write a more detailed step by step to make it easier to follow!

    If you have any questions please dont hesitate to ask.

    Monday, November 28, 2011 5:44 PM
  • Yeah, sorry probably an important piece of the puzzle.  here's a link to show you how to add ADSI to WinPE:

    http://www.deployvista.com/Blog/JohanArwidmark/tabid/78/EntryID/127/language/sv-SE/Default.aspx


    Wednesday, November 30, 2011 6:22 PM
  • Hi Niall,

    Would you be able to provide me with your drive re-assign script?

    My email is 1.geekasaurus@gmail.com

    To everyone else sorry for digging up such an old thread but I just hit the same roadblocks today :(

    Saturday, June 21, 2014 2:40 PM
  • Hi Niall,

    Niall just left on holidays and will not be back for ~ 2 weeks.

    http://www.enhansoft.com/

    Saturday, June 21, 2014 3:30 PM
    Moderator
  • hi

    the reassign script is available for download in the following link

    http://www.windows-noob.com/forums/index.php?/topic/7294-the-cm12-bitlocker-frontend-hta/?p=36701

    it's in the bitlocker folder when you unzip everything, called diskpart.txt

    cheers

    niall



    Step by Step Configuration Manager Guides > 2012 Guides | 2007 Guides | I'm on Twitter > ncbrady

    Wednesday, September 3, 2014 6:42 AM
    Moderator