Tuesday 1 October 2013

PreCaching Task Sequence on Client Machine - Part 2

Hi,

If you have read through http://poorlycoded.blogspot.com.au/2013/03/precaching-task-sequence-on-client.html and decided you want to know more, well let me try and assist :)

The below script will read from a list of packages and override the content variables for those packages:
sPackageIDs = "ABC12345,ABC98765,ABC00000,ABC111111"
Set objEnv = CreateObject("Microsoft.SMS.TSEnvironment")
Set objFSO=CreateObject("Scripting.FileSystemObject")
Set objShell=CreateObject("Wscript.Shell")
Set objDrives=objFSO.Drives

cacheFolder = "\PathToCache"

path = objFSO.GetParentFolderName(WScript.ScriptFullName)


For Each objDrive In objDrives
 If objDrive.DriveType=2 And objDrive.DriveLetter<>"X" Then
  If objFSO.FolderExists(objDrive.DriveLetter & ":\Windows") Then 
   Drive=objDrive.DriveLetter & ":"
  End If 
 End If
Next 

strCache = Drive & cacheFolder
tsEnv2Path = objFSO.Buildpath(path,"tsenv2.exe")


pkgSplit = Split(sPackageIDS,",")

for each pkg in pkgSplit
strPKGVar = "_SMSTS" & pkg
    For Each variable In objEnv.GetVariables 
        if variable = strPKGVar then
            if objFSO.FolderExists(objFSO.BuildPath(strCache,pkg)) then
                objShell.Run(tsEnv2Path & " set " & variable & "=s" & strCache & "\" & pkg & "\",1,true)
        end if
    next

Next 

You can then put this into your task sequence and have those packages overriden by the local, cached         package.Obviously be careful you don't have a format step, otherwise this will all be in vain, and if you cache folder is in a non standard location, perhaps ensure its added to the Safe Folder variable by adding this line, or setting the OSDStateStore variable in your TS.

objEnv("OSDStateStorePath")=strCache


Hope this helps! PS i've knocked together the above script without testing in my lab - so obviously test this   first :)

Tuesday 27 August 2013

ConfigMgr 2007 - Unknown Computer Deployments 'cached'

Just had a bit of a problem in an old ConfigMgr 2007 environment with an old Operating System Deployment task sequence, advertised to Unknown Computers collection. Unknown computer support was going to be removed from this company.

This manifested itself in the following manner:
  • Old advertisement targeted to Unknown Computers was disabled a number of weeks ago
  • Unknown computers were still picking up this disabled advertisement and attempting to run it
The SMSPXE log would show an unknown computer booting, and being offered a DISABLED advertisement. Which was a source of confusion, to say the least. WDS service was restarted on one of the PSPs to determine if there was any kind of computer records being cached - this did not resolve the issue.

In the end, the fix for this was to force an update of the collection membership of the Unknown Computers collection. After this was forced, unknown computers would not pick up the old (disabled) advertisement, and everything was peachy.

This may be a strange issue isolated to one particular ConfigMgr instance, but thought i'd share in case this helps anyone.I'm assuming that the old advertisement assignment to the unknown computer objects had never been refreshed. However, i would have thought disabling the Advertisement would have gotten past this issue.

 If anyone knows the actual, for-real cause of this, sing out in the comments. 




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!