Thursday 7 March 2013

Adding Action to Task Sequence via ConfigMgr SDK

Hello again!

I've been doing more work with the ConfigMgr 2012 SP1 SDK and quietly bashing my head against my keyboard...This is probably just due to me not being a coding genius, instead a coding pleb. So, I wanted to figure out how to add an action to a Task Sequence - took a look at the SDK and the code example is as follows:

public IResultObject AddTaskSequenceActionCommandLine(
    WqlConnectionManager connection, 
    IResultObject taskSequence,
    string name, 
    string description)
{
    try
    {
        // Create the new step.
        IResultObject ro;

        ro = connection.CreateEmbeddedObjectInstance("SMS_TaskSequence_RunCommandLineAction");
        ro["CommandLine"].StringValue = @"cmd /c Echo Hello";

        ro["Name"].StringValue = name;
        ro["Description"].StringValue = description;
        ro["Enabled"].BooleanValue = true;
        ro["ContinueOnError"].BooleanValue = false;

        // Add the step to the task sequence.
        List array = taskSequence.GetArrayItems("Steps");

        array.Add(ro);

        taskSequence.SetArrayItems("Steps", array);

        return ro;
    }
    catch (SmsException e)
    {
        Console.WriteLine("Failed to add action: " + e.Message);
        throw;
    }
}




"Oh yeah - that looks pretty easy" I though to myself. But after a lo-o-o-ong time trying to get this working, I was hitting a brick wall. I could get the steps of the task sequence, I could add them to the list, and I could even set the steps to the updated array. Great! However this was not showing up in the Task Sequence. No exceptions were being raised, just nothing happening at all. It seemed like the changes just were not saving, and I guess this is actually what was happening.

So, in ConfigMgr SDK terms, a ConfigMGr Task Sequence (WMI Class: SMS_TaskSequence) and a ConfigMgr Task Sequence Package (WMI Class: SMS_TaskSequencePackage) are two separate items. Modifications to the SMS_TaskSequence object don't do anything UNLESS they are associated to the relevant SMS_TaskSequencePackage.
Now, this might be totally straightforward to you, but it took a bit of head scratching by me to get it. But, get it I did!

public IResultObject AddTaskSequenceActionCommandLine(
    WqlConnectionManager connection, 
    IResultObject taskSequence,
    string name, 
    string description)
{
    try
    {
        // Create the new step.
        IResultObject ro;

        ro = connection.CreateEmbeddedObjectInstance("SMS_TaskSequence_RunCommandLineAction");
        ro["CommandLine"].StringValue = @"cmd /c Echo Hello";

        ro["Name"].StringValue = name;
        ro["Description"].StringValue = description;
        ro["Enabled"].BooleanValue = true;
        ro["ContinueOnError"].BooleanValue = false;

        // Add the step to the task sequence.
        List array = taskSequence.GetArrayItems("Steps");

        array.Add(ro);

        taskSequence.SetArrayItems("Steps", array);

        //Get the relevant Task Sequence Package
        IResultObject tsPackage = connection.GetInstance("SMS_TaskSequencePackage.PackageID='" +
                 pkgID + "'");
        
                Dictionary inParams = new Dictionary();
                inParams.Add("TaskSequence", taskSequence);
                inParams.Add("TaskSequencePackage", tsPackage);

                // Associate the task sequence with the package. 
                IResultObject result = connection.ExecuteMethod("SMS_TaskSequencePackage", "SetSequence", inParams);

        return ro;
    }
    catch (SmsException e)
    {
        Console.WriteLine("Failed to add action: " + e.Message);
        throw;
    }
}




So, I just had to run the SetSequence method on the SMS_TaskSequencePackage class. This method takes parameters of the Task Sequence object, and the Task Sequence package object.Once this was done, the action added without any dramas. Huzzah!

I stress again, this is probably clearly explained somewhere, but pretty hard to find out, hence me sharing this. Hopefully it helps! Let me know in the comments if I've been daft and missed the article that explains this :)

Tuesday 5 March 2013

PreCaching Task Sequence on Client Machine

One of the most annoying things i've found about ConfigMgr is the inability to preload contents on the client - either by preloading the client cache, or any other method.
I have found a way to preload Task Sequence components, so you can run an OS deployment off a client machine, reducing network load and increasing the amount of machines you can potentially build at one time.
You will need TSENV2.EXE to do this, you can get this here - its the Advanced Task Sequence Environment tool - used to overwrite normally readonly TS Variables (those starting with_)

So the idea here is to override the TS variables pointing to network locations and redirect them to a local folder. The other part of this is ensuring your local content does not get removed by a format or wipe when the OS is applied.

Part 1 : Overriding content location

The content location is stored in the _SMSTSPackageID task sequence variable
To override this, use TSENV2 like so:
tsenv2.exe set _SMSTSPackageID =sNewLocation

For example:
tsenv2.exe set _SMSTSA0100012 = sC:\Cache\A0100012\

This points the package source locally. The lowercase s above is actually very important, and not a typo!

Part 2 : Protecting Cache folder

Obviously we want our cache folder to not be deleted or otherwise removed until the build has completed - there are a couple of ways of doing this.
Firstly you need to ensure your TS has no Format and Partition steps, as this will remove the cached packages.
1. Set the cache folder to be the Local State Store directory, this will ensure its protected
2. Add the cache folder to the protected folders list (this contains such folders as the Task Sequence local folder and the state store folder. This list is stored in the variable : _SMSTSProtectedPaths

You can choose the option that best suits you, I prefer the local state store method as its very easy (just set the OSDStateStore variable and you are done).
To use mthod 2, you will need a script that reads the _SMSTSProtectedPaths variable, and then uses TSENV2 to overwrite it with the existing content, plus the new folder.

Step 3 - Implementation
To use this method to build your machine locally, you need to do the following:
1. Copy the package content to the client machines (up to you how you want to do this, robocopy, usb, use sccm) As long as all the packages are in the same folder and clearly identified with the package ID it doesnt matter how they get there.
2. Create a script to override your TS variables to the new location. Do this by iterating through each _SMSTSblahblah variable in the TS Variables, then matching the package id to a cached subfolder in your cache location and finally overriding each variable.

I will try to get some sample code up if anyone is interested (drop me a comment). It's a very simple script - as mentioned above, just get your content to the computer, step through each _smsts variable, match it to existing content on the machine and override.
Hope this helps!
FYI - this was done on ConfigMgr 2007 , I havent tested to see if the same principles apply to ConfigMgr 2012.

Monday 4 March 2013

Configuration Manager 2012 SP1 SDK - Error trying to update boot image

Hi,
Long time, no post.

Expect a bunch more action on this blog, I'm working on a project that is based on System Center Orchestrator and ConfigMgr 2012 SP1 - as such, I'm writing a custom Orchestrator Integration Pack to contain a bunch of (what I think) are useful actions. Anyway, onto the post...

I have been trying to write an Orchestrator action to inject Drivers into a specified boot image. But hey, that's all detailed in the ConfigMgr SDK right? Well... kind of!

The SDK shows you how to import the driver to the package - However when you run the RefreshPkgSource method of the SMS_BootImagePackage class with the following:
bootImagePackage.ExecuteMethod("RefreshPkgSource",null);
An SMSException is raised, and in the SMS Provider logs you will notice an eror along the lines of: *~*~Null parameter object! ~*~*

Annoying!

So I checked the paramters of the RefreshPkgSource method here - It has a ContextID parameter defined as optional. I then checked the ContextID property of an instance of the SMS_BootImagePackage class, and saw a blank entry. Putting two and two together, it seems it is expecting something other than null to actually run the method.

So I created a Dictionary object to hold the blank parameter instead of specifying a null parameter and YOWZA - success!
Full code is as follows:

Configurations.WQLConnect wql = new Configurations.WQLConnect();
WqlConnectionManager connection = wql.Connect(Credentials.siteServer, Credentials.User, Credentials.Password);
            try
            {
                // Get the boot image package.
                IResultObject bootImagePackage = connection.GetInstance(@"SMS_BootImagePackage.packageId='" + request.Inputs["Boot Image Package ID"].AsString() + "'");

                // Get the driver.
                IResultObject driver = connection.GetInstance("SMS_Driver.CI_ID=" + request.Inputs["Driver CI"].AsString());

                // Get the drivers that are referenced by the package.
                List referencedDrivers = bootImagePackage.GetArrayItems("ReferencedDrivers");

                // Create and populate an embedded SMS_Driver_Details. This is added to the ReferencedDrivers array.
                IResultObject driverDetails = connection.CreateEmbeddedObjectInstance("SMS_Driver_Details");

                driverDetails["ID"].IntegerValue = request.Inputs["Driver CI"].AsInt32();
                driverDetails["SourcePath"].StringValue = driver["ContentSourcePath"].StringValue;

                // Add the driver details to the array.
                referencedDrivers.Add(driverDetails);

                // Add the array to the boot image package.
                bootImagePackage.SetArrayItems("ReferencedDrivers", referencedDrivers);
               
                // Commit the changes.
                
                bootImagePackage.Put();
                Dictionary inParams = new Dictionary();
                inParams.Add("ContextID", "");

                IResultObject result = bootImagePackage.ExecuteMethod("RefreshPkgSource", inParams);




Hope that helps someone, and saves you a little bit of time!