This blog post was created by Michael Schmidt.
At Microsoft, we have the unique challenge of having to build a OSD experience that includes providing a great user experience prior to deployment of a new OS. In our case, we had the challenge of figuring out how we could do this considering that task sequence runs in Session0 and many of our users would never get this pretty user experience.
In the interest of helping others who have this same goal - bettering the user experience of OSD – we wanted to cover launching an interactive process from the Task Sequence (TS). No binaries are provided; this post only journals a high-level overview of creating a C++ binary to launch an interactive process.
A similar idea with source is available from The Deployment Guys.
Assuming the user interface/wizard is pre-built in the form of a standard binary (.exe), a launcher is necessary. Shown below is an example of the launcher concept, and how it might be used.
Since processes launched under WinPE are visible, this launcher provides value only when run from an existing OS. Two of the primary modes for operating a TS are:
With a custom launcher, a UI could be spawned from any place in your Task Sequence. It could be used to:
Another point worth mentioning is that a native launcher has no dependency on the .NET Framework. All API calls covered will run on Windows XP and higher. Building a launcher after this manner grants deployment flexibility, in relation to target OS and required runtimes.
When running a Task Sequence within an existing Operating System (OS), all TS work is performed in SYSTEM context. As a result, all user interface (UI) wizardry you might inject wouldn’t be visible to the user (Windows Vista+). To quickly cover this (emphasis added):
Impact of Session 0 Isolation on Services and Drivers in Windows Vista “The Microsoft Windows Vista™ operating system mitigates this security risk by isolating services in Session 0 and making Session 0 noninteractive. In Windows Vista, only system processes and services run in Session 0. The first user logs on to Session 1, and subsequent users log on to subsequent sessions. This means that services never run in the same session as users’ applications and are therefore protected from attacks that originate in application code.”
Take note of the following points:
Our goal is to launch a process from SYSTEM context into a session where our user interacts. Since TS execution is performed in Session 0 (non-interactive), we need to locate an alternative, interactive session which the user is running under. How can one tell which Session ID the user is operating under? One useful API to determine this might be:
In most cases, WTSGetActiveConsoleSessionID is sufficient to retrieve the working session. Unfortunately, while this session provides the physical console session ID, problems arise when multiple users are logged in (or when our target user is connected over Remote Desktop/Terminal Services). A more appropriate function for our purpose is:
Now, only one TS can execute at a time. When the TS launches, a process is spawned by the TS to show progress; this process is “tsprogressui.exe”. OSD opens this process from SYSTEM context into the active user’s session, which is precisely what we’re attempting to do. Thus, in our launcher we simply search for the OSD-spawned “tsprogressui.exe” process. By searching for this process and calling ProcessIdToSessionId against it, we can determine the session ID our UI should spawn in.
Searching processes can be accomplished through the following API calls:
An example of this is provided by MSDN; using this example, we can extract the session ID by:
Now for the tricky part. I defer source examples for this next section to the wide world of the web, but will overview on the idea. Most important is the CreateProcessAsUser API. Using this function, it becomes possible to launch your UI from the Task Sequence in SYSTEM context, into the user’s session. When this is accomplished, your UI will be visible/interactive.
The difficulty in making this call is the preparation. Numerous parameters must be created and pre-populated prior to making the call, and this is where the trouble lies.
An Access Token, representing the user we wish to execute under, is required for CreateProcessAsUser. Before launching a process under the user’s session, this token must be created/duplicated. To perform this, we extract and duplicate an Access Token from the existing user’s Winlogon session.
Once a handle to Winlogon is obtained, an Access Token can then be extracted. First, a template containing the privileges we require is necessary (LUID type); this will be used when creating/duplicating our Access Token (TOKEN_PRIVILEGES object).
While many other parameters are required, they’re much simpler to generate. Creating the Access Token is the tricky part; MSDN documentation and other code samples suitably cover the rest. However, when launching from a TS you’ll likely need to wait for the process to end, and want to know the resulting exit code. Launch, wait, and process the return code in this order:
This is all it takes… a little bit of code and a little bit of work and you can give a “new” face to OSD like never before thought of. To wrap up the order presented for creating a launcher:
A few small tips to consider when developing, testing, or troubleshooting.
Working between SYSTEM and USER context can be difficult and complex. PsExec is invaluable for troubleshooting; this utility can open a process in SYSTEM context, allowing the ability to test your binary. To open a SYSTEM context command-line, run the following from an elevated command window:
It’s useful to see what tasks are executing under which Session ID. Add the “Session ID” column to Task Manager:
Nice post mate. I'm abit frustrated the SCCM TS doesn't natively let you assign programs with the "Allow Users to Interact with this Program" checkbox checked. It would save alot of hassle.
I'm currently trying to deploy an application via a TS that I know works fines when it has Allow Users to Interact checked, and deployed individually, but I'm abit dumb struck when it comes to deploying it in a TS.
Is there a way other than described here, where I could make a specific task (executable in a program) in a task sequence interactive, perhaps by calling it a certain way? I know my TS would work beautifully if only that specific task/program was interactive.
Many Thanks. Will.