Share This Post

A QuickStart Guide to Using Web Services in MDT / SCCM

A baker doesn’t have the same job as an accountant who doesn’t have the same job as a painter: different tools, different working environment, different products. Whereas you wouldn’t dream of developing a working environment for one of those users and forcing the other two to use it, this often is the base premise for corporate desktops; a one-size-fits-all approach that doesn’t correctly fit anyone it is deployed to. Often the build dumps out a desktop that has every application the company has figuring that the target employee will simply use what they need and ignore the rest. Worse, how about an anemic build that requires a great deal of hands-on administrative “fixing” to finish the configuration for the desired user, or one which isn’t finished until after it is delivered to the user and they log on for the first time. The goal should be to have a fully automated build process that produces a desktop tailored specifically to the needs of the target user and that requires nothing more than a quick QA when the automation ends.

SCCM alone goes a long way in build automation, but still requires a great deal of back-end development and configuration to begin to realize a fully formed lite touch build (LTI). MDT, in my opinion, is a stronger deployment tool in that it has easily implemented features that help administrators take their build capabilities further. Thankfully integration of MDT into SCCM is a snap. But as-is out of the box, even MDT’s advanced features are often not enough to satisfy a diverse deployment need. Fortunately, the developers of MDT designed the product to be fully customizable and easily built upon. Although this article won’t cover the how-to’s on customizing MDT’s wizards, task sequences, foundational scripts or custom database enhancements, it does cover using one of the features that comes with MDT: Web Service. For the purposes of this article, the environment is an MDT 2012 Update 1 integrated SCCM 2012 SP1 implementation, but that does not limit the potential target environments.

For those not completely familiar with a web service (WS), the condensed and over simplified introduction is this: A WS is a middle man that simplifies obtaining information or performing a process on behalf of the client and returning some type of data to the client. As the name suggests, the WS is a web based process which is used (in this article) by having a source client make an HTML request to the WS server, and that server returning an XML response which can be used by the client. For instance, the build process may need to know what the correct display resolution is for the hardware it is building. The build process sends the make and model of the local hardware to the WS, the WS performs a lookup based on that information, the WS returns the resolution information to the client, and finally the build process uses that information to configure the local environment. Using that methodology, the build process doesn’t need to have been developed with all the configuration information locally and doesn’t require any modification when new hardware is introduced to the environment. This obviously simplifies the build process and reduces administrative time when making environmental changes. This is also a good solution when information is not known in advance. For instance, perhaps your build process needs to make adjustments based on who the target employee is; that information can be gathered at the beginning of the build and a WS used to validate the information or return configuration specifications.

Much of what is required to create a zero touch (ZTI) or LTI build process can be done without the use of a WS using MDT and its built-in ability to make SQL calls to the MDT database. As that database can also be expanded to include other information, the use of a WS might not immediately look to be of value. But there are a few known SQL configuration and security obstacles, and your fully automated build process may need to do more than just look up data. Perhaps you need to do things such as ensure a unique computer name, add the computer to a specific Active Directory (AD) security group(s), send email notifications at the end of a build, register a new computer in the the MDT database, etc. Here is where a WS comes in handy as you can easily extend your build to use non-MDT data sources, as well as perform actions outside of the local environment.

That said, implementing a WS in MDT isn’t completely straightforward. MDT provides the framework for WS calls, but neither provides a ready to use WS interface nor crystal clear documentation on how to implement and use one on your own. In this article I’ve condensed the how-to in the hopes that your implementation and usage is quick and fully understood.

Adding A Web Service

Before you can use a WS, you need a WS. As the name implies, to implement you need a web server. Because this is a web call, the choice of host operating systems and HTTP server is up to you, but for the purposes of this article I’m using a Windows Server 2012 with IIS 7 and .NET Framework 4.5. Your ultimate placement of the IIS server and it’s proximity to the primary data source you wish to use (e.g., the MDT Database) should reflect your network environment and deployment process, but for a lab environment, they can share the same host.

Using a Pre-Developed Deployment Web Service

If you are not familiar with how to create a WS, or you would rather implement a service quickly, using a pre-developed deployment WS is the way to go. In fact, even if you plan on creating your own, the following information is good to use to ensure you’ve correctly configured IIS.

  1. Obtain the WS application.  I personally like the one created by Maik Koster, and available from CodePlex.
  2. Install and configure the WS as instructed here. This is also a good configuration step if you are creating your own WS site as that will have the same access and security needs as the pre-developed service does. Keep in mind that this will require you to specify a domain user account which will need to have sufficient rights to all the processes and data sources you wish to access.
  3. If using the WS to communicate with your MDT database, be sure to download the MDTWebService_StoreProcedures_Complete.zip file as this WS uses custom stored procedures on the MDT database instead of making SQL queries directly.ensure you have configured the stored procedures prior to making any WS calls. There is not currently any documentation on how to do this on the CodePlex site, but the ZIP download mentioned in step 1 contains a SQL script and all you need to do is open that SQL script in SQL Management Studio and execute it. It can be executed multiple times as it will remove any of it’s existing stored procedures and then recreate them. You may find that the script generates an error, and if it does, it is most likely on the UpdateSettings creation routine (line 2141). This will occur if you are running the script against the MDT 2012 database as the script looks to have been created for the MDT 2010 database. To correct this, you can either fix the SQL script, or remove that particular function.
  4. Test the WS. As the documentation lists, you can test the WS by accessing any of the three URLs. If you can successfully use the WS via the HTML page, your MDT calls will also succeed.

Create Your Own Web Service

If you have any web development experience, this may be the better option for you. When you create your own service, you can customize it’s abilities to meet the exact needs of your environment. A pre-defined WS often exposes more data and update options than is desired and can be a security risk, so creating your own can ensure that only the information and processes your build environment should have access to are exposed.

Creation of WS is beyond the scope of this article, but creation of one can be done using free tools such as Visual Studio 2012 Express for Web. Once up an running, creating the site is as easy as creating a new .NET 3.5 ASP.NET Web Service Application (this can be later changed to 4.5). Once you’ve created the WS project, just review the “HelloWorld” function it created, ensure that you uncomment the System.Web.Script.Services.ScriptService line, and then you’re off and running, ready to create all the functions you need in your build environment.

Ensure that you have configured the IIS environment as described above in step 2, and that you test your functions similar to step 4 above.

Using a Web Service within MDT

There are three ways one can use a WS within MDT, each focusing on a particular need and each from a different area within the build task sequence.

Using a CustomSettings.ini file and a Gather Task

Before we go into the different ways to use this, I’m including a basic “syntax” overview of a WS section for general reference.

CustomSettings.ini Web Services Syntax Example

01   [Settings]
02   Priority=Init, WSSectionName
03   Properties=MySingleVar, MyArrayVar(*) 

04   [Init]
05   MySingleVar=value 

06   [WSSectionName]
07   WebService=http://server/page.asmx/FunctionName
08   Parameters=Param1,Param2,...
09   Method=value
10   MDTProperty=WSProperty
11   MyVar=XmlElementName

Explanation

  1. Mandatory.  Primary MDT defined section.
  2. Mandatory.  A MDT defined key which directs the processing of sections, in the order specified. The value is a comma separated list of user named sections.
  3. Mandatory.  A MDT defined key, although no values must be specified. Any non-MDT or SCCM intrinsic property you wish to use anywhere within the LTI/ZTI task sequence must be defined here first. Can be a single-valued property, or an multi-valued property when defined using the “(*)”.
  4. Optional. A user named section. The section can exist within the INI even if it is not specified within the Settings section Priority key.
  5. Optional. For a generic section, you can assign values to user defined properties. One assignment per line.
  6. Optional.  In this structure it is a user named web services section.  The section can exist within the INI even if it is not specified within the Settings section Priority key.
  7. Mandatory.  A MDT defined key which specifies the URL and function name of the web service to use.  The presence of this key is what classifies the section as a web services section.
  8. Optional. A MDT defined key used to specify any required web service function parameter name and associated value.  Each parameter included in the comma separated list must also be a MDT property and already have the desired value.  The property name must match the required function parameter name or additional translation keys must be included within the section.
  9. Optional. A MDT defined key used to instruct MDT in how to make the web request.  If omitted, MDT will use POST.  Valid values are GET, POST or REST.
  10. Optional. Any key within a web services section which is not MDT defined is considered a “translation” key.  A translation key allows MDT to look for a key name that matches a MDT property name, and use the discovered key value to identify the name of either the associated web service function parameter, or the return XML element node name.  This example key shows the translation between an MDT property name and a web services function name.
  11. Optional. Any key within a web services section which is not MDT defined is considered a “translation” key.  A translation key allows MDT to look for a key name that matches a MDT property name, and use the discovered key value to identify the name of either the associated web service function parameter, or the return XML element node name.  This example key shows the translation between an MDT property name and a returned XML element node name.

The MDT object that handles WS sections has some operational behaviors that you need to be aware of. When processing a WS section, MDT isn’t enumerating every key within the section like it does in some other section types. MDT makes specific queries into the WS section looking for specific keys. As mentioned, the WebService, Parameters and Method keys are used to configure the WS call, but the remaining keys are used to either translate property/parameter names, or process returned values. MDT does the translation by enumerating the Parameters key value list, looking for a key within the WS section whose name matches the parameter name. If found, the discovered WS Parameter name is used within the WS call, passing the value of the MDT property specified. Upon return, MDT does a little more work, enumerating every property within it’s cache looking to see if there is a matching key name in the WS section. If found, MDT uses the key value to identify the XML element via an XPath query.

Zero and Single Value Returns

Any time MDT processes the CustomSettings.ini file, it processes rules contained therein. This can be specifically triggered by creating a Gather task within the task sequence and specifying the “Gather local data and process rules” options. It is within the CustomSettings.ini file that we can define WebService look-ups and set run-time data. Below are a few examples of how this works.

CustomSetting.ini Sample

[Settings]
Priority=Default, GetComputerID
Properties=wsCID, MyOtherVar, MyUnusedVar

[Default]
MyOtherVar=Dx21

[GetComputerID]
WebService=http://server/page.asmx/GetCompID
Parameters=SerialNumber
wsCID=int

[UnusedWsSection]
WebService=http://server/page.asmx/DoSomething
Parameters=SerialNumber, MacAddress, MyOtherVar
SerialNumber=ComputerSerialNumber
MyOtherVar=CompanyName
MyUnusedVar=string

Web Service Function Code Behind

<WebMethod()> _ 
Public Function GetCompID(SerialNumber As String) As Integer
     '[Code to create DB objects and connect to database]

     objSQLCmd = New SqlCommand("SELECT TOP 1 ID FROM ComputerIdentity " _
          "WHERE SerialNumber=@P0", objSqlConn)
     objSqlCmd.Parameters.AddWithValue("@P0",SerialNumber)

     Try
          GetCompID = CType(objSQLCmd.ExecuteScalar,Integer)
     Catch ex as Exception
          GetCompID = 0
     End Try

     '[Code to close SQL objects and connections]
End Function

Quick Explanation

In this example, we configure ZTIGather to first process the Default section, then process theGetComputerID section. We also define a custom property, wsCID, which we will use to hold our return value.

Next, ZTIGather processes the GetComputerID section, calling the specified WS and function name, passing in the required parameters, then putting the return value into the wsCIDvariable.

The UnusedWsSection is there only to demonstrate other configurations of a WS section.

Lets take a little deeper look. The desired result is to get the MDT ID associated with the specific computer. Looking at the WS function, we know the function’s name, that it takes a single parameter (the computer’s serial number), and that it returns an integer value.

Within the CustomSettings.ini, we have created a section that defines the WS call, GetComputerID. The WebService key defines the URL and function name of the WS. As the function requires a parameter, we include the Parameters key and passed the parameter variable. Here are a few things to remember at this point:

  • The name of the MDT property (or multiple variables in a comma separated list) specified within the Parameters key has two roles. First, it is used as the literal name of the WS function parameter, and secondly the value of the MDT property is passed as the input assigned to that specific WS Parameter. To help clarify, refer back to the Code Behind and see that the function has a required named parameter. Although this isn’t the way MDT does it, the end result is a call to the WS function where the parameter list is passed not in the exact order the function lists the parameters, but rather where each parameter is named:Function(ParamName:=value) As referenced in the syntax sample, if all of the MDT Property names are a match for the WS Named Parameters then nothing more is needed other than specifying the MDT property within the Parameters key. But if the MDT property value you wish to pass is not contained in a matched name, you need to tell MDT how to translate the MDT property name to the WS parameter name. This is where you would add a new key where the key name is the MDT Property name, and the key value is the WS Named Parameter name. The UnusedWsSection section in the sample has an example of this.
  • Although this should be clear based on the previous notes, remember that you can’t use anything but a MDT Property the Parameters key: no hard coded text, numbers, environmental variables, script calls, etc.
  • Because MDT does not process any keys within the WS section as a property value assignment task, your specified property must already contain any needed value before MDT processes the WS Section. The ZTIGather routine will have already discovered and populate all the intrinsic properties (on your MDT deployment server, open the “Microsoft Deployment Toolkit Documentation Library” and search for “Property Definition” to get a full list of the properties intrinsic to MDT). It is up to you to have custom properties populated prior to the WS Section.In our example, SerialNumber is a MDT intrinsic property already defined for you. The UnusedWsSection section shows how to populate your custom property prior to the WS Section and thus make it’s value available.

With the WS function and parameters defined, MDT will then attempt to make the call to the web server. A partial look at the ZTIGather.log generated on the client would look like this:

------ Processing the [GETCOMPUTERID] section ------
Determining the INI file to use.
Using COMMAND LINE ARG: Ini file = CustomSettings.ini
Finished determining the INI file to use.
Using specified INI file = CustomSettings.ini
CHECKING the [GETCOMPUTERID] section
Property UserDomain is now = [Domain]
Property UserID is now [UserID]
<Message containing password has been suppressed >
Property WSCID is now 1
Obtained WSCID value from web service: int = 1

Notice that a UserDomain and UserID properties are populated. If you are using MDT only, these are defined within the bootstrap.ini file. If using SCCM with MDT integration, this will be the Network Access account and will be “injected” for you. If you are using this outside of that environment, you will need to populate those MDT properties on your own first. In either of the primary scenarios, this is the user account used to communicate with the web server. This is important because you don’t want the web server open to just anyone as that constitutes a security risk. This way, the WS function can only be triggered by the specified account.

Within the CustomSettings.ini, in the Settings section Properties key, we created a custom property, wsCID. Within theGetComputerID section, because we know the WS function is returning a value, we specified wsCID to be the recipient of the return value. Once the WS call completes and that property is populated, we can use it elsewhere as either a parameter for another WS function, a value to use within the build, or a decision making property within a task sequence condition. Here are a few important thing to remember:

  • Your variable must exist prior to accessing it. What that means is that if you neglect to define that variable within the Settings section Properties key, MDT won’t make it available for use later.
  • Within the ZTIGather process, MDT doesn’t overwrite properties with values. This means that if you specified a property to receive the value of the WS function and that property already have a value, the WS returned value is discarded. For intrinsic variables as discussed above, there are a few exceptions to that rule, and you can further manipulate those exceptions by editing the ZTIGather.xml file, as well as create other “intrinsic” properties – but that is beyond the scope of this article.

Lets focus on how MDT is going to assign the return. In our example, we know that the WS function will return an integer value. If we look at the raw XML that is returned by our function, it looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<int xmlns="http://dx21.llc/WS">1</int>

The retuned XML has a single element named “int” containing a single value. Within the GetComputerID WS section, any key which is not the WebService, Parameters, or Method key must be a MDT property name for translation or assignment. As our WS return’s single element is named “int”, that is then considered by MDT to be the name of the XML element and is used by MDT for a reverse translation: MDT enumerates every property in it’s cache, looking within the WS section for a key name that matches the property name and where that key’s value is the name of the XML element. If a match is discovered, that MDT property is assigned the WS function return value, providing the MDT property does not already have a value. The result in this scenario is that MDT assigns the wsCID Property the value of the int value ( 1 ).

Your most encountered return types will be “int” and “string”. That said, it is possible to have other single-value data types returned and you need to know the name of the XML element so you can specify that for your return property. Always test your WS function call from the browser first so you can see the return result.

The above demonstrated a single value return. Lets say though, that instead of calling a WS function that returned a value, we called a function that did something and either didn’t return a value, or returned a value we didn’t care about. Pretend we called a function that just registered the computer’s serial number in the MDT database if that serial number wasn’t found, and let say that a failure of that registration was not sufficient grounds to terminate the task sequence. In that scenario, the above process of defining a WS section, assigning the Parameters key values, and calling the section via the Settings section Priority key would still apply. But we would omit any MDT property named key with a value of the WS function return element name.

Array Returns

Now that we have covered a single value return, lets look at array returns. Arrays, aka multi-valued properties, are common within MDT and SCCM. Open the ZTIGather.log on a client and in the section where it is gathering intrinsic properties, you will run into log lines that look like this:

Property IPAddress001 is now = 192.168.1.153
Property IPAddress002 is now = fe80::98f7:d302:5804:1b32
Property MacAddress001 is now 00;15:5F:02:45:1F

Oversimplified and explained further in subsequent sections, these property types are used when the property can have multiple values, such as IP Addresses or a listing of applications to install. MDT recognizes the base property name (e.g.,IPAddress) and stores values for that property within an index position starting at 001 and incrementing to 999. When accessing a multi-valued property, if you only specify it’s base name, MDT will automatically return whatever value is stored in index 001. If you specify an exact index position, MDT will return that instead (e.g., IPAddress002).

So lets take a look at how we can return arrays of data for assignment into a multi-valued property.

CustomSetting.ini Sample

[Settings]
Priority=Default, GetArrayDemo
Properties=arrayDemoVar(*)

[Default]

[GetComputerID]
WebService=http://server/page.asmx/GetArrayDemo
arrayDemoVar=string

Web Service Function Code Behind

<WebMethod()> _ 
Public Function GetArrayDemo() As String()
     Dim aRetVal() As String = {"One","Two","Three","Four","Five"}
     Return aRetVal
End Function

Quick Explanation

In this example, we configure ZTIGather to first process the Default section, then process theGetArrayDemo section. We also define a custom list property, arrayDemoVar, which we will use to hold our return values.

Next, ZTIGather processes the GetArrayDemo section, calling the specified WS and function name, not passing in any parameters for this example, then putting the return values into thearrayDemoVar property.

This demonstration builds on what we have seen thus far, so I won’t cover the flow of events within the CustomSettings.ini file with these exceptions:

  • Notice that under the Settings section Properties key, a custom multi-valued property has been created by specifying the root property name, followed by “(*)”.
  • Although we know that our WS function will return an array of values, we only specify the root property name and the single return type “string” which is the XML element name of each child element returned under the root element, as shown below. If you were to use “ArrayOfString” as the value for the MDT property key, MDT wouldn’t make the match and would consequently not store the return values.

Lets look at the XML return of the WS function:

<?xml version="1.0" encoding="UTF-8"?>
<ArrayOfString xmlns="http://dx21.llc/WS" xmlns:xsd="[url]" xmlns:xsi="[url]">
     <string>One</string>
     <string>Two</string>
     <string>Three</string>
     <string>Four</string>
     <string>Five</string>
</ArrayOfString>

Now lets look at part of the ZTIGather.log:

------ Processing the [GETARRAYDEMO] section ------
Determining the INI file to use.
Using COMMAND LINE ARG: Ini file = CustomSettings.ini
Finished determining the INI file to use.
Using specified INI file = CustomSettings.ini
CHECKING the [GETARRAYDEMO] section
No parameters to include in the web service call were specified
Property UserDomain is now = [Domain]
Property UserID is now [UserID]
<Message containing password has been suppressed >
[...]
Property ARRAYDEMO001 is now = One
Property ARRAYDEMO002 is now = Two
Property ARRAYDEMO003 is now = Three
Property ARRAYDEMO004 is now = Four
Property ARRAYDEMO005 is now = Five
Added ARRAYDEMO value from web service: string = five

I’ve truncated part of the log for clarity. Essentially, MDT will enumerate each child string element, adding a new index position to the base property with the associated value. At the end of the day, your multi-valued property contains five values. If you access it as just arrayDemo, then the value of arrayDemo001 is returned, otherwise, you can enumerate each index position and process values accordingly.

Processing Multiple Properties

If you’ve used a SQL section within the CustomSettings.ini, then you know that MDT will populate multiple properties with just one step. So for instance, if you have created a Make and Model record within the MDT database, and populated the details of that record with such things as the horizontal and vertical resolution, when you include a “MMSetting” section (automatically created by the MDT deployment workbench when you configure database rules) within the CustomSettings.ini, MDT goes out, makes the SQL call, and populates the appropriate intrinsic properties with returned information. One call, multiple returns.

We can do the same thing with a WS call. But unlike before where we returned either a single value or an array, multiple properties are returned as well formed XML and then MDT processes them. Lets see how this works.

CustomSetting.ini Sample

[Settings]
Priority=Default, MultiReturn
Properties=elementOne,elementTwo,elementThree

[Default]

[MultiReturn]
WebService=http://server/page.asmx/GetXMLDemo

Web Service Function Code Behind

<WebMethod()> _ 
Public Function GetArrayDemo() As String()
      Dim objXML as new XmlDocument
     objXML.LoadXml("<rootNode><elementOne>One</elementOne>" & _
          "<lementTwo>Two</elementTwo>" & _
          "<elementThree>Three</elementThree>"  & _
          "<OsdComputerName>ADF154FF</OsdComputerName>")

     Return objXML
End Function

Quick Explanation

In this example, we configure ZTIGather to first process the Default section, then process the MultiReturn section. We also define a few custom properties which we will use to hold our return values.

Next, ZTIGather processes the MultiReturn section, calling the specified WS and function name, not passing in any parameters for this example. We have not specified any return properties.

Thus far, nothing should look too out of the ordinary with the exception of not specifying any properties to hold the return data. Lets first look at the returned XML:

<?xml version="1.0" encoding="UTF-8"?>
<rootNode>
     <elementOne>One</elementOne>
     <elementTwo>Two</elementTwo>
     <elementThree>Three</elementThree>
     <OsdComputerName>ADF154FF</OsdComputerName>
</rootNode>

This return is a little different than before. In previous WS functions, we were returning specific data types, be those integers, strings or arrays. In this example we are returning our own generated XML. Therefore, some of the markup that our ASP.NET WS was automatically adding based on other settings within the application’s code (not shown) are not added to this output.

Here is the relevant section within the ZTIGather.log:

------ Processing the [MULTIRETURN] section ------
Determining the INI file to use.
Using COMMAND LINE ARG: Ini file = CustomSettings.ini
Finished determining the INI file to use.
Using specified INI file = CustomSettings.ini
CHECKING the [MULTIRETURN] section
No parameters to include in the web service call were specified
Property UserDomain is now = [Domain]
Property UserID is now [UserID]
<Message containing password has been suppressed >
Property ELEMENTONE is now = One
Obtained ELEMENTONE value from web service: ELEMENTONE = One
Property ELEMENTTWO is now = Two
Obtained ELEMENTONE value from web service: ELEMENTTWO = Two
Property ELEMENTTHREE is now = Three
Obtained ELEMENTTHREE value from web service: ELEMENTTHREE = Three
Property OSDCOMPUTERNAME is now = ADF154FF
Obtained OSDCOMPUTERNAME value from web service: OSDCOMPUTERNAME = ADF154FF

Here we have a custom XML return that is providing values for four properties. The first three are our custom properties, and in keeping with the rules of MDT, we defined those properties first within the Settings section Properties key so they are available to us. When the ZTIGather routine evaluated the XML output, it discovered those custom properties as well as intrinsic properties, matched them automatically to like-named XML elements, and assigns the appropriate value. Again, take care that the property does not already have a value as MDT will not overwrite it.

If you are generating your own WS function which returns your custom XML data, remember that MDT isn’t going to evaluate any element attribute, so skip including it.

Outside of the Gather Task, but Still Using CustomSettings.ini

Although the simplicity of environmental discovery and build configuration through nothing other than the CustomSettings.ini and the Gather task is appealing and can really resolve the bulk of the build configuration needs, it can get convoluted fast if you have a complex environment, and is still no where near as powerful as the task sequence itself. Your build environment may also have a need to look up information or make changes multiple times during the build process as the environment changes. In any event, MDT WS processing is available to you throughout the life of your build via custom scripts you develop. In this section we’ll look at how we can leverage the WS processing features from within our own script, while still utilizing the CustomSettings.ini to host WS configuration information.

When building a task sequence, you undoubtedly reuse the same action over and over. More times than not, these action items do nothing more than trigger a single script with different parameters. This is efficient in that a single action engine can be used over and over without change, reducing the administrative development effort and decreasing the complexity of the environment. Combining a custom script with a CustomSettings.ini filled with WebService sections is right in line with the streamlined approach in that a single script that accepts parameters at run time can perform all the leg work of connecting to the WS, executing the desired function, and processing the outcome without any need for change when you find you need additional WS functions; you just change add a new WebService section to the CustomSettings.ini and you’re done.

In the CustomSettings.ini Syntax Sample above, we touched on the Settings section Priority key. This key’s value drives the ZTIGather process by telling that script which sections to process and in which order. If you haven’t specified a section name in that setting, it is never evaluated. To that, you are free to add additional sections to the CustomSettings.ini which are not evaluated by the ZTIGather script but can be access by MDT later via your custom script. To go even further, you’re not limited to a single CustomSettings.ini file: you can create a completely separate INI file named anything you want and which is dedicated to WS sections and specify that file when using your custom script.

Environmental Setup

Your custom script should conform to the MDT methodology and integrate with MDT’s services and structure. Although beyond the scope of this article to cover how to create custom MDT scripts, those unfamiliar should reference the “Microsoft Deployment Toolkit Documentation Library” on their MDT deployment server and search for “Creating Custom Scripts for MDT” to get started.

Your custom MDT script, in WSF format, should reference the ZTIUtility.vbs and ZTIDataAccess.vbs scripts. If you’re using this outside of SCCM and/or MDT, ensure you bring those scripts along and keep them in the same root directory as your custom script.

You also do not need to completely reinvent the wheel. My scriptlet examples below are all very simplified to demonstrate the basic principal, and all formatted in both syntax and usage to match the excellent script provided by Maik Koster on CodePlex. I encourage you to download the “ZTI_ExecuteWebservice_v3.zip” offering as that ready-for-production script not only accomplishes this task as-is, it also provides a great educational reference. Within the examples, I’ll refer to this as the “MK Script”.

Zero and Single Value Returns

Here we will look at processing a single valued return, similar to the way the ZTIGather script would process it during the Gather task.

Task Sequence Command Line (Single Line)

cscript ZTI_WS.wsf /wsIniFile:"[Path]WSLibrary.ini" /wsSection:GetComputerID /wsSelectionNamespace:http://Dx21.llc/WS /wsResultName:int /wsProperty:MDTID

WSLibrary.ini Sample

[GetComputerID]
WebService=http://server/page.asmx/GetCompID
Parameters=SerialNumber

Scriptlet

01 Dim oWebService: Set oWebService = New WebService
02 oWebService.iniFile = oEnvironment.Item("wsIniFile")
03 oWebService.SectionName = oEnvironment.Item("wsSection")
04
05 Dim oXML: Set oXML = oWebService.Query
06
07 If oXML Is Nothing Then Exit Function
08
09 oXML.setProperty "SelectionNamespaces", _
10      "xmlns:mk='" & oEnvironment.Item("wsSelectionNamespace") & "'"
11 Dim nodeValue
12 Set nodeValue = oXML.SelectSingleNode("mk:" & oEnvironment.Item("wsResultName"))
13
14 If nodeValue Is Nothing Then Exit Function
15
16 oEnvironment.Item(oEnvironment.Item("wsProperty")) = nodeValue.Text

Web Service Function Code Behind

<WebMethod()> _ 
Public Function GetCompID(SerialNumber As String) As Integer 
     '[Code to create DB objects and connect to database]

     objSQLCmd = New SqlCommand("SELECT TOP 1 ID FROM ComputerIdentity " & _
          "WHERE SerialNumber=@P0", objSqlConn)
     objSqlCmd.Parameters.AddWithValue("@P0",SerialNumber)

     Try
          GetCompID = CType(objSQLCmd.ExecuteScalar,Integer)
     Catch ex as Exception
         GetCompID = 0
     End Try

     '[Code to close SQL objects and connections]
End Function

In this example, the task sequence command line calls our WS script and passes to it the configuration information the multi-use WS script requires for processing. Line 01 creates the MDT WebService object (defined within the ZTIDataAccess.vbs script) which is what is going to do most of the heavy lifting. When used in this manner, line 02 instructs the WS object as to which “library” INI to use, and line 03 points the WS object to the correct WebService section. This could have been our standard CustomSettings.ini as well (in the MK Script, this is an optional parameter and if not used, the MK Script will attempt to use the MDT RulesFile property, which works well in a pure MDT environment, but less so in an SCCM or custom environment). As the WebService section will already supply the URL plus function name, parameters, method, and any MDT to WS parameter remapping, we’re ready to make the query on line 05. If successful, we have an XML object, if not, we have nothing and therefore need to close up (line 07). Lets take a quick look at the XML we subsequently obtained from the call:

<?xml version="1.0" encoding="UTF-8"?>
<int xmlns="http://dx21.llc/WS">1</int>

Lines 09 to 12 are interesting. The MK Script utilizes the passed namespace (in the case of this XML, the namespace is “http://dx21.llc/WS“) and a passed result name (the name of the XML element to evaluate, in this case the name is “int”) to locate the value. It creates an abbreviation (“mk”) to the namespace in order to make the XPath query, but on it’s own, the script doesn’t know what the namespace is. Therefore you must pass in the namespace, which is handy if you are making WS calls to various sites (as those sites may all return different namespaces). If the XPath query finds the node (“mk:int” in this example) then it will read the value into the specified MDT property (line 16). Obviously you must know the namespace and the XML element name in advance. This method has an advantage of being able to access any node within the returned XML regardless of depth, but one could also make general assumptions regarding the single / no value XML return and simply access the document element directly:

Set nodeValue = oXML.documentElement

When you use the ZTIGather routine, MDT adhered to a “no overwrite” rule for properties. That restriction is only administrative to the ZTIGather process and you are under no such limitation when you create your own custom scripts. The MK Script incorporates that functionality by allowing you to specify the overwrite action (the default of the MK Script is to allow overwrites).

The example above shows processing of a single valued return. In the event that you were placing a WS call that does not return a value or you don’t care what the value is, ensure the script doesn’t attempt to process a return value if you haven’t specified a MDT property.

Array Returns

Just as before, we can process mutli-valued properties in a similar manner to single valued returns, although there are a few differences in processing the return. In the example data below (again, modeled after the MK Script) we are calling the custom script with configuration parameters, and letting that script do the work for us.

Task Sequence Command Line (Single Line)

cscript ZTI_WS.wsf /wsIniFile:"[Path]WSLibrary.ini" /wsSection:GetArrayDemo /wsProperty:MyArrayVar

WSLibrary.ini Sample

[GetArrayDemo]
WebService=http://server/page.asmx/GetArrayDemo

Scriptlet

01 Dim oWebService: Set oWebService = New WebService 
02 oWebService.iniFile = oEnvironment.Item("wsIniFile") 
03 oWebService.SectionName = oEnvironment.Item("wsSection") 
04  
05 Dim oXML: Set oXML = oWebService.Query 
06  
07 If oXML Is Nothing Then Exit Function 
08  
09 Dim nodeValue: Set nodeValue = oXML.documentElement 
10  
11 If nodeValue Is Nothing Then Exit Function 
12  
13 Dim dArrayVals: Set dArrayVals = CreateObject("Scripting.Dictionary") 
14 Dim objNode, intCount 
15  
16 intCount = 0  
17 For Each objNode In nodeValue.childNodes 
18      If objNode.text <> "" Then 
19           dArrayVals.Add intCount, objNode.text 
20      intCount = intCount + 1 
21      End If 
22 Next 
23  
24 oEnvironment.ListItem(oEnvironment.Item("wsProperty")) = dArrayVals.Items

Web Service Function Code Behind

&lgt;WebMethod()> _
Public Function GetArrayDemo() As String()
     Dim aRetVal() As String = {"One","Two","Three","Four","Five"}
     Return aRetVal
End Function

Notice that in this script example, instead of performing an XPath query, I simply attached to the document element. Lets take a quick look at the returned XML:

<?xml version="1.0" encoding="UTF-8"?>
<ArrayOfString xmlns="http://dx21.llc/WS" xmlns:xsd="[url]" xmlns:xsi="[url]">
     <string>One</string>
     <string>Two</string>
     <string>Three</string>
     <string>Four</string>
     <string>Five</string>
</ArrayOfString>

I replaced the XPath query for a direct attachment to the document element because of the additional namespace entries returned by the WS call. This adds a complication that is not impossible to work with in a similar fashion to the MK Script, but for the sake of simplicity I’ve opted for the easy route. For the most part, our script is the same up to line 09, which is where we then connect to the document element for eventual enumeration of the child values.

Lines 13 to 22 enumerate the child elements and store their value in a dictionary object. Line 24 then stores the returned data into a multi-valued property. This is where a little extra information may come in handy. Here is the output from the custom script log when you assign the multi-valued property:

Property MyArrayVar001 is now = One
Property MyArrayVar002 is now = Two
Property MyArrayVar003 is now = Three
Property MyArrayVar004 is now = Four
Property MyArrayVar005 is now = Five

As you may have already guess, MDT doesn’t really use true arrays for properties. The ZTIUtility.vbs script creates a worker class called Environment that handles (amongst other things) setting and retrieving properties in the MDT-specific XML storage file and the SMSOSD environmental objects. All properties in both environments are single valued properties, but as arrays are often better suited for some storage tasks, MDT does some slight of hand to store and convert a set of numerically incremented single-valued properties into what appears to be an array. When you interact with the MDT Environment object, you treat multi-valued properties as arrays, but MDT is just converting your array back and forth to single valued properties. There is nothing really stopping you from enumerating each of the returned child elements and assigning the desired property plus an added numeric identifier the individual result; MDT will still recognize it as a multi-valued property in other areas of your build. If you do opt to assign values that way, ensure that you also set one increment above the last as a blank value so MDT will know where the multi-value property ends.

Processing Multiple Properties

The last section we will look at is the processing of multiple properties. Here is our demo data:

Task Sequence Command Line (Single Line)

cscript ZTI_WS.wsf /wsIniFile:"[Path]WSLibrary.ini" /wsSection:GetXMLDemo

WSLibrary.ini Sample

[GetXMLDemo]
WebService=http://server/page.asmx/GetXMLDemo

Scriptlet

01 Dim oWebService: Set oWebService = New WebService
02 oWebService.iniFile = oEnvironment.Item("wsIniFile")
03 oWebService.SectionName = oEnvironment.Item("wsSection")
04 
05 Dim oXML: Set oXML = oWebService.Query
06
07 If oXML Is Nothing Then Exit Function
08
09 Dim nodeValue: Set nodeValue = oXML.documentElement
10
11 If nodeValue Is Nothing Then Exit Function
12
13 Dim objNode
14 For Each objNode In nodeValue.ChildNodes
15   oEnvironment.Item(objNode.nodeName) = objNode.Text
16 Next

Web Service Function Code Behind

<WebMethod()> _ 
Public Function GetArrayDemo() As String()
     Dim objXML as new XmlDocument
     objXML.LoadXml("<rootNode><elementOne>One</elementOne>" & _
          "<elementTwo>Two</elementTwo>" & _
          "<elementThree>Three</elementThree>" & _
          "<OsdComputerName>ADF154FF</OsdComputerName>")
     Return objXML
End Function

And quickly the XML output:

<?xml version="1.0" encoding="UTF-8"?>
<rootNode>
    <elementOne>One</elementOne>
    <elementTwo>Two</elementTwo>
    <elementThree>Three</elementThree>
    <OsdComputerName>ADF154FF</OsdComputerName>
</rootNode&gt

Here the only real change is when we get to line 15. At this point, all we need to do is enumerate each of the child elements off the root, use the element’s name as the property name and assign the elements value to that property.  Remember that when you let MDT manage this via the ZTIGather routine, MDT would perform translation lookups against specified INI.  Although you’re still using that INI file, you are not having MDT process the result which means that there is no MDT property to XML element name translation automatically being done.  You will need to accomplish that within your task yourself.  You can do this in the same manner MDT does it, by using the TranslateToColumnID function exposed in the WebService MDT object (Line 01).  This function takes the name of the MDT property, queries for that within the WS section specified (line 03), and if a key is found, returns that key’s value.  As this current example needs to translate a XML element name to an MDT property, you can again do that similar to the way MDT does it (refer to the ZTIGather.wsf script, theQueryWebServices function).

Skipping the CustomSettings.ini

Up until now, we have explored using a multi-purpose script designed to process known types of WS return data from configurable sources. Although this will most likely fulfill 99% of the deployment needs, there may be situations when you need to do more than process one result at a time, process unconventional data, or perhaps make use of these services outside of MDT/SCCM. It is in those situations, development of a single-purposed script may be the answer.

The previous script examples along with the MK Script should provide enough examples of using the WebService object and programmatically dealing with the return XML, so I will not include additional samples. We used the MDT WebService object in those scripts calling the IniFile and SectionName properties, which worked in conjunction to find and load an INI file, then load in all the needed information from the specified section. This is a great shortcut, but you can make use of other WebService object properties directly to skip the external file lookup. Here is a quick run-down of the properties available within this object when not using an INI library file:

  • .WebService
    Just pass in the URL and function name in the same format as was in the INI file
  • .Parameters
    Use this to set or clear the WS parameters. Pass in a comma separated list of parameter names to set, or nothing to clear.
  • .Method
    Use this to set the HTTP method to use: GET, POST or REST as the input parameters. POST is already the default.
  • .Quiet
    Set this Boolean value to switch between a process that throws an error into the logs (when set to False) versus just logging the error as informational (when set to True).
  • .Query
    Returns a fully formed XML document if the call was successful, nothing if there was an error.

Considerations and Conclusion

As you can see, there is a lot of flexibility with WS in MDT, whether you make use of the ZTIGather and CustomSettings.INI process, or script your own. But when you leave the comforts of the ZTIGather routine, you do lose some of the safety features such as property overwrite protection. When you skip using an INI file in your custom scripts, you lose the ability for MDT to convert MDT properties to WS Parameter if they differ. Neither of these things are insurmountable, but do introduce issues that might be hard to track down or require a lot of additional scripting to resolve.

In any event, the use of web services greatly expands the capabilities of your build process and can even improve the efficiency and speed of the build. Remember all the hoops you had to jump through and the bloat in your WinPE environment if you wanted your that environment to access and act upon your domain services? Web Services removes the need for this, which can reduce the size of your WinPE environment resulting in quicker load times and thus faster builds. Web Services can also speed your build by off-loading some processing tasks to beefy servers, as well as reduce development time by allowing administrators to distribute processes to other departments and using the resultant web service as a black box within the build.

All in all, setup may seem a little daunting, but the inclusion of this service is well worth it.

@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.

Leave a Reply