Michael Niehaus' Windows and Office deployment ramblings
One of the questions I received after presenting a session at TechEd Europe was about encoding any clear-text passwords that you might place into the Bootstrap.ini file, so that if someone looked at the file they wouldn’t be able to tell what value it represented. I suggested that it would be fairly simple to write a script that would take an encoded value, decode it, and save the decoded value to the “real” task sequence variable. That evening, a script was written and a blog post started.
As the MDT scripts already provide everything needed to do this, the resulting scripts are pretty simple, with more comments than lines of code. For the already-typed up scripts, see the attachment below.
First, let’s talk about the approach. We need to have encoded values that we can decode. It’s easy enough to save those encoded values in task sequence variables, set via CustomSettings.ini or Bootstrap.ini. So imagine something like this:
[Settings] Priority=Decode, Default Properties=EncodedUserPassword [Decode] EncodedUserPassword=SGVsbG8=
[Settings] Priority=Decode, Default Properties=EncodedUserPassword
[Decode] EncodedUserPassword=SGVsbG8=
There’s not much to that: I have declared a new variable called EncodedUserPassword, and set the value of that variable to “SGVsbG8=”, which is the base64-encoded equivalent of the string “Hello”.
The next step then is to decode that encoded value. The simplest way of doing this is to use a user exit script. The logic for that needs to do the following:
The basic logic:
For each sVar in Array("USERID", "USERPASSWORD", "USERDOMAIN", "DOMAINADMIN", "DOMAINADMINPASSWORD", "DOMAINADMINDOMAIN", _ "ADMINPASSWORD", "BDEPIN", "TPMOWNERPASSWORD", "ADDSUSERNAME", "ADDSPASSWORD", _ "SAFEMODEADMINPASSWORD", "USERNAME", "USERPASSWORD", "PRODUCTKEY") ' If the encoded value exists, decode it and save it to the proper task sequence variable If oEnvironment.Item("Encoded" & sVar) <> "" then oLogging.CreateEntry "Decoding variable Encoded" & sVar & " for assignment to " & sVar, LogTypeInfo oEnvironment.Item(sVar) = oStrings.Base64Decode(oEnvironment.Item("Encoded" & sVar)) End if Next
For each sVar in Array("USERID", "USERPASSWORD", "USERDOMAIN", "DOMAINADMIN", "DOMAINADMINPASSWORD", "DOMAINADMINDOMAIN", _ "ADMINPASSWORD", "BDEPIN", "TPMOWNERPASSWORD", "ADDSUSERNAME", "ADDSPASSWORD", _ "SAFEMODEADMINPASSWORD", "USERNAME", "USERPASSWORD", "PRODUCTKEY")
' If the encoded value exists, decode it and save it to the proper task sequence variable
If oEnvironment.Item("Encoded" & sVar) <> "" then oLogging.CreateEntry "Decoding variable Encoded" & sVar & " for assignment to " & sVar, LogTypeInfo oEnvironment.Item(sVar) = oStrings.Base64Decode(oEnvironment.Item("Encoded" & sVar)) End if Next
(Don’t try to copy and paste this, as it’s not complete and it doesn’t wrap nicely in the blog posting. Use the already typed-up version attached below.) The only real “magic” in this code is to use the “oStrings.Base64Decode” function, which already exists in MDT’s ZTIUtility.vbs script (as MDT actually stores these variables base64-encoded – if you think about that, you realize that we’re taking an encoded value, decoding it, then telling MDT to save it back again encoded. Kind of silly, yes, but it does mean you won’t ever see the value in clear text.)
To use this user exit script in CustomSettings.ini, you need to copy the “DecodeExit.vbs” script into your deployment share’s “Scripts” folder, then add a reference to it in CustomSettings.ini:
[Settings] Priority=Decode, Default Properties=EncodedUserPassword [Decode] EncodedUserPassword=SGVsbG8= UserExit=DecodeExit.vbs
[Decode] EncodedUserPassword=SGVsbG8= UserExit=DecodeExit.vbs
OK, great, you’re all set to go now right? Well, not really. Look again at my example: It’s setting the “UserPassword” variable. That’s the password used to make a connection to the deployment share. It really wouldn’t do any good to set that one in CustomSettings.ini. Instead, it needs to be set in Bootstrap.ini. Simple enough, right? Just copy and paste the same lines into Bootstrap.ini. Well, almost, as there is one additional challenge: You have to get MDT to include the user exit script in the boot image, so that it’s available when the boot image is generated. There are two ways that you could do that:
<Copy source="%DEPLOYROOT%\Scripts\DecodeExit.vbs" dest="Deploy\Scripts\DecodeExit.vbs" />
Either approach will work. After you make either change, you’ll need to update the deployment share to generate new boot images (then import those to WDS, generate new boot CDs, etc.). The advantage of the first is that it will survive MDT upgrades. The advantage of the second is that it’s easier to do and will detect script changes (e.g. if you modify the script to include more variables and then want to update the boot image again).
So how do you come up with the encoded value in the first place? Well, if you search the internet, you’ll find lots of “base64 encoder” web pages, so you can always do that. Or you can use the second script that I’ve attached, Encode.wsf, which leverages the matching “oStrings.Base64Encode” function from ZTIUtility.vbs. You would run the script like so:
cscript.exe Encode.wsf /Value:”Hello"
It will display the resulting encoded value on the console, so you can then copy-and-paste into CustomSettings.ini or Bootstrap.ini.
As usual, even the simplest setup requires a lot of explanation…
Great post!
Good job, but still - it's security through obscurity... We could get real encryption with certs implemented...
The problem with certs is that they secure the data, but who secures the cert? Regardless, you can use the same type of user exit to implement any type of encoding or encrypting. I just used a simple base64 encoding (which we always refer to as "obfuscation", not "encryption" or security of any kind) because it's simple and doesn't trick you into thinking this is somehow secure.
Thanks Michael, you finally came up with the solution. It will be even better if you include it as part of MDT 2012 Update 1!
To me, even though it is not 100% secured solution but it is way better than clear-text value for sure!
Nice job..it would be better if it is part of the GUI.
Powershelly! Some of this is copy pasted from others including Michael :) :
#function borrowed from gallery.technet.microsoft.com/.../Powershell-script-to-33887eb2
function ConvertFrom-Base64($stringfrom) {
$bytesfrom = [System.Convert]::FromBase64String($stringfrom);
$decodedfrom = [System.Text.Encoding]::UTF8.GetString($bytesfrom);
return $decodedfrom
}
# Grab the variables from the Task Sequence
$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment
$tsenv.GetVariables() | % { Set-Variable -Name "$_" -Value "$($tsenv.Value($_))" }
#Set Credentials to Task Sequence variable values
$ClearID = ConvertFrom-Base64 -stringfrom "$UserID"
$ClearDomain = ConvertFrom-Base64 -stringfrom "$UserDomain"
$ClearPW = ConvertFrom-Base64 -stringfrom "$UserPassword"
$User = "$ClearDomain\$ClearID"
$Password = ConvertTo-SecureString -String "$ClearPW" -AsPlainText -Force
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User,$Password
Hi Michael, great job.
Tested with MDT 2012 U1 and works perfectly.
Thanks for the above. I have followed the process and updated my customsettings.ini file - but please can someone advise what expression i need to use to call the encoded password?
I have used the DomainAdminPassword=$EncodedUserPassword to join to the domain but it fails..... i'm not sure if i'm using the correct expression statement - pls can someone help