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!

Monday 19 November 2012

Using MDT Monitoring with Config Manager Task Sequences - PART 2

Hi,

Been a long while inbetween posts - had some study (MS Private Cloud) and been a bit busy with work. But - here is some additional information around using MDT Monitoring with Configuration Manager.
Part one works fine, until the machine is restarted, then the monitoring will effectively halt.
What I wanted to do, is instead of having an additional Task Sequence step after every reboot, have a task that will run on system startup. That way every reboot will restart the monitoring.
My ideal situation was as below :
  • use startnet.cmd in winpe to start the monitoring
  • use an async command in the unattend.xml to run schtasks to create a scheduled task
  • use task sequence step to clean up the scheduled task
However, I ran into all kinds of problems.
Firstly, no matter what I attempted I could not get the monitoring script to run (and function as expected) running from WinPE using startnet.cmd. The script would start, the logging would identify that a step had changed, but the webservice would not fire an event. Now, normally i would work around this and modify the monitoring script to just call the event service with the relevant parameters. BUT - I wanted this to be as much 'out of the box' as possible, no super tweaks or hacks to how the MDT monitoring works.
So instead, I remained with a TS step that runs initially to start the monitoring (As per part 1). This works like a charm.

The second issue I had was actually setting up a scheduled task.
I tried using setupcomplete.cmd - but what I didnt know was that this gets created with a commandline to run the OSD TS. So my change would continuously get overwritten. (But hey, at least I learnt something!)
Secondly i tried adding the relevant command to unattend.xml. However, this would never create the scheduled task. Unsure why exactly, and to be honest I didnt trouble myself too much over it. (PS If anyone has gotten this to work, certainly let me know via the comments!)
So, what else? Well I went with the tried and true TS step. I added an additional step after the Setup Windows and Config Mgr step in my task sequence, that would run the below script to set up the task :

Set objFSO = CreateObject("Scripting.FileSystemObject")
Set WshShell = CreateObject("Wscript.Shell")
if not objFSO.FolderExists("C:\Windows\Setup\Scripts") then
 objFSO.CreateFolder "C:\Windows\Setup\Scripts"
end if
path = objFSO.GetParentFolderName(Wscript.ScriptFullName)
objFSO.COpyFile path &"\*.*", "C:\Windows\Setup\Scripts\" , TRUE
strTaskName = "Monitor"
strCommandLine = "C:\Windows\Setup\Scripts\ZTIMonitor.wsf"
wshShell.Run "schtasks /create /tn " & strTaskName & " /tr " & strCommandLine & " /sc onstart /RU SYSTEM" , 1 ,TRUE

WshShell.Run "cmd /c " & """START wscript C:\Windows\Setup\Scripts\ZTIMonitor.wsf""" ,1 
 


I DO realise that I was a little over zealous with the script copying (really only need ZTIGather, ZTIUtility, ZTIDataAccess and the ZTIMonitor script created in part one) You can also add a line in there to re-enable the DaRT remote control executable as well, to fire up your remote control again.

This set up a scheduled task, on system startup to run the ZTIMonitor.

Elegant? Not at all.
Functional? Yeah, pretty much!

The last thing remaining is to remove the "Monitor" Scheduled task at the end of the TS. Another TS Step (run command line) will do this.

Just to spell it out further here, im not giving anyone a complete end to end solution here - its more showing you what I have done, and giving people some ideas that perhaps they might not have thought of on their own. Think of it as us collaborating on an idea :)

So in conclusion I got the MDT monitoring system to work with a Config Mgr task sequence, with 3 extra TS Steps. I'm happy about that. Now next plan, is to actually make it useful!
Future posts will describe my attempts to use this monitoring service to write events to a DB and monitor using a simple ASP.NET page.

Friday 2 November 2012

Using MDT Monitoring with Config Manager Task Sequences - PART 1

Wow, first post!

So, im not going to muck around with hellos, introductions etc etc. Lets get straight into it!

I've been pretty interested in MDT 2012s new monitoring feature, pretty useful feature straight out of the box. However, seems that this will only provide information on MDT Task Sequence steps. Great if you are solely using MDT for your deployments, not so useful if you are using MDT integrated with ConfigMgr, and your own custom ConfigMgr task sequences.
I wanted to find a way to monitor a Config Manager task sequence using the MDT monitoring web service, with no modifications whatsoever.

First of all, there is one big negative point with MDT monitoring, that is the fact that its not historical. It will tell you where your deployments are at at that point in time, but information around the past events isnt retained. In my opinion, this makes using it as your OSD monitoring solution a little flawed. Generally you will want historical info, the full picture of your OSDs as it were.

For this post I am going to go over how I got the MDT monitoring working for Config Manager task sequences. Future posts will describe how to use this process to get the info into your own monitoring solution.

So - Lets begin...

If you do not know how the MDT monitoring feature works, i would STRONGLY recommend you check out the MDT Monitoring Deep Dives by OSD guru Maik Koster.
Also check out MDT Monitoring Info
AND
Troubleshooting MDT Monitoring both by MDT Master Michael Niehaus.

Done your background reading? Great!

SCCM Task sequences are not aware of the MDT monitoring service, meaning that every step being run is not actually communicating to the web service. That makes sense. The challenge is, how to monitor a running task sequence and pass info back to MDT?

The easiest way (IMO -if you see something easier definitely let me know via the comments) that I could see to do this was to monitor the current task sequence step. Every time the TS step changes, fire an event to the web service.
I did this by monitoring the  _SMSTSNextInstructionPointer TS variable. This contains a pointer to the current step. When the value of this changes, I fire off an event. See below for the script.

 



So essentially what we are doing here is :
  • Creating a GUID (unique identifier required to track a job)
  • Specifying the MDT event service name (this might not be needed if you have this in your customsettings.ini)
  • Comparing the value of _SMSTSNextInstructionPointer to its last value. If its different, running the CreateEvent method from ZTIUtility.vbs
  • Looping FOREVER...(or at least until a reboot :) )

There are a couple of things to be aware of here..

At present, a Toolkit Package/Gather needs to be run ahead of the monitoring starting. I am working on a way to get this running from step 1, will update this when that work is complete)
Everytime a reboot occurs, the monitoring needs to be restarted. (Again, i'm trying to find a way to autostart this to make it a bit more resilient)

The instructions for getting this working are below:
  1. Save above script (place this in the Scripts folder of your MDT package. Im using the name "ZTIMonitor.wsf"
  2. Edit your existing task sequence - place a new run command line step below your Use Toolkit/ Gather steps.
  3. The command line to run is : cmd /c START %scriptroot%\ZTIMonitor.wsf
  4. Copy this step to run after every reboot your TS performs.
  5. Save your TS
  6. Run an OSD, and you should see your monitoring events appearing in MDT Deployment Workbench once the monitoring step is hit.

This works very well for my test lab - usual stuff applies here about testing this yourself, i'm not responsible for any data loss , yadda yadda yadda. The blog name isn't poorlycoded for nothing ;) Please drop me a line if you want more info or things arent working as expected.

Thanks for reading!