SCCM Task Sequence – Robocopy and WIM Image Backups from WinPE

This post will show you how to perform automated robocopy and WIM Image backups of all available drives (even Bitlocked!) during a task sequence in WinPE.

Requirements

The Script

Make a new SCCM package named “Scripts – Backups” and save this script as “Run-RobocopyBackup.wsf” in the package source directory.

Notes:

  • You will need to search for and change “sStateRoot” to your backup server path.
  • The script will create a folder within the sStateRoot path with the computer’s name.
  • The script will skip the Program Files, Program Files x86, Windows, and Default User directories.
  • The script will fail the TS Action if it doesn’t find at least 1 volume containing the folder “C:\Users” or “C:\Documents and Settings”.
'Run-RobocopyBackup.wsf

<script type="text/javascript" language="VBScript" src="../../wdpackage/scripts/ZTIUtility.vbs">// <![CDATA[

<script language="VBScript">

' //***************************************************************************
' // ***** Script Header *****
' //
' // Solution: Solution Accelerator for Microsoft Deployment
' // File: Z-Sample.wsf
' //
' // Purpose: Template
' //
' // Usage: cscript Z-Sample.wsf [/debug:true]
' //
' // Customer Build Version: 1.0.0
' // Customer Script Version: 1.0.0
' // Customer History:
' //
' // ***** End Header *****
' //***************************************************************************

'//----------------------------------------------------------------------------
'//
'// Global constant and variable declarations
'//
'//----------------------------------------------------------------------------

'Option Explicit

Dim iRetVal

'//----------------------------------------------------------------------------
'// End declarations
'//----------------------------------------------------------------------------

'//----------------------------------------------------------------------------
'// Main routine
'//----------------------------------------------------------------------------

On Error Resume Next
iRetVal = ZTIProcess
ProcessResults iRetVal
On Error Goto 0

'//---------------------------------------------------------------------------
'//
'// Function: ZTIProcess()
'//
'// Input: None
'//
'// Return: Success - 0
'// Failure - non-zero
'//
'// Purpose: Perform main ZTI processing
'//
'//---------------------------------------------------------------------------
Function Throw_Warning(msg)
	msg = "Run-Robocopy (WARNING): " & msg
	oLogging.CreateEntry msg, LogTypeError
	msgbox msg
End Function

Function Write_Log(msg)
	msg = "Run-Robocopy: " & msg
	oLogging.CreateEntry msg, LogTypeError
End Function

Function Test_UsersDir(aSourcePaths)

	If Right(sPath,1) = "\" Then
		sPath = Left(sPath,(Len(sPath) - 1))
	End If
	retval = False
	For Each sPath in aSourcePaths
		sFolder = sPath & "\Users\"
		sFolder2 = sPath & "\Documents and Settings\"
		'wscript.echo "searching: " & sFolder
		If oFSO.FolderExists(sFolder) = True Then
			retval = True
			Exit For
		ElseIf oFSO.FolderExists(sFolder2) = True Then
			retval = True
			Exit For
		End If
	Next

	Test_UsersDir = retval
End Function

Function Get_ComputerName
	sComputerName = oUtility.ComputerName
	Get_ComputerName = sComputerName
End Function

Function Test_FolderAccessOK(sTargetRoot)
	On Error Resume Next
	sTargetRoot = Trim_Slash(sTargetRoot) + "\"
	sFile = "scanstatetest_1111.txt"

	'create a scanstatetest_1111.txt file at target
	Set objFile = oFSO.CreateTextFile(sTargetRoot & sFile)
	objFile.close

	'test file existance
	bCreated = oFSO.FileExists(sTargetRoot & sFile)

	'delete file
	oFSO.DeleteFile(sTargetRoot & sFile)

	'test file existance
	bExistsAfterDelete = oFSO.FileExists(sTargetRoot & sFile)

	If bCreated = True And bExistsAfterDelete = False Then
		retval = True
	Else
		retval = False
	End If

	Test_FolderAccessOK = retval
End Function

Function Get_TargetRoot(sTargetRoot)
	sTargetRoot = Trim_Slash(sTargetRoot)

	'try path sTargetRoot & \ & "usmt-v4_capture" & i
	'return first path not found
	bFoundPath = False
	bExists = Null
	For i = 0 to 100
		sFolder = sTargetRoot & "\" & "robocopy_" & i & "\"
		bExists = oFSO.FolderExists(sFolder)
		If bExists = False Then
			bFoundPath = True
			Exit For
		End If
	Next

	If bFoundPath = False Then
		retval = False
	Else
		retval = sFolder
	End If

	Get_TargetRoot = retval
End Function

Function Make_FolderPath(strPath)
	'ref: http://www.windowsitpro.com/article/tips/jsi-tip-10441-how-can-vbscript-create-multiple-folders-in-a-path-like-the-mkdir-command-
	Dim strParentPath, objFSO
  Set objFSO = CreateObject("Scripting.FileSystemObject")
	On Error Resume Next
	strParentPath = objFSO.GetParentFolderName(strPath)

  If Not objFSO.FolderExists(strParentPath) Then MakeDir strParentPath
	If Not objFSO.FolderExists(strPath) Then objFSO.CreateFolder strPath
	On Error Goto 0
  MakeDir = objFSO.FolderExists(strPath)
End Function

Function Get_SourcePath
	sFolder = "C:\Users"
	sFolder2 = "C:\Documents and Settings"

	If oFSO.FolderExists(sFolder) = True Then
		retval = sFolder
	Else
		retval = sFolder2
	End If

	If oFSO.FolderExists(retval) = False Then
		retval = False
	End If

	Get_SourcePath = retval
End Function

Function Run_RobocopyBackup(aSourcePaths,sBackupsPath)
	bContinueRun = True

	sTargetRoot = Get_TargetRoot(sBackupsPath)
	bVarTest = False
	bVarTest = Is_Null(sTargetRoot)
	If bVarTest = True Or sTargetRoot = False Then
		bContinueRun = False
		msg = "Failed to generate a target directory."
		Throw_Warning msg
	End If

	If bContinueRun = True Then
		Dim aLines
		Redim aLines(0)
		i = 0
		For Each sSourcePath in aSourcePaths
			bFailThisPath = False
			If sSourcePath = "" Then
				bFailThisPath = True
			End If

			If bFailThisPath = False Then
				sTargetPath = Build_TargetPath(sTargetRoot,sSourcePath)
				If Right(sTargetPath,1) <> "\\" Then
					sTargetPath = sTargetPath & "\"
				End If

				'make folder path @ target
				bPathsMade = Make_FolderPath(sTargetPath)

				''write our batch file
				''write our batch file'ref: http://forums.datamation.com/intranet-journal/130-writing-text-file-using-vbscript.html
				If Ubound(aLines) < i Then
					Redim Preserve aLines(i)
				End If

				'leave this here -- something is betting passed By_Ref in Make_FolderPath and screwing it up. I should fix this...I've been assuming everything was by_val :/
				'If the slash doesn't get added, it won't copy the entire contents of the source if the source is a drive letter and the working path on that drive isn't the root!!
				If Right(sSourcePath,1) = "\" Then
					sSourcePath = sSourcePath
				Else
					sSourcePath = sSourcePath & "\"
				End If
				sExpression = "robocopy.exe " & sSourcePath & " " & sTargetPath & " /MT /XJ /DCOPY:T /R:1 /W:0 /B /E /XF pagefile.sys /XD ""C:\System Volume Information"" ""C:\Windows"" ""C:\Users\Default User"" ""c:\Program Files (x86)"" ""C:\Program Files"""
				aLines(i) = sExpression
				i = i + 1
			End If
		Next

		For Each sLine in aLines
			msg = "Running the following command: " & sLine
			Write_log msg
			sExitCode = oShell.Run(sLine,1,True)
			If sExitCode = "16" Then
				msg = "Robocopy exited with error code 16 - fatal error - no files were copied. Quitting script."
				Throw_Warning msg
				Wscript.Quit(100)
			End If
		Next

		retval = true
	End If

	Run_ScanState = retval
End Function

Function Build_TargetPath(sTargetRoot,sSourcePath)
	If Right(sTargetRoot,1) = "\" Then
		sTargetRoot = Left(sTargetRoot,(Len(sTargetRoot) - 1))
	End If
	If Right(sSourcePath,1) = "\" Then
		sSourcePath = Left(sSourcePath,(Len(sSourcePath) - 1))
	End If

	sSourceTemp = sSourcePath
	sSourceTemp = Replace(sSourceTemp,":","")
	sSourceTemp = Replace(sSourceTemp,"\","_")

	sTargetPath = sTargetRoot & "\" & sSourceTemp

	Build_TargetPath = sTargetPath
End Function

Function Is_Null(oVariable)
	If oVariable = "" Or oVariable = Null Then
		retval = True
	Else
		retval = False
	End If
	Is_Null = retval
End Function

Function Trim_Slash(sString)
	If Right(sString,1) = "\" Then
		sString = Left(sString,(Len(sString) - 1))
	End If
	trim_slash = sString
End Function

''''''''''''merged from enumerate-mountpoints.vbs
Function Get_UniqueFixedMountPoints
	strComputer = "."
	Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")
	Set colItems = objWMIService.ExecQuery _
	    ("SELECT * FROM Win32_MountPoint")

	Dim MPVolumes(1000)

	Dim aMPs
	Redim aMPs(0)
	i = 0
	For Each objItem In colItems
		blnSkipMP = False
		sCurrentVolume = objItem.Volume
		For Each sStoredVolume in MPVolumes
			If sStoredVolume = sCurrentVolume Then
				blnSkipMP = True
			End If
		Next

		If blnSkipMP = False Then
			MPVolumes(i) = objItem.Volume
			If Ubound(aMPs) < i Then
				Redim Preserve aMPs(i)
			End If
			sDirName = objItem.Directory
			aDirName = Split(sDirName,"=")
			sDirectory = Replace(aDirName(1),"\\","\")
			sDirectory = Left(sDirectory,(len(sDirectory) - 1))
			sDirectory = Right(sDirectory,(len(sDirectory) - 1))
			If Right(sDirectory,1) = "\" Then
				sDirectory = Left(sDirectory,(len(sDirectory) - 1))
			End If
			'wscript.echo sDirectory
			aMPs(i) = sDirectory
			i = i + 1
		End If
	Next

	Get_UniqueFixedMountPoints = aMPs
End Function

Function Get_LogicalDisks
	strComputer = "."
	Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")
	Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")
	Set colItems = objWMIService.ExecQuery _
	    ("SELECT * FROM Win32_LogicalDisk")

	Dim aLDs
	Redim aLDs(0)
	i = 0
	For Each objItem In colItems
		If Ubound(aLDs) < i Then
			Redim Preserve aLDs(i)
		End If
		If objItem.DriveType = 3 Then
			sDevID = objItem.DeviceID
			aLDs(i) = sDevID
			i = i + 1
			'wscript.echo "found ld: " & sDevID
		End If
	Next
	Get_LogicalDisks = aLDs
End Function

Function Get_FixedVolumePaths(aMPs,aLDs)
	'strip any mount points that do not start with an LD
	dim aFixedVolumePaths
	Redim aFixedVolumePaths(0)
	For Each sMountPoint in aMPs
		bFoundLDforMP = False
		sMPDriveLetter = Left(sMountPoint,2)
		'wscript.echo "sMPDriveLetter: " & sMPDriveLetter

		'check that the mp has a drive letter in the ld list
		For Each sLogicalDisk in aLDs
			'sLDDriveLetter = Left(sLogicalDisk,2)
			'wscript.echo "sLogicalDisk: " & sLogicalDisk
			If sMPDriveLetter = sLogicalDisk Then
				bFoundLDforMP = True
			End If
		Next

		If bFoundLDforMP = True Then
			If Ubound(aFixedVolumePaths) < i Then
				Redim Preserve aFixedVolumePaths(i)
			End If
			aFixedVolumePaths(i) = sMountPoint
			i = i + 1
		End If
	Next

	Get_FixedVolumePaths = aFixedVolumePaths
End Function

''''''''''''MAIN
Function ZTIProcess()

		iRetVal = Success

		ZTIProcess = iRetval

		'!!!!!!!!!!!   INSERT YOUR CODE HERE   !!!!!!!!!!!!
		sStateRoot = "\\backupserver\share\robocopy"
		Set oTSEnv = WScript.CreateObject("Microsoft.SMS.TSEnvironment")

		'Get all potential source paths
		Dim aMPs
		aMPs = Get_UniqueFixedMountPoints
		Dim aLDs
		aLDs = Get_LogicalDisks
		Dim aSourcePathsTemp
		aSourcePathsTemp = Get_FixedVolumePaths(aMPs,aLDs)

		'strip out X:\ since it's always the WinPE disk
		Dim aSourcePaths
		Redim aSourcePaths(0)
		i = 0
		For Each sPath in aSourcePathsTemp
			If UBound(aSourcePaths) < i Then
				Redim Preserve aSourcePaths(i)
			End If
			If InStr(sPath,"X:") = False And sPath <> "" And sPath <> vbNull Then
					aSourcePaths(i) = sPath
					i = i + 1
			End If
		Next

		'Test all paths for C:\users or C:\documents and settings
		bUsersDirFound = False
		bUsersDirFound = Test_UsersDir(aSourcePaths)
		If bUsersDirFound <> True Then
			Throw_Warning "Failed to find the user directories."
			Wscript.Quit(100)
		End If

		sComputerName = Get_ComputerName
		If sComputerName = null Or sComputerName = "" Or sComputerName = -1 Then
			Throw_Warning "Failed to find the computer name."
			Wscript.Quit(300)
		End If

		bFolderAccess = False
		bFolderAccess = Test_FolderAccessOK(sStateRoot)
		If Is_Null(bFolderAccess) = True Or bFolderAccess = False Then
			msg = "Script doesn't have access to the target root: " & sStateRoot
			Throw_Warning msg
			bContinue = False
		Else
			sTargetPath = (Trim_Slash(sStateRoot)) + "\" + sComputerName
			Run_RobocopyBackup aSourcePaths,sTargetPath
		End If

End Function
// ]]></script>

Getting the Actions Right

We’ll need a few TS Actions to make this work.

  1. First, create a new group called “Backups”.
  2. Next, create a “Use Microsoft Deployment Toolkit Package” action.
  3. It would be a good idea to put a “Decrypt Bitlocker” action directly after the MDT action. See the blog post above under the “Requirements” header for detailed instructions.
  4. Create a “Connect to Network Folder” action with the following parameters. The account must have read\write permissions on the share and the NTFS files\folders in the share.
    Name: Connect to Backup Share
    Share: \\backupserver\sharename
    Account: domain\shareuser
  5. Create a “Set Task Sequence Variable” action with the following parameters. This only affects the WIM captures.
    Name: Set Backup Drive to All
    TS Variable: BackupDrive
    Value: ALL
  6. Create a “Set Task Sequence Variable” action with the following parameters. This is for your WIM captures.
    Name: Set Backup Location
    TS Variable: ComputerBackupLocation
    Value: \\backupserver\sharename\captures
  7. Create a “Run Command Line” action with the following parameters.
    Name: Robocopy Backup
    Command: run-robocopybackup.wsf
    Package: Scripts - Backups
  8. Create a “Run Command Line” action with the following parameters.
    Name: WIM Image Backup
    Command: cscript.exe "%SCRIPTROOT%\ZTIBackup.wsf"
    Package: Scripts - Backups
That’s it! You should now have some backups. 🙂
Advertisements

SCCM Task Sequence – Disable Bitlocker in WinPE

I made a task sequence action that backs up a computer using robocopy before partitioning, only to find that the system is protected by BitLocker. Here’s how to automatically unlock Bitlocker drives in WinPE in a task sequence. This script will only work if you integrated the WinPE ADSI Plug-In into your boot image. For instructions, see this post: “SCCM – Adding Active Directory Support to WinPE 3.1“.

The Script

I didn’t write the script myself; I heavily modified a script that I can’t find the source for. If part of this is yours, please let me know! I know I found it on a blog somewhere…sorry :(. Make a new SCCM package named “Scripts – DecryptDBE”, and save this script as “Auto-DecryptBDE.vbs” in the package source directory.

sUsername = "REMOVED"
sPassword = "REMOVED"
sDCfqdn = "REMOVED--DomainController"

Function Write_Log(msg)
	Wscript.Echo msg
End Function

Function Get_RecoveryKeysFromDN(dn,sDCfqdn,sUsername,sPassword)
	Set objDSO = GetObject("LDAP:")
	strPathToComputer = "LDAP://" & sDCfqdn & "/" & dn

	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, sUsername, sPassword, _
		ADS_SECURE_AUTHENTICATION + ADS_USE_SEALING + ADS_USE_SIGNING)
	objFveInfos.Filter = Array("msFVE-RecoveryInformation")

	'Iterate through each recovery information object and save any existing key packages
	Dim aKeys()
	Redim aKeys(0)
	i = 0
	bFoundKey = False
	For Each objFveInfo in objFveInfos
		bFoundKey = True
		If uBound(aKeys) < i Then
			Redim Preserve aKeys(i)
		End If
		strName = objFveInfo.Get("name")
		strRecoveryPassword = objFveInfo.Get("msFVE-RecoveryPassword")
		sNamePass = strName & "|" & strRecoveryPassword
		aKeys(i) = sNamePass
		i = i + 1
	Next

	If bFoundKey = True Then
		retval = aKeys
	Else
		retval = null
	End If

	Get_RecoveryKeysFromDN = retval
End Function

Function Find_ADRecoveryKey(sBDEPassword,sDCfqdn,sUsername,sPassword)
	'Search for all computer objects
	strBase = "<GC://" & sDCfqdn & ">"
	strFilter = "(&(objectCategory=computer))"
	strQuery = strBase & ";" & strFilter  & ";distinguishedName;subtree"

	''create connection
	Set oConnection = CreateObject("ADODB.Connection")
	oConnection.Provider = "ADsDSOObject"
	oConnection.Properties("User ID") = sUsername
	oConnection.Properties("Password") = sPassword
	oConnection.Properties("Encrypt Password") = True
	oConnection.Properties("ADSI Flag") = ADS_SERVER_BIND Or ADS_SECURE_AUTHENTICATION
	Set oCommand = CreateObject("ADODB.Command")
	oConnection.Open "Active Directory Provider"
	Set oCommand.ActiveConnection = oConnection
	oCommand.CommandText = strQuery
	oCommand.Properties("Page Size") = 100
	oCommand.Properties("Timeout") = 100
	oCommand.Properties("Cache Results") = False

  Set objRecordSet = oCommand.Execute
  If objRecordSet.EOF Then
    WScript.echo "The domain could not be contacted."
    WScript.Quit 1
  End If

  'For each computer object found look through it's keys for the one we want.
  bKeyFound = False
  Do Until objRecordSet.EOF
    dnFound = objRecordSet.Fields("distinguishedName")
    Dim aRecoveryKeys
    aRecoveryKeys = Get_RecoveryKeysFromDN(dnFound,sDCfqdn,sUsername,sPassword)

    If IsArray(aRecoveryKeys) = True Then
    	If Ubound(aRecoveryKeys) > 0 Then
	    	For Each sKey In aRecoveryKeys
					If instr(sKey,sBDEPassword) Then
						msg = "Matching key found under computer dn: """ & dnfound & """."
						write_log msg
						strTempString = Split(sKey,"|")
		    		sRecoveryKey = strTempString(1)
		    		bKeyFound = True
					End If
				Next
			End If

		End If

    If bKeyFound = True Then
    	Exit Do
    Else
			objRecordSet.MoveNext
		End If
  Loop
  ' Clean up.
  Set objConnection = Nothing
  Set objCommand = Nothing
  Set objRecordSet = Nothing

  If bKeyFound = True Then
  	retval = sRecoveryKey
  Else
  	retval = false
  End If

  Find_ADRecoveryKey = retval
End Function

Function Unlock_AllDrivesWithAD(sDCfqdn,sUsername,sPassword)
	On Error Resume Next
	'foreach encrypted drive
	Set oDrivesPasswords = CreateObject("Scripting.Dictionary")
	Set oWMIService = GetObject("winmgmts:\\.\root\CIMV2\Security\MicrosoftVolumeEncryption")
	Set oVolumes = oWMIService.InstancesOf("Win32_EncryptableVolume")

	For each volume In oVolumes
		bDecryptNeeded = False
		'check for encryption
		'ref: http://msdn.microsoft.com/en-us/library/windows/desktop/aa376434(v=vs.85).aspx
		volume.GetEncryptionMethod iBdeMethod
		volume.GetLockStatus iBDEStatus
		volume.GetKeyProtectors 0,VolumeKeyProtectorID
		sDriveLetter = volume.DriveLetter

		If iBDEStatus <> 0 Then
			msg = "Found locked volume. Drive letter: """ & sDriveLetter & """."
			Write_Log msg
			For Each objId in VolumeKeyProtectorID
				msg = "KeyProtector for drive letter """ & sDriveLetter & """: """ & objId & """."
				write_log msg
		  Next
				bDecryptNeeded = True
		End If

		If bDecryptNeeded = True Then
			'loop through all key protectors
			For Each BDEPassword in VolumeKeyProtectorID
				sADRecoveryKey = null
				'search AD for corresponding recovery keys
				sADRecoveryKey = Find_ADRecoveryKey(BDEPassword,sDCfqdn,sUsername,sPassword)
					'attempt unlock
				If sADRecoveryKey <> False Then
					msg = "Unlocking drive with AD key"
					write_log msg
					volume.UnlockWithNumericalPassword sADRecoveryKey
					volume.GetProtectionStatus iBDEStatus
					If iDBEstatus = 0 Then
						msg = "Drive unlocked."
						write_log msg
					Else
						msg = "Failed to unlock the drive."
						write_log msg
						Wscript.Quit(100)
					End If
				End If
			Next
		End If
	Next
End Function

Function Unlock_AllDrivesWithManualKey(sUsername,sPassword)
	On Error Resume Next
	'foreach encrypted drive
	Set oDrivesPasswords = CreateObject("Scripting.Dictionary")
	Set oWMIService = GetObject("winmgmts:\\.\root\CIMV2\Security\MicrosoftVolumeEncryption")
	Set oVolumes = oWMIService.InstancesOf("Win32_EncryptableVolume")

	For each volume In oVolumes
		bDecryptNeeded = False
		'check for encryption
		'ref: http://msdn.microsoft.com/en-us/library/windows/desktop/aa376434(v=vs.85).aspx
		volume.GetEncryptionMethod iBdeMethod
		volume.GetLockStatus iBDEStatus
		volume.GetKeyProtectors 0,VolumeKeyProtectorID
		sDriveLetter = volume.DriveLetter

		If iBDEStatus <> 0 Then
			msg = "Failed to unlock all volumes with AD recovery keys. Asking user for manual key input."
			Write_Log msg
			msg = "Found locked volume. Drive letter: """ & sDriveLetter & """."
			Write_Log msg
			For Each objId in VolumeKeyProtectorID
				msg = "KeyProtector for drive letter """ & sDriveLetter & """: """ & objId & """."
				write_log msg
		  Next
				bDecryptNeeded = True
		End If

		If bDecryptNeeded = True Then
			'loop through all key protectors
			bContinue = True
			For Each BDEPassword in VolumeKeyProtectorID
				If bContinue = False Then
					Exit For
				End If
				'Ask the user for one repeatedly until the drive unlocks or the user presses cancel.
				bContinue = True
				While bContinue = True
					msg = "No key was found in AD for volume " & sDriveLetter & " with public key " & BDEPassword & ". Please enter a password to unlock the drive. Type ""next"" to attempt skipping to the next BDEPassword (if available). Press cancel to quit."
					sUserKey = InputBox(msg)
					If sUserKey = Null Or sUserKey = "" Then
						Wscript.Quit(100)
					ElseIf LCase(sUserKey) = "next" Then
						bContinue = False
					Else
						volume.UnlockWithNumericalPassword sUserKey
						volume.GetLockStatus iBDEStatus
						If iBDEstatus = 0 Then
							msg = "Drive unlocked."
							write_log msg
							bContinue = False
						Else
							msg = "Failed to unlock the drive."
							msgbox msg
						End If
					End If
				Wend
			Next
		End If
	Next
End Function

Unlock_AllDrivesWithAD sDCfqdn,sUsername,sPassword
Unlock_AllDrivesWithManualKey sUsername,sPassword

A couple quick notes: it would be prudent for me (or you) to rewrite this script so that AD credentials were passed by argument instead of hard coded. Also, it’d be nice if it found the domain controllers via DNS SRV records instead of being hard-coded.

Task Sequence Work

Just make a “Run Command-Line” action in your task sequence with the following parameters.

Name: Decrypt Bitlocker Drives
Command: Auto-DecryptBDE.vbs
Package: Scripts - DecryptDBE

I wish you more awesome task sequences!

SCCM – Adding Active Directory Support to WinPE 3.1

Wouldn’t it be awesome if you could do AD queries from WinPE 3.1? I have the process written up deep in another blog post, and it’s come to my attention that it’s hard to find without a pointer.

Check out this post. Start with the section “ADSI Files” leading through “Upgrading to WinPE 3.1”.

Getting Started with SCCM 2007 and Windows 7 OSD (Part 1)

Thanks again!

SCCM Task Sequence – Ask for Computer Name

One of the first things I needed to do after starting the SCCM trek was to ask for the computer name in a deployment task sequence. It’s not that bad; follow along!

Downloads

Scripting

First, save the following source as “PromptForSystemName.vbs”. It’s modified version of the script from t3chn1ck’s wordpress blog post found here: PromptForSystemName vbScript.
'==========================================================================
' NAME: PromptForSystemName.vbs
'
' AUTHOR: Nick Moseley
' DATE  : 6/1/2009
'
' COMMENT: This script will detect if the current assigned value for the computer name
' begins with MININT, indicating that this image is bare metal image.  It then prompts
' the end-user to enter a new computer name.
'
' VERSION : 1.1
' 1.0 (12/08/2008)- Intial script to check if the computer name begins with
'  "minint", which indicates the system was booted with CD or PXE.
' 1.1 (06/01/2009)- Added check if the computer name equals "minwinpc",
'  which indicates the system was booted with USB key
'==========================================================================

Dim sNewComputerName, oTaskSequence, sTSMachineName, bPromptName
Set oShell = WScript.CreateObject ("WScript.shell")
sExpression = "move-installationprogress.exe"
oShell.Run sExpression

Set oTaskSequence = CreateObject ("Microsoft.SMS.TSEnvironment")
' Get the name the computer is set to receive and truncate to first 6 letters
sTSMachineName = lcase(oTaskSequence("_SMSTSMachineName"))
If left(sTSMachineName,6) = "minint" Then
 bPromptName = True
ElseIf sTSMachineName = "minwinpc" Then
 bPromptName = True
Else
 bPromptName = False
End If
' Note: The wscript.echo commands are logged in SMSTS.log for troubleshooting.  They are not displayed to the end user.
If bPromptName = True Then
 wscript.echo "Detected that the computer name is scheduled to receive a random value.  Prompting user to input a standard name."
 sNewComputerName = InputBox ("Please enter a standard computer name to continue.", "Computer Name")
 oTaskSequence("OSDComputerName") = UCase(sNewComputerName)
 wscript.echo "Set Task Sequence variable OSDComputerName to: " & sNewComputerName
Else
 wscript.echo "Computer set to receive the standard name """ & sTSMachineName & """, continuing as is."
End If

Next, save the following source as “move-installationprogress.au3”. Download and Install AutoIt, then right-click and compile the au3 script to an x64 executable.

;Move-InstallationProgress.au3, John Puskar, https://windowsmasher.wordpress.com
WinMove("Installation Progress", "", 0, 0)

If WinExists("Computer Name") Then
	$pos = WinGetPos("Computer Name")
	WinMove("Computer Name", "", $pos[0], ($pos[1]+50))
EndIf
Exit

Create a new package named “Scripts – Ask for Name”, then copy both the vbscript and the executable to the source folder of this package.

Putting things together

  1. Open the Task Sequence that needs to ask for the computer name.
  2. Create a new TS Run Command-Line Action with the following parameters.
    Name: Ask for Computer Name
    Command: PromptForSystemName.vbs
    Package: Scripts - Ask for Name

Notes

It’s important to note that the vbscript will only ask for a name if the computer is ‘unknown’, meaning that it’s not in the SCCM database. If you’d like it to prompt no matter what, you’ll need to modify the script.

Thanks! As usual, please let me know if you find a problem or a more creative solution.

SCCM Task Sequence – Moving the Progress Window

Doesn’t that progress window get annoying when you’re trying to use vbscript to prompt for user information during OSD? I know! It keeps me up at night! We’re going to write an AutoIt script to move that sucka’.

Downloads

The Script

;Move-InstallationProgess.au3 by John Puskar https://windowsmasher.wordpress.com
WinMove("Installation Progress", "", 0, 0)
Exit

Do you like how I put a personal reference in there for 1 simple line of code? Oh the vanity!

Putting things Together

  1. Save the script source above as C:\temp\Move-InstallationProgress.au3
  2. Download and install AutoIt
  3. Right-click the script and choose “Compile (x64)”.
  4. Create a new sccm package named “Scripts – Move TS Progress Window”, and copy Move-InstallationProgress.exe to the package source directory.
  5. In your task sequence, create a new “Run Command-Line Action” with the following parameters.
    Name: Move Progress Window
    Command: move-installationprogress.exe
    Package: Scripts - Move TS Progress Window

Grats on a Good Day.

SCCM Task Sequence – Pushing Dell BIOS Settings

In a previous post, I wrote a guide on how to update your Dell to the latest BIOS version through a task sequence. This post covers how to make BIOS setting changes through a task sequence.

Downloads

Capturing the Settings

First, we need to capture the settings that you’d like to push.

  1. Download and install the Dell CCTK on a system that matches the Make\Model you’d like to push settings to.
  2. Create a new SCCM package to house the CCTK and BIOS Settings files.
  3. Copy the contents of the following folder to the package source directory.
    C:\Program Files (x86)\Dell\CCTK\X86_64
  4. Run the CCTK Gui and select “Create Package”.
  5. Next, select “This System’s File” then click “Next”.
  6. Click “Edit”, then scroll down to the “Storage” section and uncheck the check box for any settings which match “sata#”. I’ve learned that otherwise, on most systems it will apply “Enabled” instead of “Auto” (despite choosing Auto on this screen) which causes an error on boot.
  7. Next, click “Export Configuration” and save your settings file to the source folder of your CCTK package.

Pushing the Settings

OK, now we’re ready for some Task Sequence work. Woot.

  1. Create a new task sequence, or open a task sequence that you’d like to add the BIOS settings changes to.
  2. Create a group named “Push BIOS Settings” with the following parameters.
    WMI: If -Any- are true
    select * from win32_computersystem where Model like 'Latitude%'
    select * from win32_computersystem where Model like 'Precision%'
    select * from win32_computersystem where Model like 'Optiplex%'
  3. Create a new group named “Prep”.
  4. In the “Prep” group, create a “Run Command Line” action with the following parameters. This will enable WinPE support for CCTK.
    Name: Install WinPE HAPI Driver
    Command: HAPI\hapint.exe -i -k C-C-T-K -p "hapint.exe"
    Package: Dell CCTK 2.01 Portable x64
    
  5. Next, create TS Actions in the Prep group which set a BIOS password. See my previous post “SCCM Task Sequence – Updating Dell BIOS versions” for instructions on how to do this.
  6. Create a new group in the “Push BIOS Settings” group named “Settings by Model”.
  7. For each model, create a TS Action with the following paramters.
    Name: Set Default BIOS Settings (Model)
    Command: cctk.exe -i <CCTKSavedSettingsFile>.cctk --valsetuppwd=<BiosPassword>
    Package: Dell CCTK 2.01 Portable x64
    WMI: select * from Win32_computersystem where Model like "<MyMakeandModel>%"
    

Example TS Action for the Dell 990:

Name: Set Default BIOS Settings (Opti990)
Command: cctk.exe -i OSUChem_OptiPlex990.cctk --valsetuppwd=MyPassword
Package: Dell CCTK 2.01 Portable x64
WMI: select * from Win32_computersystem where Model like "Optiplex 990%"

Enabling the TPM

I’ve noticed that on some systems, the TPM cannot be enabled by a CCTK settings file. Also, on some systems, I can’t seem to activate the TPM during WinPE regardless of the commands used. If you want to enable and activate the TPM, perform the following steps.

  1. Create a TS action in the Push Bios Settings group, -after- the Settings by Model group, with the following parameters.
    Name: Enable TPM
    Command: cctk --tpm=on --valsetuppwd=<MyPassword>
    Package: Dell CCTK 2.01 Portable x64
    
  2. Next, copy the entire “Push Dell BIOS Settings” group, and paste a new copy into a place in the task sequence -after- the “Setup Windows and ConfigMgr” step.
  3. On this new copy of the group, change the name from “Push Dell BIOS Settings” to “Push Dell BIOS Settings (Inside OS)”.
  4. Remove the “Install WinPE HAPI Driver” TS Action from this new group.
  5. Add a new “Restart Computer” TS Action after “Enable TPM” targeted at the “Currently installed Operating System”.
  6. Add a new “Run Command Line” TS Action after “Restart Computer” with the following parameters.
    Name: Activate TPM
    Command: cctk.exe --tpmactivation=activate --valsetuppwd=<MyPassword>
    Package: Dell CCTK 2.01 Portable x64
  7. Rearrange the TS Actions to match the following list:
    <Settings By Model Group>
    Enable TPM
    Restart Computer
    Activate TPM
    Set Default BIOS Settings (Model1)
    Set Default BIOS Settings (Model2)
    Set Default BIOS Settings (Model3)
    etc…

Wrapping Up

CCTK is capable of changing the SATA storage controller operation mode. This is why if you’re imaging computers, it’s important to set the BIOS settings twice, in WinPE and inside the OS. Pushing settings in WinPE lets Windows setup select the proper SATA or RAID controller driver before the OS starts. Settings need pushed again after WinPE mode is finished because some settings (such as TPM=enable or TPM activation) only work inside the OS for some systems. Lousy workaround, I know, but it’s solid.

Here are screen shots of my current TS. The first image contains my WinPE actions. The second image contains my (Inside OS) actions.

Enjoy your BIOS setting adventures. This stuff is magic!

SCCM Task Sequence – Updating Dell BIOS versions

FUN ALERT! Here’s how to update the Dell BIOS in a task sequence.

Notes

BIOS update executables are x86-only. Since I primarily deploy x64 machines, the task sequence (or at least it’s actions) must run inside the guest OS, and not in WinPE. Also, the BIOS update will fail unless you know the BIOS password, or the password is not set. For this reason, I make multiple tries to set a BIOS password using different ‘current’ passwords before running the actual update action.

Downloads

The Process

Prep Work

  1. First, download all the BIOS versions you’ll need for your platform. Note that some systems require multiple intermediary upgrades.
  2. Next, make an SCCM package named “Dell BIOS Updates” containing all the executables for the platforms you’d like to update.  The following is an image of my package source folder.
  3. Next, download and install Dell’s CCTK utility.
  4. Make a SCCM package named “Dell CCTK 2.01 Portable x64”
  5. Copy the contents of the following folder to the package source directory.
    C:\Program Files (x86)\Dell\CCTK\X86_64
  6. Create a new task sequence, or open a task sequence that you’d like to add the BIOS updates to.
  7. Create a new folder in this task sequence named “Dell BIOS Updates”, and use the following WMI criteria to filter. Change “Optiplex” to the make of your PC if necessary (Latitude, Precision, PowerEdge, etc.).
    select * from win32_computersystem where Model like 'Optiplex%'

TS Actions – Passwords

  1. Create a new “Run Command Line” action named “Set a BIOS Password (attempt 1)” with the following settings. This will set a BIOS password if there is no password currently set.
    command line:  cctk.exe --setuppwd=<newBiosPassword>
    package: Dell CCTK 2.01 Portable x64
  2. Create a new “Run Command Line” action named “Set a BIOS Password (attempt 2)” with the following settings. This will set a BIOS password if there is a current password that is different.
    command line:  cctk.exe --setuppwd=<newBiosPassword> --valsetuppwd=<current\oldBiosPassword>
    package: Dell CCTK 2.01 Portable x64

TS Actions – Prepping for Multiple Update Steps

Skip this section if your particular model(s) don’t require intermediary update steps.

  1. Create a new group named “Update <modelname>”. Assign this group the following 2 WMI criteria. Change “A11” to the -latest- version of the BIOS available, so that the group is skipped if the system is already up to date. Also, change “Optiplex 990%” to the make\ model number of your PC. The % represents a wildcard allowing anything after, such as a space or null character.
    If -all- the conditions are true
    select * from WIN32_BIOS where SMBIOSBIOSVersion < "A11"
    select * from win32_computersystem where Model like 'Optiplex 990%'
  2. Create a new group for each update step named “Update to A##”. The following filtering criteria should be used, and the BIOS version  in the WMI criteria should be changed to the version you’re updating -to-.
    select * from WIN32_BIOS where SMBIOSBIOSVersion < "A10"

Examples

  • Group Name: Update to  A03
    WMI:  select * from WIN32_BIOS where SMBIOSBIOSVersion < “A03”
  • Group Name: Update to A07
    WMI: select * from WIN32_BIOS where SMBIOSBIOSVersion < “A07”

TS Actions – Updates to Newer systems

Onward to the actual update step! Woohoo! This action works for systems newer than the Optiplex 755.

For each update step required, use a “Run Command Line” action with the following criteria:

Name: Update <make\model> to <new BIOS version>
WMI:
If all are true:
select * from WIN32_BIOS where SMBIOSBIOSVersion < "<NewBiosVerion>"
select * from win32_computersystem where Model like '<make model>%'
Command: <Biosname>.exe /f /s /p=<currentBiosPassword>
Package: Dell BIOS Updates

Examples

Name: Update Opti990 to A11
WMI:
If all are true:
select * from WIN32_BIOS where SMBIOSBIOSVersion < "A11"
select * from win32_computersystem where Model like 'Optiplex 990%'
Command: <Biosname>.exe /f /s /p=MyPassword
Package: Dell BIOS Updates

TS Actions – Updates to Older systems

Systems older than the Optiplex 760 are a little tricky. I can’t get BIOS updates to work while the system has a BIOS password, so we first need to remove the password.

  1. Create a TS action with the following command to remove the BIOS password:
    cctk.exe --setuppwd= --valsetuppwd=<currentPassword>
  2. For each update step required, use a “Run Command Line” action with the following criteria:
    Name: Update <make\model> to <new BIOS version>
    WMI:
    If all are true:
    select * from WIN32_BIOS where SMBIOSBIOSVersion < "<NewBiosVerion>"
    select * from win32_computersystem where Model like '<make model>%'
    Command: <Biosname>.exe -NOREBOOT -NOPAUSE
    Package: Dell BIOS Updates
  3. Finally, set a new BIOS password with the following command in a TS action:
    cctk.exe --setuppwd=<new password>

Wrapping Up

Here’s an image of my task sequence; I hope it helps. Good luck!