• Michael Niehaus' Windows and Office deployment ramblings

    RIS-style naming with MDT 2010: Use a web service

    • 7 Comments

    I’ve been toying around with using a web service that could be used to implement RIS-style computer naming.  Unfortunately, I haven’t had time to work on it much in the last year or so, so I’ll post the code as-is and tell you up front that it’s not really complete – you might need to make some changes to it to meet your specific needs.  So here’s the code (written using Visual Studio 2008 and .NET 3.5), which shows how easy it is to do LDAP queries and object creation using .NET:

    using System;
    
    using System.Collections;
    
    using System.Collections.Generic;
    
    using System.ComponentModel;
    
    using System.Diagnostics;
    
    using System.DirectoryServices;
    
    using System.Data;
    
    using System.Linq;
    
    using System.Web;
    
    using System.Web.Services;
    
    using System.Web.Services.Protocols;
    
    using System.Xml.Linq;
    
    
    
    namespace DemoWebService
    
    {
    
        [WebService(Namespace = "http://tempuri.org/")]
    
        [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    
        [ToolboxItem(false)]
    
        public class NameService : System.Web.Services.WebService
    
        {
    
            [WebMethod]
    
            public String GenerateName(String dnsDomain, String prefix, String uuid, String machineObjectOU)
    
            {
    
                // Build the search
    
                DirectoryEntry entry =
    
                    new DirectoryEntry("LDAP://" + dnsDomain);
    
                DirectorySearcher search = new DirectorySearcher(entry,
    
                   "(name=" + prefix + "*)");
    
    
    
                // Execute the search and build a list of the matching names and their UUIDs
    
                Dictionary<String, Guid> existingNames = new Dictionary<String, Guid>();
    
                foreach (SearchResult result in search.FindAll())
    
                {
    
                    String name = result.Properties["name"][0].ToString().ToUpper();
    
                    Guid netbootGuid = new Guid();
    
                    if (result.Properties["netbootGuid"].Count > 0)
    
                        netbootGuid = new Guid((byte[])result.Properties["netbootGuid"][0]);
    
                    Trace.WriteLine("Found computer " + name + " with GUID " + netbootGuid.ToString());
    
                    existingNames.Add(name, netbootGuid);
    
                }
    
    
    
                // See if we can find an existing match.  If so, return it.
    
                Guid existingUuid = new Guid(uuid);
    
                if (existingNames.ContainsValue(existingUuid))
    
                {
    
                    foreach (String name in existingNames.Keys)
    
                        if (existingNames[name] == existingUuid)
    
                        {
    
                            // TODO: Maybe we want to move the computer object to the specified OU
    
                            return name;
    
                        }
    
                }
    
    
    
                // Find the first available name in sequence
    
                String nextName = null;
    
                for (Int32 i = 1; i <= 999; i++)
    
                {
    
                    String testName = prefix + i.ToString("000");
    
                    if (!existingNames.ContainsKey(testName))
    
                    {
    
                        nextName = testName;
    
                        break;
    
                    }
    
                }
    
                if (nextName == null)
    
                    return null;  // All names were taken
    
    
    
                // Add the computer to AD
    
                try
    
                {
    
                    DirectoryEntry dirEntry = new DirectoryEntry("LDAP://" + machineObjectOU);
    
                    DirectoryEntry newUser = dirEntry.Children.Add("CN=" + nextName, "computer");
    
                    newUser.Properties["samAccountName"].Value = nextName + "$";
    
                    newUser.Properties["netbootGUID"].Value = existingUuid.ToByteArray();
    
                    newUser.Properties["description"].Value = "Added by MDT";
    
                    newUser.CommitChanges();
    
                    newUser.Close();
    
                }
    
                catch (Exception e)
    
                {
    
                    Trace.WriteLine("Unable to add computer: " + e.ToString());
    
                }
    
    
    
                // Return the name
    
                return nextName;
    
            }
    
    
    
        }
    
    }
    

    To use this, you would add an entry to CustomSettings.ini to call the web service.  That would look something like this:

    [Settings]
    Priority=Default, GetName
    Properties=DnsDomain, Prefix

    [Default]
    Prefix=MDTTEST
    DnsDomain=mydomain.com
    MachineObjectOU=OU=Workstations,DC=mydomain,DC=com

    [GetName]
    WebService=http://myserver/NameService.asmx/GenerateName
    Parameters=DnsDomain, Prefix, UUID, MachineObjectOU
    OSDComputerName=string

    The web service would be passed the DNS domain name (mydomain.com), computer prefix (MDTTEST), the current machine’s SMBIOS UUID, and the OU to which new computers should be added.  It will return a name starting with the specified prefix and ending with the next available three-digit number.  So the first machine would be MDTTEST001, the second MDTTEST002, etc.  These computer names are added to Active Directory with the “netbootGUID” attribute set, so that if the machine is ever rebuilt it will use the same computer name again.  (The code purposely doesn’t try to find any computer object with that SMBIOS UUID.  Instead, it only looks for computers with the right prefix that have a matching UUID.  This might not be the behavior you want, but it was the behavior I wanted.)

    The output from ZTIGather.wsf when processing this INI file would look something like this:

    Added new custom property DNSDOMAIN
    Added new custom property PREFIX
    Using from [Settings]: Rule Priority = DEFAULT, GETNAME
    ------ Processing the [DEFAULT] section ------
    Property MACHINEOBJECTOU is now = OU=Workstations,DC=mydomain,DC=com
    Using from [DEFAULT]: MACHINEOBJECTOU = OU=Workstations,DC=mydomain,DC=com
    Property DNSDOMAIN is now = mydomain.com
    Using from [DEFAULT]: DNSDOMAIN = mydomain.com
    Property PREFIX is now = MDTTEST
    Using from [DEFAULT]: PREFIX = MDTTEST
    ------ Processing the [GETNAME] section ------
    Determining the INI file to use.
    Using COMMAND LINE ARG: Ini file = CS.ini
    Finished determining the INI file to use.
    Using COMMAND LINE ARG: Ini file = CS.ini
    CHECKING the [GETNAME] section
    About to execute web service call using method POST to http://server/NameService.asmx/GenerateName: DnsDomain=mydomain.com&Prefix=MDTTEST&UUID=814100CD-CE48-CB11-A536-B7561D1E4450&MachineObjectOU=OU=Workstations,DC=mydomain,DC=com
    Response from web service: 200 OK
    Successfully executed the web service.
    Property OSDCOMPUTERNAME is now = MDTTEST001
    Obtained OSDCOMPUTERNAME value from web service:  string = MDTTEST001

    This isn’t quite as flexible as the RIS naming, where you could use other variables in the computer name, but there’s no reason you couldn’t add more logic to cover those cases too.  There’s also no guarantee that the web service will add the computer account to the same DC that the computer ends up using, which could cause some naming conflicts if the new computer object doesn’t replicate before the computer tries to join the domain.  Use at your own risk :-)

  • Michael Niehaus' Windows and Office deployment ramblings

    MDT 2010 New Feature #15: Finish actions

    • 4 Comments

    When you perform a Lite Touch deployment, the task sequence runs through to completion, and then you will see a summary wizard that shows you any errors or warnings that may have occurred during the process.  That wizard could be skipped by setting “SkipFinalSummary=YES” in CustomSettings.ini.  (One behavior worth pointing out:  With MDT 2010 Beta 2, if the task sequence fails, the SkipFinalSummary setting will be ignored and the wizard will be shown anyway.)

    A common request we received was to provide a mechanism for specifying what should be done after the wizard is completed (or if it is skipped).  With MDT 2010 Beta 2, we have provided that through a “FinishAction” variable that you can set in CustomSettings.ini.  Valid values include:

    • SHUTDOWN.  When the process is complete, turn off the computer.
    • RESTART or REBOOT.  When the process is complete, reboot the computer.
    • LOGOFF.  When the process is complete, log off.

    If you don’t specify any value, the default is to just exit like was done in MDT 2008.  The shutdown, restart, or logoff activities happen after the Lite Touch scripts have cleaned up the machine (copying logs to the network, removing MININT, removing autologon settings, etc.).  In the case of restart or logoff, the machine will end up at the logon screen, ready for a user to log on.

  • Michael Niehaus' Windows and Office deployment ramblings

    Troubleshooting MDT 2012 Monitoring

    • 6 Comments

    I mentioned a while back that I wanted to do a blog post talking about how to troubleshoot the new MDT 2012 monitoring feature for Lite Touch deployments, but first I had to actually describe it.  If you haven’t reviewed that post, you might want to check it out first at http://blogs.technet.com/b/mniehaus/archive/2012/03/09/mdt-2012-new-feature-monitoring.aspx.

    So now let’s talk about troubleshooting.  First, let’s look at the server side.  You have to enable monitoring on a computer that has MDT 2012 installed.  When you use Deployment Workbench on that computer and check the box to enable monitoring, Workbench will first check to see if the specified monitoring host name is local:

    image

    It doesn’t really matter if you specify an IP address, a short host name, or a fully-qualified host name, as long as the clients can resolve whatever you specify.  If you specify a name that Workbench doesn’t think is local (because Workbench itself can’t resolve the name back to an IP address assigned to the current machine), it won’t try to install the monitoring component; instead, it will try to contact that server to see if monitoring is running on that computer.  If it is, great; if it isn’t, you’ll see an error message:

    image

    If you look closely at the error “tip” at the end of the “Monitoring host” line, you’ll see a message like “Unable to connect to the specified server and port”.  If you think you specified the local computer name and got that error, then Workbench couldn’t figure out that it was the local computer name (something that’s harder to do than you might think).  If you are specifying a different server and see this error, then it’s having problems communicating with that other server.

    Tip #1:  Make sure the name you specify in Workbench can be resolved to the IP address of the current machine.

    What does the checkbox do?

    You’ve checked the checkbox and can’t see that anything happened.  So what was actually done?  Two things:

    1. A new “Microsoft Deployment Toolkit Monitor Service” service was installed on the computer and started.
    2. An additional entry was added to the [Default] section of CustomSettings.ini telling the clients how to contact the server, with an entry such as:

      EventService=http://mdt-server.mdt.local:9800

    Tip #2:  Make sure the “Microsoft Deployment Toolkit Monitor Service” is installed and running.  If it’s not installed and it should be, you can uncheck the box, click apply, then check the box again and click apply to reinstall it.  If it’s installed but not running, try to start it.

    Tip #3:  Make sure the entry was added to CustomSettings.ini by looking at the Rules tab.  Because of a peculiarity with the way Workbench works, if you make any changes to the Rules tab after you’ve clicked the “Enable monitoring” checkbox but before you’ve clicked OK, it’s possible that the changes made on the Rules tab overlay the EventService entry in CustomSettings.ini, but it’s easy enough to put it back manually.

    What if the service doesn’t start?

    The service has two dependencies:

    1. .NET 3.5 SP1 needs to be installed.  That shouldn’t be an issue, because you can’t install MDT 2012 without .NET 3.5 SP1.
    2. The ports you specified need to be available for use.  (Generally that’s not an issue either, as 9800 and 9801 aren’t commonly-used TCP ports.  But it is possible to have another application use them.  Fortunately, MDT will happily use other ports.)

    So there’s no dependency on IIS or SQL Server.  The service uses .NET to host a web server as part of the service process, and it uses a SQL Compact database (basically a set of DLLs, which ship with MDT, that run in the service process) to store the monitoring information.  It’s designed to be easy to install and run.

    Tip #4:  If you try to start the service and it won’t start, that most likely means the ports you chose were already in use.  (If you want to know what’s using the ports, use a tool like TCPView, available from http://technet.microsoft.com/en-us/sysinternals/bb897437.)  Pick different ports.

    While it’s always possible that there could be some other reason the service fails, I haven’t seen any other causes.  But if you know the ports are not in use and the service still won’t start, capture a trace using DebugView (http://technet.microsoft.com/en-us/sysinternals/bb896647) to see if it provides any further clues.  If not, contact Microsoft Support for assistance.

    Verifying the Monitoring Service

    The monitoring service listens on the two ports that you specified.  The first of these ports (9800) is used by computers being deployed to send progress events.  The second (9801) is used by Workbench itself to query information about deployments being monitored.  To make sure these ports are accessible, we can manually connect to each one using Internet Explorer.

    To verify the “event port” from the monitor server itself, you can use a URL such as:

    http://localhost:9800/MDTMonitorEvent/

    If that works, you should see a response like:

    image

    That’s a proper response in this case – the web service doesn’t expect to be called in this way (an HTTP GET request instead of an HTTP POST request), so it’s telling you the proper way to call the service.

    To verify the “data port” from the monitor server itself, you can use a URL such as:

    http://localhost:9801/MDTMonitorData/

    image

    This response (which is an ODATA feed in case you are curious) confirms that the data feed is working as expected.

    But those are the easy queries – they are using “localhost”, which is almost never subject to firewall restrictions.  Next, you need to try these queries remotely, using the appropriate “remote” URLs:

    http://mdt-server:9800/MDTMonitorEvent/

    http://mdt-server:9801/MDTMonitorData/

    If those work, great.  If they don’t, then you need to make sure that whatever firewall is running on the monitoring server allows the ports you specified (e.g. 9800 and 9801) to be accessed from remote hosts.

    Tip #5:  Make sure you can access the monitor service ports both locally and remotely.  Adjust the firewall rules as necessary.

    Note that there are other networking “challenges” that can get in the way, e.g. IPSec domain isolation.  In this configuration, computers that aren’t domain-joined, e.g. running from Windows PE, can’t talk to domain-joined computers because they aren’t using encrypted IPsec communication.  This type of configuration will never work – you would need to set up the monitoring service on a “boundary server” that has been configured to allow non-IPsec traffic on the configured ports.  So don’t assume that if a “remote” URL works from a domain-joined machine, it will also work from a workgroup machine (or Windows PE) – know how your network is configured.

    From the Client Side

    When the EventService task sequence variable is set (via the processing of CustomSettings.ini), each MDT script executed in the task sequence will send an event to the monitor service on the “event port” URL.  When this succeeds, you’ll see a message like this:

    image

    If a script is unable to send an event, you’ll see something different:

    image

    That’s a clear sign that something isn’t right.  Make sure the service is running, that the firewall ports are open, etc. – the same challenges we already reviewed.

    Tip #6:  Check the client logs to make sure the clients are able to talk to the monitoring service.

    Another way you might notice an issue:  If the monitor service isn’t running, the clients will still try to connect to it, eventually timing out.  This timeout process will cause a delay at the end of each step in the task sequence, so if you are watching the task sequence progress dialog, you’ll see steps that you never noticed before (because they usually run so fast) now taking a long time.

    From Workbench

    When you try to look at the monitoring data from Workbench, it calls a PowerShell cmdlet (Get-MDTMonitorData), then that PowerShell cmdlet makes the “data port” query to retrieve the details from the monitoring service.  If the service is working as expected, you can see the list of monitored machines in Workbench.  If the service isn’t working, you’ll see something like this instead:

    image

    Good advice, make sure the service is running Smile

    Finally

    Still having issues?  Post them as comments here, or send me an e-mail at mniehaus@microsoft.com and we’ll try to figure out what’s going on. 

  • Michael Niehaus' Windows and Office deployment ramblings

    MDT 2010 New Feature #20: General Purpose ConfigMgr wizard

    • 0 Comments

    In MDT 2008, we provided unknown computer support for ConfigMgr 2007, since it didn’t provide that capability – you first had to import new computers into the ConfigMgr database before you could install an OS, so MDT helped automate that process.  When ConfigMgr 2007 R2 was released, it included unknown computer functionality, so we have now removed most of that from MDT 2010.

    One of the useful parts of this unknown computer process was a pre-execution hook that would run a wizard, leveraging the same wizard framework that we used for MDT 2010 Lite Touch deployments.  This was useful because we provided all the pieces to make it work:  the TSCONFIG.INI file that tells ConfigMgr what to run, the script that gets executed by ConfigMgr (referenced in TSCONFIG.INI), the rules processing logic to gather information from WMI and other data sources, and the wizard files themselves.

    With MDT 2010, we’ve left these pieces in place, but set them up to do something more basic: prompt for a new computer name.  This is provided as a sort of general purpose “sample”, showing how to hook this into ConfigMgr.  While you might not find the sample particularly useful, you can edit the wizard definition (using Notepad or something like the MDT Wizard Editor, http://mdtwizardeditor.codeplex.com/) to add additional panes.

    All the files related to this general purpose sample are located in the “C:\Program Files\Microsoft Deployment Toolkit\SCCM” directory:

    • ZTIMediaHook.wsf.  This is the pre-execution hook script that drives the whole process (gathering information from WMI and other sources, then displaying the wizard).
    • Deploy_SCCM_Definition_ENU.xml.  This file defines the wizard itself, which by default has one pane that asks for the computer name.
    • Deploy_SCCM_Scripts.vbs.  This file contains the initialization and validation scripts called by the wizard pane (which don’t do much in this case).
    • TSConfig.ini.  This file gets added to the boot image and tells ConfigMgr to run the ZTIMediaHook.wsf script.

    So if you wanted too do some customization, the files you would want to change are the “Deploy_SCCM_Definition_ENU.xml” (to add or change wizard panes) and “Deploy_SCCM_Scripts.vbs” (to specify additional initialization or validation logic).

    To actually get these pieces added into a boot image, you can check the “Add media hook files to enable the Deployment Wizard for this boot media” checkbox when running either the “Create Boot Image using Microsoft Deployment” wizard (which creates only new boot image which you would then need to configure the task sequence and ISOs to use) or the “Create Microsoft Deployment Task Sequence” wizard (which can create a new boot image as part of the task sequence creation process).

    Booting from this boot image, you should see a wizard that looks like this:

    image

    You can then specify the computer name you want, which will set the OSDComputerName task sequence variable.  Like I said, pretty simple, but provided as a starting point for your own customizations – just edit the XML and VBS files, create a new boot image, and then deploy.  (Remember that all of these files are actually embedded in the boot image WIM file, so when you make changes you either need to create a new boot image or mount the existing one to change those files.  Update the distribution points after making changes.)

    I’ve also attached a short video that shows the startup process (including the initial ConfigMgr wizard screen that can be used for specifying static IP information and for typing in the media password).

  • Michael Niehaus' Windows and Office deployment ramblings

    MDT 2010 New Feature #3: Suspend and resume a Lite Touch task sequence

    • 2 Comments

    MDT 2010 includes a new script called LTISuspend.wsf that isn’t actually part of any of the task sequence templates – by default it’s not used.  But if you add it into a task sequence during the “State Restore” phase activities, it will allow you to temporarily suspend the task sequence.  This is intended for reference image creation processes where there might be some activities that you just can’t possibly automate – you can make the changes by hand and then when finished restart the task sequence to create your image.  (If there is any way to do the automation, you should – this is for those “I have no other choice” situations.)

    To use this new script, add a step into the task sequence with the following command line:

    cscript.exe %SCRIPTROOT%\LTISuspend.wsf

    When this step runs, you’ll see a popup dialog on the screen:

    image

    There will be a “Resume Task Sequence” shortcut on the desktop that should be used to eventually restart the task sequence from the next step after the suspend step.  You can do anything you want while the task sequence is suspended, including rebooting, but when you are done you should make sure that you are logged in as the local Administrator account again, and that UAC is still disabled for the local Administrator account.

    This same capability is used by MDT 2010 in other ways – more on those later.

  • Michael Niehaus' Windows and Office deployment ramblings

    Make sure a ConfigMgr task sequence has all the packages it needs

    • 9 Comments

    If you’ve done ConfigMgr OS deployments, you’ve probably seen an error dialog like this before:

    image

    Pretty simple, right?  Distribute this package to a local DP and then try again.  But there are often dozens (or in extreme cases, hundreds) of packages referenced by the package, so you might have to go through this cycle a few times before you get them all.  Fortunately, there are some ConfigMgr WMI classes that can help figure out which packages are missing.  With a little bit of PowerShell logic, we can automate the whole process, through these basic steps:

    1. Get a list of all the task sequences.
    2. Get a list of all the packages referenced by those task sequences.
    3. Compare the complete list of distribution points against the list for each referenced package.
    4. Add a new distribution point to each package that is missing (keeping in mind that only boot images need to be distributed to SMSPXEImages$ distribution point shares).

    This process will take care of boot images, OS images, OS install packages, driver packages, software update packages, or any other type of packages that are referenced (because behind the scenes, packages are packages to ConfigMgr – only the UI differentiates).

    The scripts needed to this are attached to this blog.  You’ll need to save the SCCM.psm1 file in an appropriate PowerShell module folder (see the comments in the script for more details) so that the CheckTaskSequences.ps1 script can find it (and of course you’ll have to enable PowerShell script execution using something like “Set-ExecutionPolicy RemoteSigned”).  Then, just run CheckTaskSequences.ps1 each time you create a new task sequence.  It will very quickly identify the packages that need to be distributed and take care of those for you.

    Note:  Version 1.1 of the script is now attached (using version 1.5 of SCCM.psm1) to address a couple of bugs found in the current version.  See the comments in CheckTaskSequences.ps1 for more information.

  • Michael Niehaus' Windows and Office deployment ramblings

    Adding members to a ConfigMgr collection using PowerShell v2 CTP3

    • 4 Comments

    After you have created a collection, you need to be able to populate it with either direct membership rules or query rules.  That’s where the next function comes into play.  As with the previous examples, take the following script code and add it to your growing SCCM.PSM1 file:

    --- snip ---

    function Add-SCCMCollectionRule
    {
        [CmdletBinding()]
        PARAM
        (
            [Parameter(ValueFromPipelineByPropertyName=$true)] $collectionID,
            [Parameter(ValueFromPipeline=$true)] [String[]] $name,
            [Parameter()] $queryExpression,
            [Parameter()] $queryRuleName
        )
        Process
        {
            # Get the specified collection (to make sure we have the lazy properties)
            $coll = [wmi]"\\$sccmServer\$($sccmNamespace):SMS_Collection.CollectionID='$collectionID'"

            # Build the new rule
            if ($queryExpression -ne $null)
            {
                # Create a query rule
                $ruleClass = [wmiclass]"\\$sccmServer\$($sccmNamespace):SMS_CollectionRuleQuery"
                $newRule = $ruleClass.CreateInstance()
                $newRule.RuleName = $queryRuleName
                $newRule.QueryExpression = $queryExpression

                $null = $coll.AddMembershipRule($newRule)
            }
            else
            {
                $ruleClass = [wmiclass]"\\$sccmServer\$($sccmNamespace):SMS_CollectionRuleDirect"

                # Find each computer
                foreach ($n in $name)
                {
                    foreach ($computer in get-SCCMComputer -filter "Name = '$n'")
                    {
                        # See if the computer is already a member
                        $found = $false
                        if ($coll.CollectionRules -ne $null)
                        {
                            foreach ($member in $coll.CollectionRules)
                            {
                                if ($member.ResourceID -eq $computer.ResourceID)
                                {
                                    $found = $true
                                }
                            }
                        }
                        if (-not $found)
                        {
                            Write-Verbose "Adding new rule for computer $n"
                            $newRule = $ruleClass.CreateInstance()
                            $newRule.RuleName = $n
                            $newRule.ResourceClassName = "SMS_R_System"
                            $newRule.ResourceID = $computer.ResourceID

                            $null = $coll.AddMembershipRule($newRule)
                        }
                        else
                        {
                            Write-Verbose "Computer $n is already in the collection"
                        }
                    }
                }
            }
        }
    }

    --- snip ---

    So now you can add a static collection membership rule or a query rule:

    Connect-SCCMServer

    Add-SCCMCollectionRule -collectionID CEN00001 -name MYSERVER

    Add-SCCMCollectionRule -collectionID CEN00001 -queryRuleName "My Rule" -queryExpression "select * from SMS_R_System"

    But notice that the function above can actually accept some inputs from the PowerShell pipeline.  That means you can get more creative.  For example, assume you have a file that has the list of machines you want to add, something like this Computers.txt file that has one computer name per line:

    COMPUTER1

    COMPUTER2

    COMPUTER3

    With that, you can do something like this:

    Get-Content C:\Computers.txt | add-SCCMCollectionRule -collectionID CEN00001

    It will add three new rules to the collection.  Or, if you want to create a collection and add a member at the same time, you can pipe the collection into the collection rule function:

    New-SCCMCollection -name "My Collection" | Add-SCCMCollectionRule -name MYSERVER

    A few comments on the logic above:

    • The above function can’t take a list of query rules from the pipeline, although it could be modified to do that – it could then take it’s input from the pipeline as well, maybe from the import-CSV cmdlet. 
    • I tried to get PowerShell to call the AddMembershipRules function (to more efficiently add batches of rules) but I’ve been unable to get this to work.  (PowerShell wins that battle – for now.)
    • I really should be defining parameter sets that show which combinations of parameters are valid together (e.g. you shouldn’t specify -name at the same time as -queryExpression) and which should be specified together (e.g. -queryRuleName and -queryExpression should both be specified).
    • I should provide help text so you can use “get-help” to get the details.

    Those are all exercises for some other time :-)

  • Michael Niehaus' Windows and Office deployment ramblings

    Hiding (and showing) the task sequence progress dialog box

    • 4 Comments

    fThe task sequencer used in MDT 2010 and ConfigMgr 2007 is designed to show you at all times what’s going on with your task sequence:

    image

    As a result, it forces this dialog to be on the top of all other windows, which can be annoying in some situations because you want to get this out of the way.  I’ve seen various solutions for this, typically that just move the dialog out of the way (to the edge of the screen, off the screen, etc.).  But there’s a much easier way to do this:  you can just tell this dialog to close itself using a very simple script:

    ' Hide the progress dialog

    Set oTSProgressUI = CreateObject("Microsoft.SMS.TSProgressUI")
    oTSProgressUI.CloseProgressDialog
    Set oTSProgressUI = Nothing

    That causes the progress dialog to close (not surprisingly) – at least until it is told to update the progress, something that would typically happen at the start of the next step.  So it’s only gone temporarily, but that’s OK – in most cases, you just want it to be gone for one step anyway, so have that step run a VBScript that hides the dialog, does its work, and then exits.

    If you did want the progress dialog to show up again before the next step, you can force a progress update.  If you are referencing the MDT ZTIUtility.vbs script, this is pretty simple too because it already has a function to do this.  Just include logic like this:

    ' Report progress to get the dialog to show up again

    oLogging.ReportProgress "Done", 100

    If you aren’t using ZTIUtility.vbs, you can add some logic like this:

    Public Function OpenProgressDialog

        Dim oProgress
        Dim uStep
        Dim uMaxStep

        ' Try to create the progress UI object

        On Error Resume Next
        Set oProgress = CreateObject("Microsoft.SMS.TSProgressUI")
        If Err then
            Err.Clear
            Exit Function
        End if
        On Error Goto 0

        ' Update the progress

        On Error Resume Next

        uStep = CLng(oEnvironment.Item("_SMSTSNextInstructionPointer"))
        uMaxStep = CLng(oEnvironment.Item("_SMSTSInstructionTableSize"))
        Call oProgress.ShowTSProgress(oEnvironment.Item("_SMSTSOrgName"), oEnvironment.Item("_SMSTSPackageName"), oEnvironment.Item("_SMSTSCustomProgressDialogMessage"), oEnvironment.Item("_SMSTSCurrentActionName"), (uStep), (uMaxStep))

        On Error Goto 0

        ' Dispose of the object

        Set oProgress = Nothing

    End Function

    If using ZTIUtility, the complete script could look like this:

    <job id="Scripting201">
       <script language="VBScript" src="ZTIUtility.vbs"/>
       <script language="VBScript">

        ' Hide the progress dialog

        Set oTSProgressUI = CreateObject("Microsoft.SMS.TSProgressUI")
        oTSProgressUI.CloseProgressDialog
        Set oTSProgressUI = Nothing

        ' <Do your work here>

        ' Show the progress dialog (using one of these methods) if you don't want to wait

        oLogging.ReportProgress "Done", 100

        ' <Maybe do some more work>

       </script>
    </job>

    If you aren’t using ZTIUtility.vbs, it gets a little longer:

    <job id="Scripting201">
       <script language="VBScript">

        ' Hide the progress dialog

        Set oTSProgressUI = CreateObject("Microsoft.SMS.TSProgressUI")
        oTSProgressUI.CloseProgressDialog
        Set oTSProgressUI = Nothing

        ' <Do your work here>

        ' Show the progress dialog (using one of these methods) if you don't want to wait

        OpenProgressDialog

        ' <Maybe do some more work>

        ' The OpenProgress Dialog method gets the dialog to show up again

        Public Function OpenProgressDialog

            Dim oProgress
            Dim uStep
            Dim uMaxStep

            ' Try to create the progress UI object

            On Error Resume Next
            Set oProgress = CreateObject("Microsoft.SMS.TSProgressUI")
            If Err then
                Err.Clear
                Exit Function
            End if
            On Error Goto 0

            ' Update the progress

            On Error Resume Next

            uStep = CLng(oEnvironment.Item("_SMSTSNextInstructionPointer"))
            uMaxStep = CLng(oEnvironment.Item("_SMSTSInstructionTableSize"))
            Call oProgress.ShowTSProgress(oEnvironment.Item("_SMSTSOrgName"), oEnvironment.Item("_SMSTSPackageName"), oEnvironment.Item("_SMSTSCustomProgressDialogMessage"), oEnvironment.Item("_SMSTSCurrentActionName"), (uStep), (uMaxStep))

            On Error Goto 0

            ' Dispose of the object

            Set oProgress = Nothing

        End Function

       </script>
    </job>

  • Michael Niehaus' Windows and Office deployment ramblings

    Getting Microsoft Deployment Toolkit 2008 to install updates from WSUS

    • 10 Comments

    Microsoft Deployment Toolkit contains a script named ZTIWindowsUpdate.wsf that can be enabled to run during Lite Touch OS deployments.  By default, it will talk to the Microsoft Update site on the internet to get the latest updates needed for your Windows OS and Microsoft applications like Office.  But you might not want all of the machines you deploy doing that.  So with MDT 2008, we added the ability to install updates from a WSUS server.  The "Toolkit Reference" document describes the basic process:

    MDT 2008 can also configure WUA to collect updates from computers on the corporate network that are running WSUS instead of connecting to Microsoft Updates over the Internet. MDT 2008 can optionally configure WUA to use a specific computer running WSUS using the WSUSServer property.

    But the actual description of the WSUSServer property, and a sample of how to set it, was accidentally left out of the documentation.  This needs to be configured via CustomSettings.ini by adding an entry that looks like this:

    WSUSServer=http://mywsusservername

    With that set, the ZTIWindowsUpdate.wsf script will automatically configure the Windows Update Agent to talk to this WSUS server instead of using Microsoft Update.

    One other note: the new OS being deployed to the machine must be running a supported version of the Windows Update Agent (WUA).  Windows XP and Windows Server 2003 don't contain that needed version, so they need to be upgraded.  This will be done automatically by the script, downloading the files from the internet if necessary.  But it would be more efficient for you to download them in advance and place them where the script can find them.  Again from the documentation:

    For additional information and for WUA deployment instructions, go to http://technet.microsoft.com/en-us/library/bb932139.aspx.

    You can obtain the latest version of the WUA stand-alone installer for:

    Windows Vista and Windows Server 2008 include the most recent version of WUA, so no upgrade is necessary for these operating systems. In Windows XP and Windows Server 2003, one of the following will occur:

    • If the WUA 3.0 stand-alone installer files are in the TOOLS\architecture folder (where architecture is either x86 or x64) on the deployment point, MDT 2008 will automatically install WUA on the target computer.

      When downloading the WUA 3.0 stand-alone installer files, save them in the distribution\TOOLS\architecture folder (where distribution is the folder where the distribution point is created).
    • If the WUA 3.0 stand-alone installer files are not in the TOOLS\architecture folder on the deployment point and if the existing version of WUA is configured for a WSUS server, then WUA will attempt to update itself from a WSUS server. If the existing version of WUA is not configured for a WSUS server, then MDT 2008 will attempt to download and install WUA 3.0 from the Microsoft Update site. In this case, Internet access is required for the target computer.

    So if you set WSUSServer and download the updated stand-alone installers, then the ZTIWindowsUpdate.wsf script will be able to update your computer without access the internet to do so.

  • Michael Niehaus' Windows and Office deployment ramblings

    ConfigMgr 2007 Driver Management: The Novel, part 3

    • 1 Comments

    In the first part of this series, I talked about the three (well, four with Johan’s) approaches for managing drivers with ConfigMgr 2007.  There’s actually another method that I didn’t mention so far (one that we briefly mentioned at the end of the MMS session):

    • “Treat them all as software”
      • In this scenario, you’ll basically not use the driver injection capabilities in ConfigMgr at all.  Instead, you’ll install all the drivers using “Install Software” steps after the new OS is up and running.  This assumes the images being deployed already contain all the needed network or mass storage drivers.  (You could choose a hybrid scenario to handle these, more on that below.)
      • Pros:
        • Many drivers require software packages anyway (especially more complicated ones like wireless broadband cards, Bluetooth adapters, etc.), so this way the driver and the application get installed at the same time.
        • You don’t need to figure out how to extract the drivers, just how to install the package silently.
      • Cons:
        • You have to figure out how to run the driver installer silently.  If you can’t figure this out, you need to repackage the driver (something that is something of an art form).
        • Some OSes (e.g. Windows XP) may prompt because of the missing drivers before the actual installation occurs.
        • If you put each driver into a separate package, you’ll end up with lots and lots of packages (see the part 1 posting for what challenges lots of packages presents).
        • If you combine the driver software into a single package, you’ll need to wrap all the installers so that a single script or batch file installs them all.

    Honestly, I didn’t think anyone did this for all of their drivers, although I did expect to see this done for a few limited drivers (such as the more complicated ones I mentioned before).  But I have found a couple of customers who do exactly this: every driver is installed as an application.  Still, I expect that to be rare in the “real world”.

    So that leads us to the topic of “hybrid scenarios” – you can mix and match these scenarios to address all of your driver needs, something I would strongly encourage.  Imagine a task sequence that uses something like this:

    • “Apply driver package” for all model-specific drivers.
    • “Auto apply driver” for all miscellaneous drivers (e.g. printer drivers, smart card drivers, other USB drivers – things that will be found on random machines in the environment).
    • “Install Software” for drivers that are more easily installed with their software once the new OS is up and running.

    See Chris Nacker’s blog posting at http://myitforum.com/cs2/blogs/cnackers/archive/2010/04/09/configuration-manager-2007-r2-sp2-driver-management.aspx for a real-world example of how you might set up something like this.

    In the session I also showed a simple script that created driver packages pointing to existing folders in the driver store, to quickly set up “Johan’s Control Freak Method”.  That script was basically the same script as attached to the previous blog posting, with all the logic for importing drivers into the driver store stripped out, since that logic isn’t used in Johan’s scenario.  That script, named CreateJohanDriverPackages.ps1 (which still depends on the SCCM.psm1 module), is attached to this blog posting.

    Johan has also added a new blog posting detailing the MDT 2010 Lite Touch scenarios, which you can find at http://www.deployvista.com/Blog/JohanArwidmark/tabid/78/EntryID/132/language/en-US/Default.aspx.

    A few other random driver-related items that may be of interest:

Page 4 of 27 (265 items) «23456»