Share This Post

A Look At The ZTIGather Flow and The CustomSettings.INI File

"I don't understand the CustomSettings.ini file."  I hear this from a high percentage of clients who use MDT in their environment. If they started with an MDT-only deployment position, I will often see a rules file that was primarily configured by MDT and then some light additions, but they often do not have a clear understanding of how the processing works or what each section actually does. If they started with SCCM and added MDT, often I don't see a rules file at all, but rather custom scripts that ultimately reinvent the wheel; a waste of administrative time and an unneeded level of complexity. If you use MDT alone, within SCCM, or have extracted parts for use with another deployment system, and you haven't mastered the CustomSettings.ini file, take what will be a surprisingly little amount of time and master this tool. In this article I am going to cover the flow of the Gather task, what it is doing step by step, and in doing so show you how to leverage some of the hidden features to not only reduce the complexity of your build process, but move you closer to a true Lite Touch (LTI) or Zero Touch (ZTI) build.

While reading this article, ensure you have the "Microsoft Deployment Toolkit Documentation Library" (MDTDL) help file opened; complete information on many of the section keys are easily found when searched for, and I make a lot of references to this throughout the article. For the purposes of this writing, I am using MDT 2012 Update 1.

Back To Basics: What Comprises the ZTIGather Tool

File List:

  • ZTIGather.wsf
  • ZTIGather.xml
  • ZTIUtility.vbs
  • ZTIDataAccess.vbs
  • CustomSettings.ini

All files needed to use the ZTIGather tool are located in the MDT Scripts folder. If you plan on using the MDT Gather action within an MDT task sequence, an MDT-enhanced SCCM task sequence, or you wish to bring some of this functionality with you into another deployment process, you need to ensure that all of these files are all available.

ZTIGather.wsf is the primary "engine", the script that is called when you use a Gather action or when you trigger the process from a command line. This script is also responsibility for the inclusion of MDT properties which are focused on the local environment (e.g., IsLaptop, OSVersion, etc.). ZTIUtility.vbs and ZTIDataAccess.vbs are the foundational MDT components, and provide the backbone for the ZTIGather process.

The ZTIGather.xml is of special interest, being a "master" list of additional properties to manage, focused more on properties used within MDT and SCCM for the task sequence and Windows mini-setup. They are essential to LTI/ZTI deployments. After processing the local environment, unless directed otherwise, ZTIGather loads in all these properties, along with a few handling instructions for each, most notably whether or not it can be overwritten once a value has been assigned. With the exception of 9 single-valued properties configured to be overwritable and all multi-valued properties (lists), the behavior of the ZTIGather process is to not overwrite these properties if they have an existing value. This can be a stumbling block for many. Lets say that the goal is to set the computer name. If via the ZTIGather process you first defined a "catch-all" name, then trigger a process to create a specific name if certain environmental conditions are met, you would wind up with the "catch-all" name no matter what. If you did the process in reverse however, you would wind up with the "catch-all" name only if your other conditions were not met. This can be additionally confusing because not only does the no-overwrite rule not apply to every MDT property, for the most part, this restriction is implemented only by the ZTIGather process. You can manually overwrite values at any time both inside the task sequence or through your own custom script. Although you will typically never need to alter this file, the rare occasion where you might would be to change the overwrite behavior of a specific single-valued properties. If you are thinking ahead at this time that this file would be a good place to add your own custom properties, it really isn't.

The last file on the list is the CustomSettings.ini. When working with pure MDT, this is a "required" file and is used by multiple MDT processes to configure the deployment environment. Within SCCM + MDT, this is only needed when you are going to do more than gather the local environment properties. The key thing to remember is that you are not limited to just a single CustomSettings.ini file, and you are not limited to the name "CustomSettings". Depending on the complexity of your environment, you can create many different rules files and specify them to ZTIGather as appropriate.

The 20,000 Foot Flow

ZTIGather Global Flow

One thing to know about the ZTIGather process is that it is flexible insofar as what it will do. There are two primary responsibilities of ZTIGather: to gather the local computer's environmental information, and to process a specific rules file. These are independent actions. If the script is launched with a command line option of "nolocalonly", no local environmental information is gathered. If the script is launched with a command line option of "localonly" or if the MDT property "GatherLocalOnly" is set to "True", then the process will only gather the local environmental information and exit out.

Rules processing is always assumed unless the script was directed to only gather the local environmental. With no additional command settings, ZTIGather will first look for the rules file specified in the MDT property "RulesFile". This is typically the CustomSettings.ini and if you are working within an MDT-only environment, it's location is set for you. If you are working outside of an MDT-only environment, you have two choices: set the MDT property prior to calling the script, or specify the rules file on the command line using the argument "inifile" (this is done for you when you use the task sequence action and specify a rules file). The command line argument should be the complete path and file name to use. If set to process rules and ZTIGather is unable to locate a rules file, ZTIGather will exit out with an error, which can halt your build process. If you specify the command line argument "optional", an error will not keep ZTIGather from exiting, but it will keep it from throwing an error code on exit.

Intrinsic MDT Properties

Above I briefly mentioned the two property lists ZTIGather manages: local environment properties and additional LTI/ZTI properties. Combined, there are over 300 properties introduced into the environment, far too many to cover in this article. But to understand the ZTIGather process, you should be familiar with what is already discovered and ready for your use, both so you can reference these native properties within a ZTIGather rules file, and reference them within your task sequences or custom scripts. A list of these properties can be found by searching the MDTDL for "Property Definition", then following the links for each property.

Be aware that the documentation can be slightly confusing when you are looking at the "Property configured by" section. The somewhat odd superscript tilde ( ˜ ) marking either BootStrap.ini, CustomSettings.ini, or MDT DB does not mean dynamic discovery, but rather only specifies where within the ZTIGather process the property is typically used. Look instead for a "Note" section saying that the property is dynamically set by MDT scripts. It is only those properties that you can assume a value has been assigned to by the time you reference them.

The Rules File Quick Overview for Custom Properties

Sample Rules File #1

[Settings]
Priority=Section1, Section2, …
Properties=MyProp1, MyProp2(*), …

[Section1]
. . .

ZTIGather first adds the local environmental properties hard-coded in the ZTIGather.wsf script itself. Next, unless a "local only" flag has been set, it loads in all the properties within the ZTIGather.xml file. This concludes intrinsic properties. The next action ZTIGather takes is to load in any custom properties you wish to include in the process. There is a very simple syntax for a rules file, displayed in the "Sample Rules File #1" box. There is only one required section, "Settings". Within that section, there is one required key, "Priority". Priority is a comma separated list of user defined sections you wish ZTIGather to process, listed in the order you wish ZTIGather to process them in. The other listed key is optional, but used if you wish to add custom properties. "Properties" is also a comma separated list of user defined property names. Here are the general guidelines you need to follow when using custom properties:

  • Define before use.   In other words, don't attempt to reference a custom property unless you have first added it to the Properties list. If you do not adhere to this, your custom property will be discarded when the ZTIGather process ends.
  • You can't supersede an intrinsic property.    Lets say you are adding a process to include the area code in your build, and having missed that there is an intrinsic property called AreaCode, you add AreaCode to the list of properties. MDT will recognize that there is already this property and "ignore" your inclusion. The end result will be what you want.
  • Know the difference between single and multi -valued properties and define correctly. A single-value property is as it sounds; one value to one property (e.g., AreaCode=206). When you wish to define a single-valued property, just type out the name you want. A multi-valued property means that there is more than one value which can be assigned to the property. Although the Priority and Properties keys contain multiple values separated by a comma, MDT would consider a property with a similar list a single-valued property. Multi-valued properties, also referenced as Lists, are similar to arrays (in concept) in that a property name can be referenced with an index position to hold a value. An example of this would be the IPAddress property. If your computer has two IP addresses, MDT would have stored them in IPAddress001 and IPAddress002. If you plan on working with custom multi-valued properties, make sure you define they in the Properties section by specifying the root name, followed by "(*)". Search the MDTDL for "ListProperty", as well as look at the property definition for Applications and Groups.

Follow The Rules

The main purpose of the ZTIGather process is to find values for properties. As you know, step one is to gather the local environment. Besides creating the property within the environment, ZTIGather takes steps to populate the property with the correct value. For instance, to get the machine's current name, it queries the Windows environmental variable list and stores the local computer name in the HostName property. To get the OS Version, it makes a series of registry queries and determines not only the OSVersion, but if the OS IsServerOS and IServerCoreOS. But it ends there. ZTIGather may load in the additional intrinsic properties and your custom list of properties, but they are generally blank and ZTIGather has no idea how to populate them with the correct information. This is where you come in.

Within the Settings section is the Priority key. I mentioned that this is populated with a comma separated list of "rules" to process, and the order in which to process them. Rules are simply the names of a sections contained within the rules file. There are three guidelines defining what the rule/section name can be.

Your Name Here

Sample Rules File #2

[Settings]
Priority=Jase, Default
Properties=MyProp1, …

[Default]
. . .

[Jase]
. . .

The first type of rule name is one that you make up yourself. Out of the box, MDT has made up a name for you: Default. Our sample here has two, Default and Jase. Once ZTIGather has finished processing the local environment properties, it will read the list of rules/sections defined in the Priority key, and first jump to Jase, process that section, then jump to Default and process that.

  • It makes absolutely no difference within the rules file what order you create your sections. As the example shows, the Jase section is positioned below the Default section, but it would be the Jase section that gets processed first.
  • If you include a rule to process, but fail to add that section to the file, ZTIGather won't care. It will just skip processing, report a success on that attempt, and move on to the next. Keep that in mind as typos can cause unexpected results that are hard to track.
  • You are neither limited to the Latin character set, nor restricted in the use of spaces and other special characters. Call your section "My Section", "My_Section", "ซฌฐถดฝฬัี" – whatever. ZTIGather is truly processing the names as a comma separated list, only stripping leading and trailing spaces prior to evaluation.
  • ZTIGather is case insensitive. "MySection" is the same as "mysection" and "MYSECTION".
  • You can call the same rule twice, but you can only have one section. Don't accidentally create two sections identically named. You can, however, call the same section multiple times.

Single-Valued Property Names

Sample Rules File #3

[Settings]
Priority=Make, SerialNumber
Properties=MyProp1, …

[AFD1258E]
. . .

[Microsoft Corporation]
. . .

Here is where a lot of people get confused. Your rule/section name can not only be your made up word, it can also be the name of an MDT Property. But unlike your made up word where ZTIGather is going to see that it needs to process rule "Jase" and looks for a section called "Jase", ZTIGather is going to see that it needs to process a rule called "Make", recognize it as the name of a property, and look for a section named after the value contained within the property Make.

Pretend that your build process was running in a Hyper-V environment. Using the "Sample Rules File #3", when ZTIGather processed the rule Make, it would know that was an MDT property, see that the value of Make is "Microsoft Corporation", and look for a section named "Microsoft Corporation". As our file has one, ZTIGather processes that section. But if we were running our build process on ThinkPad, Make would have the value of "Lenovo", and as we do not have a section named "Lenovo", ZTIGather would skip it.   The same process goes for the next rule, SerialNumber. If ZTIGather cannot find a section name matching the discovered serial number, that section is skipped.

Multi-Valued Property Names

Sample Rules File #4

[Settings]
Priority=MACAddress
Properties=MyProp1, …

[00:0F:20:35:DE:AC]
. . .

[00:03:FF:FE:FF:FF]
. . .

Similar to single-valued properties, if your rule/section is named after a multi-valued property, ZTIGather will recognize the reference and process it by value, not name. Here is where it can get fun. After the local environment gathering process, pretend that ZTIGather has determined that your local computer has two MAC addresses. At this point, the MACAddress multi-valued property looks like this:

  • MACAddress001=00:0F:20:35:DE:AC
  • MACAddress002=00:0E:31:35:FF:AC

As you have only referenced the root property name, ZTIGather will enumerate each value contained within that property, looking for a section named after each value in the list. So, when ZTIGather evaluates MACAddress001 it will find a matching section and process it. When it evaluates MACAddress002, no matching section is found so it is skipped. However, if instead of using MACAddress within the Priority key, you had specified MACAddress001, then it would have been treated like a single-valued property name as described above.

The Priority Key Flow

You already know that ZTIGather will process each rule within the Priority key in the order specified. You also already know the three guidelines for rule names. Lets focus for a moment on the identification flow ZTIGather takes for each rule.

The first evaluation ZTIGather does against the name is to check to see if it is called "DefaultGateway". Although this is an MDT intrinsic multi-valued property, ZTIGather does not treat it as such. Instead, it treats it exactly the same way it would treat a user-named rule, looking for a section within the rule file called "[DefaultGateway]" and processing it if found. This is the only MDT property treated this way. I encourage you to take a quick look at the MDTDL under "DefaultGateway" to see an example of how that section is processed. I will be covering exactly what it is doing and why in a subsequent section, so don't worry if it looks odd to you now.

If the rule name is not DefaultGateway, ZTIGather will next check to see if the name is a multi-valued property. If not a multi-valued property, it is then referenced against the list of single-valued properties. If not a single-value property, it is treated as a user named rule.

What is key to remember here is that the rule will be matched to only one of these four categories and once a match is made and the rule section processed, ZTIGather moves on. In this manner, for example, DefaultGateway is not processed as the special rule name first, then again as a multi-valued property.

Now may be a good time for a coffee break.

Section Processing Flow

Thus far, we have looked at the over-all flow, the flow for obtaining the Property lists, and the flow for identifying and starting the rule/section flow. Now we're going to take a look at the flow ZTIGather follows for each rule section. One of the issues I hear most is that many users developing a rules file don't completely understand how each section should be configured. The MDTDL samples make it appear that there is a specific section configuration for property assignments, web service calls, database calls, etc., and indeed many have become frustrated when they attempt to configure a section on their own beyond what MDT's automation wrote for them and/or what they pulled from the documentation samples – and it doesn't respond like they expected it to. 

The simple truth is that there is almost no function-specific section formatting: all sections are processed exactly the same, with one exception (read on for the big reveal). Once you know the flow, the entire process demystifies and creating these rules could not be clearer.

This figure generically outlines the flow ZTIGather takes when processing any section. ZTIGather will always enter a section, run it through six or seven evaluations (keep waiting for the reveal), and move on. What you can take from that is that even though most of the time you see a single-focused section (i.e., one dedicated to a database or Web Service calls), a single section can have multiple purposes, as long as each purpose has the correct configuration. Ensure you keep to the syntax rules for INI files: Section names must be unique within the file, and Key names must be unique within the section. You cannot have two keys in the same section with the same name.

The evaluation flow for a section is as following: UserExit Code? Database Section? Web Services Section? Subsections? Dynamic Package Section? Property Assignment? and finally UserExit Code? (again).

Evaluation #1: UserExit Code

Rules Section

[MySectionName]
UserExit=MyScriptName.vbs
. . .

The first evaluation ZTIGather does is to see if a UserExit key has been defined. This is an extremely powerful feature which serves three purposes within a section, two of which I'll cover here and one later on.

This key allows you to specify a custom script that ZTIGather will load at the start of the section processing. Within that script you must included a function called UserExit, configured with the four parameters listed in the sample. ZTIGather will then execute that function, passing to it the following:

 'Required Function Sample Structure 

Function UserExit(sType, sWhen, sDetail, bSkip) 
     If sWhen = "BEFORE" Then 
          'Optional Code 
     Else '"AFTER" 
          'Optional Code 
     End If 

     bSkip = [True|False] 
     UserExit = [Success|Failure] 
End Function
  • sType – A string value, which will always be "SECTION". This is an "In" parameter only.
  • sWhen – A string value, either the value "BEFORE" or "AFTER" depending on where in the section evaluation the UserExit routine is being called. This is an "In" parameter only.
  • sDetail – A string value, specifically the name of the section currently being evaluated. This is an "In" parameter only.
  • bSkip – A Boolean value. If no UserExit script in any section alters the value of this variable, then the variable will not be set. However, as it is a global variable and can always be set by another process, it is recommended that the value be set within the UserExit script. If set to True, once the UserExit script is processed in the "BEFORE" stage, no further processing of that section is done. If set to False, the section continues to be evaluated.

This gives you the ability to create your own types of property assignment sections, exposing all the MDT properties to you for manual alteration  As you can see, you are told if you are running this at the beginning of the section evaluation or the end, and as such, you can make additional decisions. If you are in the "BEFORE" stage, you have the ability to stop ZTIGather from processing any more of that specific section (it then moves on to the next rule in the priority list), and if you are in the "AFTER" stage, you can actually terminate the entire ZTIGather process if you discover an issue.

One thing to keep in mind:  If there is any error in your script which prevents ZTIGather from loading it or running your function as expected, ZTIGather will simply skip processing of that entire section, moving on to the next section to process from the Priority list – without any failure notification outside of the ZTIGather.log file.

Evaluation #2: Database Section

Rules Section

[MySectionName]
. . .
SQLServer=SqlServerName
Instance=InstanceName
Port=PortNumber
Database=DatabaseName
Netlib=(DBNMPNTW|DBMSSOCN)
Table=TableName
StoredProcedure=StoredProcName
DBID=SQLUserName
DBPwd=SQLUserPassword
SQLShare=SQLServerShareName
Parameters=Param1, Param2, …
ParameterCondition=(AND|OR)
Order=TableColName1, TableColName2, …
MDTPropertyName=SQLColumName
. . .

After the UserExit evaluation, ZTIGather reevaluates the section looking to see if there is a database connection configuration. The section is processed for a database connection if the key SQLServer is discovered. The sample section shows all the keys (in black) associated with a database section, not all of which are required. The key in red is a Translation Key and shows an association, not a specific name and value. I'll explain the Translation Key below.

A database call is by far the most complex action the ZTIGather process does, and to make it work there is a lot of configuration that needs to be done by you. Lets quickly go over each of the keys used, and of course you should reference the MDTDL for more information on each.

  • SQLServer – Required. The name of the SQL server to connect to. Can be FQDN, NetBIOS, or IP. If the instance name is specified as part of the SQL server name (i.e., "SQLServerName\InstanceName"), the instance name will used for the Instance property, overwriting any value specified in the Instance key.
  • Instance – Optional. The name of the SQL instance.
  • Port – Optional. The port to connect on
  • Database – Required. The name of the database to use. If omitted, "BDDAdminDB" will become the database name used.
  • Netlib – Optional. How to connect, via named pipes or TCP/IP socket. If omitted, "DBNMPNTW" becomes the connection type.
  • Table – Required, unless a StoredProcedure has been specified. The name of the table to query. If omitted and no StoredProcedure specified, "BDDAdminCore" will become the table used.
  • StoredProcedure – Required, unless a Table has been specified. The name of the stored procedure to execute
  • DBID – Optional, unless no SQLShare is specified. The user name to connect to the SQL server with using that server's SQL Server Authentication.
  • DBPwd – Optional, unless no SQLShare is specified. The password of the DBID specified
  • SQLShare – Optional, unless no DBID/DBPwd is specified. To use Windows Authentication instead of SQL Server Authentication, specify a share on the SQL server to connect to. The UserDomain, UserID, and UserPassword properties will be used to make the connection and subsequent SQL authentication.
  • Parameters – Optional. A comma separated list of table column names to query. Makes use of the Translation Keys.
  • ParameterCondition – Optional. The parameter condition to use. If omitted, "AND" will be used.
  • Order – Optional. The sort order for the return recordset

Lets also quickly go over the flow ZTIGather takes with a database call. A full overview is beyond the scope of this article, but knowing how it plays into the entire process is good.

  1. ZTIGather evaluates the all the connection specific properties and opens a connection to the database.
    1. If no SQL Username / password is specified and a SQL share is specified, MDT will attempt to connect to that share with the stored Windows credentials. A failure will not end processing of the section, and ZTIGather will attempt to make a connection to the database under the current context credentials.
      1. If ZTIGather encounters a failure to connect, it will end any further database processing of the section, without throwing an error, and move on to the next evaluation step. You will only know an issue occurred if you either evaluate the ZTIGather.log file, or use a procedure within the UserExit script.
  2. ZTIGather then forms a query string and makes the query.
    1. If there is a failure making the query or an error navigating the recordset, ZTIGather will end any further database processing of the section, without throwing an error, and move on to the next evaluation step. Again, evaluate the log or use a custom procedure to evaluate the environment.
    2. A successful query but with no results is considered a success, be that right or wrong.
  3. ZTIGather evaluates each field returned, looks for a Translation Key within the section, and stores the resultant field value in the correct MDT Property.
The Translation Key

Step 2 and 3 are a bit of MDT magic facilitated by the use of a Translation Key. A Translation Key is used twice, once when forming the query, and again when processing the returned data. MDT only knows about it's properties (be they hard coded, additional via the xml file, or your custom additions). Like any property, MDT knows the name of the property and the value it holds. When forming a SQL query however, MDT doesn't know the field names within the table any more than it knows the name of the table; you're supplying both. If you're using a MDT created database, then the table field names will match MDT property names. But if you have expanded the MDT database or are using a custom database, those field names may not match.

Say you were querying a database, looking for a specific set of configuration settings based on the make, model, and area code of the local computer. Say that table's fields were called "Make", "Model", and "TelephoneAreaCode". In order to form your query, your WHERE clause needs to use the table's field names, so you pass those to MDT in the Parameters key (i.e., Parameters=Make, Model, TelephoneAreaCode). MDT however, only recognizing MDT properties within the Parameters key, uses the property name as the literal name of the table field, and uses the property's value for the condition value. You're OK with the Make and Model parameters as they are MDT properties and thus would translate to "WHERE Make='Microsoft' AND Model='Virtual PC'". But "TelephoneAreaCode" isn't an MDT property and would result in MDT assigning a blank value to the query, making your query "WHERE Make='Microsoft' AND Model='Virtual PC' AND TelephoneAreaCode=''". This isn't what you want and could result in incorrect data returned. You do have an option to create a custom property and use a property assignment to assign the value of the intrinsic AreaCode property to your custom property, but MDT already provides a way to make the translation. As you want the value of the AreaCode property passed, you create a Translation Key. This would be the name of the MDT property equaling the name of the SQL field. In our example, AreaCode=TelephoneAreaCode. Then, within the Parameters key, you substitute the MDT property AreaCode for the "TelephoneAreaCode" you had (i.e., Parameters=Make, Model, AreaCode). When ZTIGather is processing the properties within the Parameters key, it will look to see if you have a Translation Key, and if found, it will substitute the SQL field name for the MDT property name while still using the MDT property value: "WHERE Make='Microsoft' AND Model='Virtual PC' AND TelephoneAreaCode='206'". This is the exact same behavior that the Order key makes, despite only the name being used and not the value.

Upon a successful query, there is a similar process. For each record returned, ZTIGather will look at the name of the SQL field and see if you have a Translation Key specified. The syntax for this Translation Key is exactly the same as before despite the reverse lookup order (there is a lot of processing happening behind the scenes for this reverse lookup to work – a lot). If a Translation Key is found, the value of that field is assigned to the MDT Property you specified, otherwise it is assigned to a property matching the name of the SQL field. Again good if you're using an MDT database, possibly problematic if using your own, especially if you do not have a custom property defined which matches the untranslated field name. Ensure that you either provide a Translation Key or a custom property when dealing with SQL returns from a non-MDT source.

Evaluation #3: Web Services

Rules Section

[MySectionName]
. . .
WebService=URL\FunctionName
Parameters=Param1,Param2, …
Method=(GET|POST|REST)
MDTPropertyName=XMLElementName
MDTPropertyName=FuncParam

. . .

Straight off a database evaluation, ZTIGather is back reevaluating the section for a web services call. The section is processed for a web service connection if the key WebService is discovered. The sample section shows all the keys (in black) associated with a web service section, not all of which are required. The keys in red are again Translation Keys; I'll recap their differences.

Similar to before, the web service function's named parameters may or may not match the name of the desired MDT property. In that event, a Translation Key is again used to associate the name of the function parameter with a property. When processing the resulting XML, element names can be translated back to property names.

Unlike the database section above, I'm not going to cover all the details of a web services section, only because I have done so in another article: A QuickStart Guide to Using Web Services in MDT / SCCM.

Evaluation #4: Subsections

Sample Rules File #5

[Settings]
Priority=Make, Default

[Default]

[Microsoft Corporation]
. . .
Subsection=MS-%Model%
. . .

[MS-Virtual PC]
. . .

Fresh off the Web Services evaluation, next ZTIGather is looking for a Subsection key. Following the syntax rules for INI files, each section can only contain one Subsection key. ZTIGather reads the value from this key, and if a matching section is found, processing of the current section is paused while the subsection is evaluated.

The subsection is evaluated exactly the same way any other section is evaluated; all 6 or 7 steps (remember the reveal tease?). If the section specified in your Subsection key itself has a Subsection key, processing of the subsection is paused while the sub-subsection is fully processed. I'll end the recursive nature of this here. Upon completion of the sub-subsection, processing of the subsection is completed, then processing of the current section is completed. It is pretty much as easy as that. Avoid circular references! 

In this example, we see that the Priority key uses an MDT Property for a section name, so if the current build is on Hyper-V, the make would be Microsoft Corporation and as a section with that name exists, it is processed. That section has a Subsection key, which has a syntax I have not yet gone over. I will address the translation ZTIGather does on specific types of keys further down, but for now, the Subsection value is evaluated and resolves (in our example) to "MS-Virtual PC". As a section matching that is found, that section is fully processed before ZTIGather returns to the "Microsoft Corporation" section, finishes processing that, then processes the "Default" section.

A fun note here. Any error in processing of the subsection will error out the entire ZTIGather process.

Back to the DefaultGateway Rule

At the start of this article in The Priority Key Flow section, we covered ZTIGather's evaluation of defined rule names from the Priority key, and how it would evaluate each specified rule as one of four types: a DefaultGateway type, a multi-value property name, a single-value property name, or a user defined name. If you haven't done so by now, have a look at the MDTDL's "DefaultGateway" page as I'll be referencing that example file. I mentioned previously that ZTIGather treated all sections exactly the same. This is true with this one exception. When processing a DefaultGateway section, ZTIGather enumerates all the locally discovered default gateway IPs, and looks for a matching key name. If found, ZTIGather attempts to process the section. That section will be evaluated exactly the same as all the others, with all 6 or 7 evaluation steps. There is no guarantee of the order the sections will be processed in as there is no guarantee of the order in which ZTIGather will obtain the list of default gateways from the local OS.

One other note needs to be made about this section. In the above Subsection description, we saw that the Subsection key was undergoing an evaluation to take it from what is shown in the example to "MS-Virtual PC". There is no such translation on keys within the DefaultGateway section: user named keys only.

Evaluation #5: Dynamic Package Section

Rules Section

[MySectionName]
. . .
SQLDefault=OtherSectionName
. . .

I have little idea why this evaluation exists, but it does and the section is evaluated for it right after the Subsection evaluation completes. Search the MDTDL for "StoredProcedure" and you'll find a reference to this key and a few links to discussions over application deployments. Following that rabbit down the hole does not reveal much about the existence of this section short of what you would have otherwise learned from mastering the MDT Database components of the Deployment Workbench. My guess is that this is a legacy section retained for backwards compatibility. That said, it is treated exactly the same as a Subsection key, despite it's name, so you can consider it a bonus Subsection opportunity!

Evaluation #6: Property Assignment

Rules Section

[MySectionName]
. . .
OSDComputerName=%SerialNumber%W8E
. . .

Back near the beginning of this article, in Section Processing Flow, I mentioned that all sections are processed exactly the same with one exception. This is the exception (this is the reveal. A bit of a let down I know). ZTIGather will always perform a property assignment evaluation of a section unless that section has previously been identified as using any data access service (i.e., a Database or Web Service section). In that event, no attempt at a property assignment occurs  The reason is fairly obvious in that a Translation Key,  matching SQL field names, Web Service function named parameters, and XML element names back to MDT properties has a the same syntax as a property assignment key, but accomplishes a completely different task. A property assignment key assigns the value of the key to the named property. If a Translation Key was accidentally processed as a property assignment key, using our example from the database section, we would wind up with AreaCode returning the string "TelephoneAreaCode". Not the desired outcome.

It is precisely for this reason (as well as an inability to rearrange the evaluation order of a section) that you cannot assign a value to a property in the same section where you are making a web service or database call. But as you now see, you can assign values in previously processed rules, then call a rule focused on web service or database calls.

In the Subsection and Dynamic Package evaluation steps I mentioned a type of translation ZTIGather does on key value assignments. The same is true for property assignments, and will be explained further on.

Evaluation #7: UserExit Code

Once again, ZTIGather looks for a UserExit key, and if found, processes the script associated with that key and again executes the "UserExit" function defined therein. As it is the same block of code, this is where the function's evaluation of the sWhen parameter comes in handy. The only flow difference is that this time the bSkip parameter is not evaluated upon exit of your function as the section has already been processed.

Subsection, Dynamic Package Section, and Property Assignment Value Translation

Rules Section

[MySectionName]
. . .
UserExit=MyCustomScript.vbs
PropertyOne=HardCodedValue
PropertyTwo=%OtherProperty%
PropertyThree=#MyFuncName(Param1, …)#
. . .

In the Subsection evaluation, Dynamic Package evaluation, and Property Assignment evaluation, ZTIGather performs an extra step with the processing of the value assigned to the key. Instead of simply using it as a hard-coded string or number, ZTIGather instead evaluates it for the possibility that you are referencing either another MDT property, a Windows environmental variable, or a return value from a custom script – along with a hard-coded value. As you can guess from many of the MDTDL samples, this is not an all-or-nothing evaluation; your value assignment can be a mix of all four elements.

Function MyFuncName(Param1, ...) 
     'Some Code 
     MyFuncName = "NewPropertyValue" 
End Function

Here is what defines each type:

  • Any text surrounded by "%" symbols is considered to be a reference to another MDT property
  • ZTIGather blindly considers that the value might contain an environmental variable.
  • Any text surrounded by "#" symbols is considered to be an evaluation block
  • Everything else is considered to be hard-coded text.

Lets walk through the flow. The first evaluation is to see if the value has two "%" symbols. If it does, the text between those two symbols is extracted and compared against the MDT property cache. If it turns out to be a multi-valued property name, the value associated with index position 001 is captured. If it turns out to be a single-valued property, that value is captured. If what was captured was not an empty string, then the "%" symbols and the text contained within is replaced with the captured value. Otherwise, nothing is replaced and the evaluation value still has the "%" reference.

Next we look to see if our value contains an Windows environmental variable. This isn't actually triggered by the presence of the "%" symbols (although that is what identifies an environmental variable and you need them in your reference); there is some black-box magic VBScript has that allows you to evaluate a string and automatically replace any environmental variables therein.

Lastly we look to see if it has two "#" symbols. If it does, we consider it to be an "evaluation block". I'll return to this in a little bit. If it does, the text between those two symbols is extracted and ZTIGather attempts to execute that function. If that function returns a value, then the "#" symbols and the text contained within is replaced with the returned value. Otherwise, nothing is replaced and the evaluation value still has the "#" reference.

At this point. ZTIGather considers whatever is left to be a hard-coded value and assigns it as-is to the property.

In these sections, ZTIGather makes this switch first. That way, your specified variables and code blocks are resolved before they are used by ZTIGather to discover subsections or property assignment.

Back to the UserExit Code

During section evaluation, we saw that we could specify a UserExit key and a custom script. That script included a required function called "UserExit" and said function took four parameters. When the UserExit kkey was discovered, ZTIGather read that script file in and made it part of the overall environment for the life of that section's processing. That means that just as the "UserExit" function within the script was called by ZTIGather, so can any other functions you write and include in the custom script. It is in this manner that when ZTIGather evaluates a Subsection,, dynamic package, or property assignment key, if you specify the name of your function (with any needed parameters) between the "#" symbols, ZTIGather can call that function and access it's return.

Things to remember:

  • If you are going to use an evaluation block for evaluation, you must include the UserExit key and the custom script.
  • ZTIGather will evaluate the UserExit key as previously described and it expects the "UserExit" function to be present. That function doesn't have to do anything at all other than return the Success variable, but it does need to be there. Ensure your custom script includes it.
  • The function cited in your evaluation block is only accessible during the life of that specific section's processing. Don't attempt to call a function that was part of the custom script loaded in a different section.

When To Use A Section and When Not To

MDT has been around for a while and has greatly evolved. There are features and abilities in the ZTIGather process that simply were not there in previous versions, but MDT has gone out of it's way to ensure as much backwards compatibility as possible. This means that there are methods still available within the ZTIGather process that may no longer be the best choice, but you can nevertheless still use.

Subsection / Dynamic Package Sections

Although still valuable, the inclusion of Web Service and Database capabilities have lessened your need for these sections. As many of the examples in the MDTDL show, the use of value translation means that ZTIGather can skip about to different rules dynamically depending upon the local environmental conditions. But if you become dependent upon that type of configuration, your rules file can quickly become bloated and convoluted – practically "spaghetti code". When you are looking to configure make / model specific settings, consider not using subsections and instead implement the MDT database with access either via a database section or a web services section. You can quickly see the complexity in an environment that has various models of laptops and desktops from multiple vendors and a single rule section is easier to maintain than dozens of make-model specific rules.

Property Assignment Sections

It is frustrating to many that ZTIGather performs property assignments next to last, and only if you haven't used a web service or database feature. When you use the older, established methods, this always means that you have an isolated rule section that does little more than property assignment and its processing order managed by it's placement within the Priority key. Although for most property configuration you will want to use a database or web service, there are certainly many good examples of needing a locally generated property value. A computer name is a good example, where that name may need to be generated based on serial number, OS version, location within the organization, etc. Take advantage of the UserExit key and function as a way to evaluate/set properties before and after the section is processed to either pre-configure those property values, or provide a catch-all value afterwards. Remember there is the added benefit of not being constrained by the no overwrite rule placed on many properties, while gaining the benefit of having full access to the same MDT utility object that ZTIGather has access to.

Database Sections

This section type is certainly not without it's configuration issues and security headaches, but once setup and going, it is your real workhorse. If you're using the MDT Database, you get all the GUI goodness of the Deployment Workbench for all sorts of configuration automation both with MDT stand-alone as well as integration into SCCM. Want to use other SQL data sources?  Add as much complexity on the back-end as you want, and it is still a simple rule to add to the file.

Web Services Sections

As one of the newer tools, this is a powerhouse and your new best friend. I don't even want to remember the hoops I had to jump through to get things such as full AD interaction from my WinPE launched scripts, but at the end of the day you could wind up with some bloated WinPE images and slow load times, all to do just the smallest task. Outside of WinPE it was the same, with lots of administrative work outside of the task sequence. Lets not even talk about security issues. A properly developed Web Service does away with all that. A bit more development work upfront, but once the process is in place, the world is your oyster. Set property values, manipulate AD groups, register information with SharePoint, send emails – If you can think it, you can do it, securely and with little disruption to the client side build process.

UserExit Sections

Less of a section, more of an overall gem to every other section type. This lets you extend processing indefinitely and really add a bit of AI to your builds.

Conclusion

You cannot obtain a true LTI or ZTI deployment without automation. Lots and lots of automation. Things have to "think" for themselves, evaluate their environment, and make decisions "on their own". This not only requires data, it requires you telling it how to think. But before you can tell it what to do, you must understand how to get there yourself. Many of my clients recognize that MDT and SCCM are powerful tools, but found some of the components necessary for true automation confusing, and the behemoth of a documentation library too vague on the how-to for things like the CustomSettings.ini rules file. My hope is that this flow review of how ZTIGather processes properties and user rules helps to demystify the process and helps reveal the true potential in your environment.

Update: 15 MAR 2013: Corrected Section flowchart

@JaseTWolfe

Share This Post

A senior architect with over 16 years of experience in desktop design, delivery and production management. 14 years of law firm-centric experience in developing, integrating, and implementing robust and full-featured desktop solutions, focused on solid Microsoft and Microsoft partner platforms crafted to deliver an optimal fit for the environment.

5 Comments

  1. Excellent article, Jase!

  2. Thank you.

  3. Thanks for this great article! It’s been a great resource as I help my team understand some of the magic behind our MDT solution.

    One thing I ran into this week is that the routines for handling web service calls don’t follow the ZTIGather rules for processing values. In particular, the value part of each line (after the equal sign) isn’t evaluated for variables. So, I had
    MDTWebServer=myserver.foo.local
    Webservice=http://%MDTWebServer%/MyWebService/foo
    but %MDTWebServer% wasn’t replaced with the server name. I know the variable was defined correctly because I also used it to define the built-in monitoring service, and it worked there. Digging into the MDT scripts, it looks like the code that calls the web service simply does a text read against the INI file for these sections, so there is no ZTIGather magic being applied. Not a huge issue, but I definitely wasted some time on that.

Leave a Reply