More on Displaying the Bitlocker Wizard with Windows 8 and MDT 2012 U1

I a previous post, I detailed how to get the Bitlocker wizard page to appear when deploying Windows 8 pro.

I recently confirmed another case where the wizard does not show up. I became aware of this thread on Technet because another user linked to my previously mentioned blog post.

I was able to confirm after a bit of testing, that when using the Windows 8 Enterprise evaluation media, MDT does not show the Bitlocker wizard page. At this point, I am not sure of the reason for this.

Sending Email Notification from MDT 2012

In my recent rework of my Build and Capture sequences that are used for updating my reference images, I thought it would be nice to have an email notification when the process was done.  This post is to show how I did this.

I did this using Powershell’s Send-MailMessage cmdlet.  This provides a simple way to send a message via SMTP, and this MDT 2012u1 provides support for Powershell scripts it seemed a logical choice.  Since I wanted to send the message at the end of a Capture process, I needed to be able to send the message from the WindowsPE environment.

WinPE_PropertiesTo start, I needed to make sure I had Powershell included in my boot image.  Fortunately this is very easy to do with MDT.  In the deployment workbench right click the deploymentshare and choose “properties”  Click on the Windows PE tab and then on the “Fearures” tab within that.  Check the boxes for .NET frame work and Powershell as shown in the image.

After applying the change, right-click on the deployment share and choose “Update Deploymentshare” to rebuild the boot images.  Once that is completed, remember to update your PXE server to the new boot image, or replace your CDs if using media.

Now for the script.  The script is short, just three lines in fact :

$PSEmailServer = "smtp.example.com"
$Subject = "Task Sequence " + $TSEnv:TaskSequenceID " " has completed"
Send-MailMessage -to "Firstname Lastname <user@example.com>" -from "MDT <user@example.com>" -subject $Subject

This script will send a message with no body, and a subject that contains the name of the task sequence that called it.  You will need to change the smtp server information and the email addresses to suite your environment.  Save the script into the Scripts folder of the deploymentshare, and we are ready to add it to the task sequence. At the point in the task sequence where you wish the message to be sent, choose Add -> General -> Run PowerShell Script. In the “PowerShell Script:” field enter the following :

%scriptroot%\SendMessage.ps1

Change the name of the script to reflect the file name you gave it when you saved the script to the Scripts directory, and you are done!  To read more about the Send-MailMessage cmdlet, see the Technet documentation

Of Black Boxes and Complex Systems

I am currently re-reading John Lienhard’s The Engines of Our Ingenuity and I came across this nuget that I think we can apply to IT.

We must teach students that someone else’s subject matter is not a black box, that those boxes can and must be opened.  What one fool can do, another fool can also do, and any student is smart enough to open any other student’s black box.  That in turn brings us back to the matter of systems.  Once we realize that we cannot deal with part of a system in isolation, it becomes very clear that encasing knowledge in boxes is one of the most destructive things we do. [page 171]

I find that last sentence to be very powerful.  While Lienhard is talking about educating engineering students in this passage, I feel that this can be applied to IT organizations.  If we allow ourselves to become too compartmentalized, then we less efficient at trouble shooting and will provide less satisfactory service to our constituants.

Don’t misunderstand me here, I understand that there  is a place (and even need) for a separation of duties and a system of checks and balances.  The problem arises when we start guarding information and building silos around our disciplines. When we do this, we make both ourselves and those around us less effective.

Edit : For more from John Lienhard visit www.uh.edu/engines

Displaying the Bitlocker Wizard Pane with Windows 8 Pro and MDT 2012 Update 1

Using MDT 2012 Update 1 with ADK, I built and captured a Windows 8 Pro image to enable my institution to more easily do some testing with Windows 8.  After setting up a task sequence to deploy this reference image I noticed something unexpected.  When choosing the Win 8 task sequence, I was not presented with the Bitlocker wizard pane.  The wizard pane was showing up fine for my Win 7 task sequences.

I posed the question to the MDT-OSD email list, and Michael Niehaus to the rescue!  Turns out there is logic in the MDT scripts to determine if the edition of Windows is a “Premium SKU” to determine what features are available.  Since I was using Windows 8 Pro instead of Enterprise it was not getting marked as a premium SKU.

Of course, since Win 8 Pro does support Bitlocker (Win 7 Pro did not), this is a bug.  A user posted a bug report to Microsoft along with some work around code.  If you have a Connect account, you can see the bug report here.

The work around :

In ZTIUtility.vbs add the following two lines of code after line 3846 :


case "PROFESSIONAL", "PROFESSIONALE", "PROFESSIONALN"
 If Left(oEnvironment.Item("OSCurrentVersion"), 3) = 6.2 Then IsHighEndSKUEx = True

Packaging a LaunchAgent Script with The Luggage

Previously, I showed how to install Luggage, and how to package a drag and drop app.  In this installment, we will look at how to package up a LaunchAgent script.

We will work with a simple script to mount a couple of file shares when a user logs into their computer.  To set it up as a LaunchAgent, we need two pieces.  First, the script itself (connectshares.sh) needs to be copied to /Library/Scripts/Myorg and everyone should have read and execute permissions.  Second, a plist (com.myorg.connectshares.plist) file which controls the execution of the script needs to be copied to /Library/LaunchAgents and and everyone should have read permissions.

So lets look at our Makefile.  We start with the basics that we need for every Makefile :


include /usr/local/share/luggage/luggage.make
TITLE=setup_connectshares
VERSION=1.0
REVERSE_DOMAIN=com.myorg
PAYLOAD=\

Now we need to get to the important part. We need to specify the payload stanzas to specify where the source files should be put when installation occurs. First, the plist file :

pack-Library-LaunchAgents-com.myorg.connectshares.plist

This one is easy, because Luggage has a built in rule for the destination /Library/LaunchAgents

The next part of the Makefile is going to be a little more complex as we want to copy our script file to a custom location.

1_Myorg:	l_Library
	@sudo mkdir -p ${WORK_D}/Library/Scripts/Myorg
	@sudo chown root:wheel ${WORK_D}/Library/Scripts/Myorg
	@sudo chmod 755 ${WORK_D}/Library/Scripts/Myorg
pack-connectshares.sh:	1_Myorg
	@sudo ${CP} ./connectshares.sh ${WORK_D}/Library/Scripts/Myorg/connectshares.sh
	@sudo chown root:wheel ${WORK_D}/Library/Scripts/Myorg/connectshares.sh
	@sudo chmod 755 ${WORK_D}/Library/Scripts/Myorg/connectshares.sh

The first part, 1_Myorg tells Luggage how to build the directory structure and what the permissions should be. We then tell Luggage that the file connectshares.sh is to be copied to that directory with proper permissions. The complete Makefile will look like this :

include /usr/local/share/luggage/luggage.make

TITLE=setup_connectshares
VERSION=1.0
REVERSE_DOMAIN=com.myorg
PAYLOAD=\
	pack-Library-LaunchAgents-com.myorg.connectshares.plist\
	pack-connectshares.sh

1_Myorg:	l_Library
	@sudo mkdir -p ${WORK_D}/Library/Scripts/Myorg
	@sudo chown root:wheel ${WORK_D}/Library/Scripts/Myorg
	@sudo chmod 755 ${WORK_D}/Library/Scripts/Myorg
pack-connectshares.sh:	1_Myorg
	@sudo ${CP} ./connectshares.sh ${WORK_D}/Library/Scripts/Myorg/connectshares.sh
	@sudo chown root:wheel ${WORK_D}/Library/Scripts/Myorg/connectshares.sh
	@sudo chmod 755 ${WORK_D}/Library/Scripts/Myorg/connectshares.sh

Place this Makefile in a directory with connectshares.sh and com.myorg.connectshares.plist and you can build your package.

Installing Novell ZCM Adaptive Agent in MDT 2012

The Novell ZCM Adaptive Agent is an example of an application that needs to be installed at deployment time and should not be included in the reference image.  In general, MDT Lite Touch handles installing applications at deployment time quite nicely.

The Adaptive agent, however is an example of an install process that does not play nice with MDT.  It really is a quite horrible installer.  First, lets take a look at the installer and what it does.

We know that to successfully install applications in MDT we need an unattended install that does not force a reboot (both of these are critical to success).  Our first step should be to look for command line switches to give us our silent install.  If we run PreAgentPkg_AgentCompleteDotNet.exe /? from a command line, we get the following information :

OK, it looks like we should be able to run “PreAgentPkg_AgentCompleteDotNet -q -x” to achieve our unattended install.  Lets go ahead and run this from a command prompt on our test system to see what happens.

The first thing we notice is that we get returned to the command prompt immediately.  Looking at the task manager, we can see that the exe is running.

After a time, though, the exe exits and we see some activity in the system tray :

Yes, thats right…  It kicks off a series of 52 individual packages!  Remember that it has already returned as if the process finished successfully.  This poses a problem in MDT.  MDT will start the next installation thinking that this one is finished.  Since we cannot have multiple msi isntallations running at the same time, one of them will fail.

What we need to do, then, is find a way to wrap this pesky installer in a script that can wait until it is finished.  Back to our test installation, we can see the last package to run is setup.exe :

Then when done, it just waits for a reboot

Armed with this knowledge, we can throw together a vbscript to launch the install with the proper switches and then wait for completion.

Set objShell = CreateObject("Shell.Application")
objShell.ShellExecute "PreAgentPkg_AgentCompleteDotNet.exe", "-q " & "-x"

strComputer = "." ' local computer
strProcess = "Setup.exe"

Do Until isProcessRunning(strComputer,strProcess)
  WScript.Sleep(5000)
Loop

Do While isProcessRunning(strComputer,strProcess)
  WScript.Sleep(5000)
Loop

WScript.Quit 0

' Function to check if a process is running
FUNCTION isProcessRunning(BYVAL strComputer,BYVAL strProcessName)

DIM objWMIService, strWMIQuery
strWMIQuery = "Select * from Win32_Process where name like '" & strProcessName & "'"

SET objWMIService = GETOBJECT("winmgmts:" _
 & "{impersonationLevel=impersonate}!\\" _
 & strComputer & "\root\cimv2")

IF objWMIService.ExecQuery(strWMIQuery).Count > 0 THEN
 isProcessRunning = TRUE
ELSE
 isProcessRunning = FALSE
END IF

END FUNCTION

The script makes use of a function to determine if a process is running.  In this case we want to see if setup.exe is running.  At the start, setup.exe is not running yet, and we use a loop to keep checking (and then sleeping) until it is running.  We then have a second loop that keeps checking until setup.exe is no longer running.  At this point we know the install is finished, and can exit the script.

This script is a bit crude, and lacks error checking but it does work.  To use it, place the script in the same directory as the Preagent installer.  When importing it as an application into MDT, set the Quiet install command to be : cscript install.vbs

HTA Script for Mapping Network Drives

I had a need for a user friendly Windows script to map network drives using credentials supplied by the user.  The script I endend up with is an HTA script that allows the user to enter their credentials and map a predefined set of network drives.  There is also a button to disconnect the mapped drives.

The script is here : https://github.com/vmiller/ConnectDrives

<!-- HTA script to allow machines that are not joined to a domain to access
     Windows file shares with domain credentials. It will atomatically prepend the
     domain to the username and then map several drives. If a drive is already
     mapped, it is disconnected and then mapped for the current user.
     
     Version 1.0.2
     Written by Vaughn Miller 7/20/2012
     
     Currently setup to map the following drives :
     M: = \\gonzo.ad.messiah.edu\dept
     O: = \\gonzo.ad.messiah.edu\users
     W: = \\mcweb\messiahweb
     ---------------------------------------------------------------------------------->


<HTML>
<HEAD>
<TITLE>Connect Network Drives</title>
<HTA:APPLICATION
ICON="EIPos.ico"
     ApplicationName="MapDrives.HTA"
     SingleInstance="Yes"
     WindowsState="Normal"
     Scroll="No"
     Navigable="Yes"
     MaximizeButton="No"
     SysMenu="Yes"
     Caption="Yes"
></HEAD>

<SCRIPT LANGUAGE="VBScript">

' *** Define Drive Mappings ***
dim arrDrives(2,2)
intMaxdrives = 2

arrDrives(0,0) = "M:"
arrDrives(0,1) = "\\gonzo.ad.messiah.edu\dept"
arrDrives(0,2) = "Dept"

arrDrives(1,0) = "O:"
arrDrives(1,1) = "\\gonzo.ad.messiah.edu\users"
arrDrives(1,2) = "Users"

arrDrives(2,0) = "W:"
arrDrives(2,1) = "\\mcweb\messiahweb"
arrDrives(2,2) = "messiahweb"
' *** End Drive Map Definitions ***

strDOMAIN = "messiah\" 'Domain to prepend to the username


Sub Window_Onload
  '# Size Window
  sHorizontal = 440
  sVertical = 175
  Window.resizeTo sHorizontal, sVertical
  '# Get Monitor Details
  Set objWMIService = GetObject _
    ("winmgmts:root\cimv2")
  intHorizontal = sHorizontal *2
  intVertical = sVertical *2
  Set colItems = objWMIService.ExecQuery( _
    "Select ScreenWidth, ScreenHeight from" _
    & " Win32_DesktopMonitor", , 48)
  For Each objItem In colItems
    sWidth= objItem.ScreenWidth
    sHeight = objItem.ScreenHeight
    If sWidth > sHorizontal _
      then intHorizontal = sWidth
    If sHeight > sVertical _
      then intVertical = sHeight
  Next
  Set objWMIService = Nothing
  '# Center window on the screen
  intLeft = (intHorizontal - sHorizontal) /2
  intTop = (intVertical - sVertical) /2
  Window.moveTo intLeft, intTop
  '# default window content
  window.location.href="#Top"
End Sub


Sub RunScript
   on Error Resume Next

   minUSRnamelength = 2
   minPASSwrdlength = 3

   strUsr = UsrnameArea.Value
   strPas = PasswordArea.Value

   Set objNetwork = CreateObject("WScript.Network")
   Set oShell = CreateObject("Shell.Application")

   If Len(strUsr) >= minUSRnamelength then
      strUsr = strDOMAIN & UCase(strUsr) '<--- adds the domain before the username

      if Len(strPas) >= minPASSwrdlength Then
         Call ClearDrives ' Delete existing mappings if they exist
         
         '***** Begin Drive mapping *****
         For n = 0 To intMaxDrives 'Loop through our array of drives
            Err.Clear
            objNetwork.MapNetworkDrive arrDrives(n,0), arrDrives(n,1), False, strUsr, strPas
            If Err.Number = 0 Then
               oShell.NameSpace(arrDrives(n,0)).Self.Name = arrDrives(n,2)
            End If
         Next
         '***** End Drive Mapping *****
          
         ELSE
            Msgbox chr(34) & strPas & """ is an incorrect password !"
            Exit Sub
         End If
   ELSE
      Msgbox chr(34) & strUsr & """ is an incorrect Username !"
      Exit Sub
   End If
    ' Clean up the objects before exiting
   Set oShell = Nothing
   Set objNetwork = Nothing
   Self.Close()
End Sub


Sub ClearDrives ' Sub Routine to remove the drives if they are already mapped
  On Error Resume Next
  Set objNetwork = CreateObject("WScript.Network")

  '***** Begin section to delete drive mappings ***
  Set AllDrives = objNetwork.EnumNetworkDrives
  For n = 0 To intMaxDrives 'Loop through our array of drives
     For i = 0 To AllDrives.Count - 1 Step 2
        If AllDrives.Item(i) = arrDrives(n,0) Then AlreadyConnected = True
     Next
     If AlreadyConnected = True then
        objNetwork.RemoveNetworkDrive arrDrives(n,0), True, True
     End If
  Next
  '***** End section to delete drive mappings
End Sub


Sub DisconnectDrives ' Calls ClearDrives subroutine and then closes the window
Call ClearDrives
    Set oShell = Nothing
    Set objNetwork = Nothing
Self.close()
End Sub


Sub CancelScript
   Set oShell = Nothing
   Set objNetwork = Nothing
   Self.Close()
End Sub

</SCRIPT>


<BODY STYLE="font:14 pt arial; color:white; filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=1, StartColorStr='#000000', EndColorStr='#0000FF')">
<a name="Top"></a><CENTER>
  <table border="0" cellpadding="0" cellspacing="0"><font size="2" color="black" face="Arial">
    <tr>
      <td height="30">
        <p align="right">Your Username</p>
      </td>
      <td height="30">&nbsp;&nbsp; <input type="text" name="UsrnameArea" size="30"></td></tr>
    <tr>
      <td height="30">
        <p align="right">Password</p>
      </td>
      <td height="30">&nbsp;&nbsp; <input type="password" name="PasswordArea" size="30"></td></tr>
  </table><BR>
<HR color="#0000FF">
 <Input id=runbutton class="button" type="button" value=" Map Drives " name="run_button" onClick="RunScript">
    &nbsp;
 <Input id=runbutton class="button" type="button" value=" Disconnect Drives " name="dis_button" onClick="DisconnectDrives">
    &nbsp;
 <Input id=runbutton class="button" type="button" value="Cancel" name="cancel_button" onClick="CancelScript">
</CENTER>
</BODY>

</HTML>

The drive definitions are coded in an array so that the mapping and disconnecting subroutines can use a loop. To modify this for your own use, you need to modify strDomain (line 50) and the drive definitions (lines 33-48).

I think a nice revision of this script would be to design it to read a configuration file for the domain info and drive definitions. This was one motivation for implementing the array/loop structure.

Packaging a Drag and Drop Application

In a previous post, I showed how to get started by getting Luggage setup.  In this post we will create a simple package based on a .app bundle.

Drag and drop applications are really user friendly to install, the user simply needs to drag them to the Applications folder.  They are not as nice, however, for the system administrator who wishes to deploy the application via some automated tool.  In some cases it is desirable to repackage these application into an installer package.  Fortunately this is easy to do with the Luggage.

For this demonstration, I will use my favorite text editor TextWrangler.app.  When we download TextWrangler it is downloaded as a disk image.  Mount the image file by double clicking on it, and we see that we have an app bundle and a link to the Applications folder.  The application bundles are actually a special directory, so our first step is to make a compressed tar of the app.

Open Terminal and execute the following commands (be sure to scroll to the right to see all of the second command) :

cd /Volumes/TextWrangler\ 4.0.1/

/usr/bin/tar cvjf ~/Downloads/TextWrangler.app.tar.bz2 TextWrangler.app

This will create a tar file in the Downloads directory.  Now, create a directory for our files that Luggage will need.  Move the tar file into this directory and  then create a text file with the file name of Makefile (make sure an extension does not get added to the filename when it is saved).  The contents of Makefile should be as follows :

include /usr/local/share/luggage/luggage.make
TITLE=install_TextWrangler
REVERSE_DOMAIN=com.example.corp
PAYLOAD=unbz2-applications-TextWranger.app

The “include” line tells Luggage where to find the luggage.make file and includes the built in rules that we will use.

The TITLE line is where we specify the title that we want our resulting installer to have.  You can change this to suite your own taste.

The REVERSE_DOMAIN line should be changed to the domain that is applicable to your organization.

The PAYLOAD line is the one that tells The Luggage what to do with the files we provide.  In this example we are using a built in rule that will unpack a bz2 tar file into the /Applications folder.

In Terminal, change into the directory where we have our tar file and our Makefile and run the following command :

sudo make pkg

The result of this command will be the creation of a .pkg file that will install TextWrangler.app into /Applications If we want that installer wrapped in a disk image we can run the following command instead :

sudo make dmg

This is a minimal example, and a fairly painless way to get started with The Luggage and Makefiles.  All make files must have these four lines at minimum.  Next, go here to learn how to package a Launch Agent.

Some new Features in MDT 2012

I’ve been working with MDT 2012 in my test environment, and here are three new features that I really like.

Dirty Environment Cleanup

Some times a deployment fails from some reason, and you just want to restart the process of a bare metal install.  In MDT 2010, the task sequence would read the logs written to the hard drive and realize that a deployment was still in progress.  At this point, MDT 2010 will halt the process with an error.  Pressing F8 and using diskpart to wipe the disk is my usual work around, but it is a bit clumsy for less experienced technicians.

In MDT 2012, if we boot to Windows PE and a previous deployment is detected we get the following dialog :

Nice!  Instead of assuming that we booted into Windows PE by accident, MDT 2012 allows for us to start over from scratch.  If we did do it by accident, we can simply click No and reboot to the hard drive.

Deploy 32bit and 64bit Images from the Same Boot Media

Apparently this has been available in SCCM, but not in Lite Touch deployments prior to now.  In MDT 2012 both 64bit and 32bit task sequences will work when booting to the 32bit Windows PE media.  This means only needing to maintain one media, and that the technicians deploying machines can always boot from the same media.

Local Administrators Wizard Page

In my organization, we usually make the primary user of a computer a local administrator.  This is a step that was being done manually after the MDT deployment completed.  This is something I was hoping to add a custom wizard page for (at some point when I could find some time to learn how to do that).  It was a pleasant suprise when I discovered that MDT 2012 included this.  I happened to be poking around in the scripts folder of my test deployment share and found the DeployWiz_AdminAccounts.xml file.  Adding SkipAdminAccounts=NO  to CustomSettings.ini results in an extra wizard page being displayed :

 Adding accounts here in the form of DOMAIN\user will result in those accounts being added to the local administrators group of the computer.  I still want to learn to add my own wizard page, but this is one that I won’t have to…

 

These are some new features that will be really useful to me in my environment.  If you have found new features you like, feel free to leave a comment.

Getting Started with The Luggage

When administering and or deploying Mac OS systems, it becomes very useful to be able to create packages.  This can be to repackage an app to automate deployment, or package up scripts or configuration files.  One such tool to do this is The Luggage.  Written by Joe Block, it is a command line tool that makes use of Apple’s Packagemaker as well as Make.  More information can be found here.

This post is intended to walk you through getting up and running with luggage from ground zero.  A future post will look at using The Luggage.  This tutorial is assuming you are running Mac OS 10.7.

Requirements : 

  • PackageMaker.app
  • XCode command line tools
  • The Luggage

Lets start be going to developer.apple.com/downloads to download the tools we need from Apple.  You will need to sign in with an Apple ID, but do not need to have a paid developer account to get the tools we need.  In the search box, enter “for Xcode” to narrow our downloads.  First, download the “Command Line Tools for Xcode”   Second, download the “Auxiliary Tools for Xcode”

Now we can open the Downloads folder and install the Apple components that we need.  First open the Command Line Tools disk image and we see that it is a single meta package installer.  Go ahead and install it accepting the defaults.

Next, we need to install PackageMaker.app from the Auxiliary Tools.  Open the disk image for for the Auxiliary Tools from the Downloads folder.  We will see multiple items in this image, but PackageMaker is the only one we are interested in for this tutorial.  PackageMaker is a drag and drop app, so lets just drag it into /Applications/Utilities

Thats all we need from Apple, go ahead and eject the disk images and we’ll move on to installing The Luggage.

We will download The Luggage from https://github.com/unixorn/luggage  We can download it using the ZIP  button, the result will be a folder with several files in your Downloads folder.  Go ahead and copy this into your Home folder (really, you can put this anywhere you wish).

Now, we just need to run a command to configure luggage for use.  Change into the luggage directory and execute the following command :

Now Luggage is ready for use and we can start packaging, go here for the next installment of the series which demonstrates how to package a drag and drop style application.