Using SCCM Task Sequence Variables as Scripts

The more I dig in to SCCM/ConfigMgr, the more cool things I find. Every time I read a new post blog about things people have done with their Task Sequences, I get inspired to try more things. One of those things is setting Task Sequence variable values from the output of a script. At this point, I’m sure we’ve all read and re-read Gary Blok’s Waas posts and picked up a few tricks, I know I have. Last week on Twitter, I asked about dynamically setting variables to script output and Gary reminded me that he had several examples of this in his Waas posts using PowerShell to create a new TSEnvironment object, which I use in most of my scripts as well. While this approach works fine, I was actually looking for a shorter alternative, but it made me wonder if it would be possible to store your scripts as Task Sequence Variables then reference them in Run Command Line steps. The answer is “YES!”. Some of you may have already known this or tried it or have determined that this is a silly idea, but for me, I’m relatively new to some of what SCCM has to offer, so I’m excited about it!

The Theory

My theory was that the Task Sequence would expand any text inside a Task Sequence variable and string it all together and run it as a command just like if you had type the full command line into the text box. This would allow you to maintain a “Variable-Based Script Repository”. I find that I always have to copy an existing script/command step or go look it up to be sure I’ve got it right – why memorize if you know where to look it up when you need it? So things like the powershell.exe command line with the correct parameters or the ServiceUI command line for debug prompts or user prompts could be stored into variables for use later in Run Command Line steps. While you could save entire command blocks into the variables, you could store single PowerShell cmdlets then string them together at runtime.

The Setup

For these examples, I’m going to use Get-CimClass Win32_OperatingSystem to return Operating System version information. Note: If I was doing this in production, I would use more shorthand code, but I want it to easy to understand the examples, so they are less than optimal, but hopefully easier to follow. Also note, I’m much more comfortable in PowerShell. If you want to see examples of doing similar things with generating batch files on the fly, check out Gary’s SetupComplete post.

To get started, run this in PowerShell ISE.

$OS = Get-CimInstance Win32_OperatingSystem $OSCaption = $OS.Caption $OSVersion = $OS.Version $OSBuild = $OS.buildNumber $OSArchitecture = $OS.OSArchitecture Write-Host Getting PS Variables Write-Host Caption: $OSCaption Write-Host Version: $OSVersion Write-Host Build: $OSBuild Write-Host Arch: $OSArchitecture

Your output should be something like this:

Getting PS Variables Caption: Microsoft Windows 10 Enterprise Version: 10.0.17134 Build: 17134 Arch: 64-bit

If we add the TSEnvironment object to the script, we get this:

$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment $tsenv.Value('OSCaption') = $OSCaption $tsenv.Value('OSVersion') = $OSVersion $tsenv.Value('OSBuild') = $OSBuild $tsenv.Value('OSArchitecture') = $OSArchitecture Write-Host Getting TS Variables Write-Host OSCaption: $tsenv.Value('OSCaption') Write-Host OSVersion: $tsenv.Value('OSVersion') Write-Host OSBuild: $tsenv.Value('OSBuild') Write-Host OSArchitecture: $tsenv.Value('OSArchitecture')

If we launch a Task Sequence and add a Debug command line (See below for steps to add Debug to as Task Sequence), we can run this manually and get this:

Getting TS Variables OSCaption: Microsoft Windows 10 Enterprise OSVersion: 10.0.17134 OSBuild: 17134 OSArchitecture: 64-bit

Amazing right? It’s almost as if this worked!!

At this point, you would normally save this to a script then use the Run PowerShell Script step in the Task Sequence to launch it. But that would require you to maintain a script in a file and replicating content each time you needed to change the script (debugging or future updates). Keep reading for some cool alternatives.

Option 1 – Run Command Line

Your first option for running in-line code would be to simply take a working script block, then remove all line breaks and replace them with semicolons. Then make sure you don’t have any double quotes in the script. Then add it to a Run Command Line step and run it. Using the script above, your command line would look like this:

C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& { $OS = Get-CimInstance Win32_OperatingSystem; $OSCaption = $OS.Caption; $OSVersion = $OS.Version; $OSBuild = $OS.buildNumber; $OSArchitecture = $OS.OSArchitecture; $tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('OSCaption') = $OSCaption; $tsenv.Value('OSVersion') = $OSVersion; $tsenv.Value('OSBuild') = $OSBuild; $tsenv.Value('OSArchitecture') = $OSArchitecture; Write-Host Getting TS Variables; Write-Host OSCaption: $tsenv.Value('OSCaption'); Write-Host OSVersion: $tsenv.Value('OSVersion'); Write-Host OSBuild: $tsenv.Value('OSBuild'); Write-Host OSArchitecture: $tsenv.Value('OSArchitecture') }"

Option 2 – Output to Script

NOTE: The code below doesn’t work (other code I tested with did work) as written when run from a TS, but just fine in a cmd prompt. I don’t feel like working it out since this is just sample code anyway. The concept/technique still stands, just not this particular code. Sorry.

If you wanted to export a block of code into a local file then call it using a Run Powershell Script, you could use the ECHO function in the Command Prompt.

You could use ECHO and >> but if your script has any special characters like > or | you will need to escape the characters with ^ or you can use | Set /p=”” to add quoted text like this:

cmd.exe /c ECHO | Set /p="Your Text" >> C:\Temp\YourFile.PS1

So your completed code would look like this and would output a file called GetOSInfo.ps1 that can now be run inside your Task Sequence or be left on the computer for later use.

Type: Run Command Line

Name: Export Script

Command Line:

cmd.exe /c ECHO | Set /p ="$OS = Get-CimInstance Win32_OperatingSystem; $OSCaption = $OS.Caption; $OSVersion = $OS.Version; $OSBuild = $OS.buildNumber; $OSArchitecture = $OS.OSArchitecture; $tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('OSCaption') = $OSCaption; $tsenv.Value('OSVersion') = $OSVersion; $tsenv.Value('OSBuild') = $OSBuild; $tsenv.Value('OSArchitecture') = $OSArchitecture; Write-Host Getting TS Variables; Write-Host OSCaption: $tsenv.Value('OSCaption'); Write-Host OSVersion: $tsenv.Value('OSVersion'); Write-Host OSBuild: $tsenv.Value('OSBuild'); Write-Host OSArchitecture: $tsenv.Value('OSArchitecture')" >> c:\Temp\GetOSInfo.PS1

Type: Run Command Line

Name: Launch Local Script

Command Line:

C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -ExecutionPolicy Bypass -Command c:\Temp\GetOSInfo.PS1

Option 3 – Storing scripts in Task Sequence Variables

Here’s the approach that I was hoping would work when I began down this road. I have tested several different ways and was surprised that they each worked pretty well. I’m honestly not sure to what extent you’d even use this, but I think it’s cool and at least worth experimenting with. I plan to use it so I can re-use the same embedded script, without having to update content AND so that I can change a value 1 time in a Task Sequence and know that all references to it were changed.

Type: Set Task Sequence Variable

Name: Set Variable – PowerShellCommandOpen

Task Sequence Variable: PowerShellCommandOpen

Value:

C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& {

Type: Set Task Sequence Variable

Name: Set Variable – PowerShellCommandClose

Task Sequence Variable: PowerShellCommandClose

Value:

}"

Not a typo. Just use }” This is the closing tag for the PowerShell script block.

Type: Set Task Sequence Variable

Name: Set Variable – GetOSInfo

Task Sequence Variable: GetOSInfo

Value:

$OS = Get-CimInstance Win32_OperatingSystem $OSCaption = $OS.Caption $OSVersion = $OS.Version $OSBuild = $OS.buildNumber $OSArchitecture = $OS.OSArchitecture $tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment $tsenv.Value('OSCaption') = $OSCaption $tsenv.Value('OSVersion') = $OSVersion $tsenv.Value('OSBuild') = $OSBuild $tsenv.Value('OSArchitecture') = $OSArchitecture Write-Host Getting TS Variables Write-Host OSCaption: $tsenv.Value('OSCaption') Write-Host OSVersion: $tsenv.Value('OSVersion') Write-Host OSBuild: $tsenv.Value('OSBuild') Write-Host OSArchitecture: $tsenv.Value('OSArchitecture')

Paste in the whole script. No need to remove line breaks or ; but DO remove ” or the script will break.

Type: Run Command Line

Name: Get OS Info

Command Line:

%PowerShellCommandOpen%%%GetOSInfo%%%PowerShellCommandClose%

Consecutive % must include an extra % around the variable in the middle

When you launch the TS, you can check the logs and see that the script ran and see the results.

Debugging

This information has been posted many times. If you want to write scripts that depend on information from your Task Sequence, you should build a Task Sequence that has a Debug step in it. The Debug step simply opens a command prompt in the user’s context then allows you to launch anything as Local System. I generally launch PowerShell_ISE.exe to open PowerShell. From there, you can interact with the Task Sequence. You also need to include ServiceUI.exe in your Task Sequence as a reference to make this work. Using Option 3, here’s how I added a Debug step to my Task Sequence.

Type: Set Task Sequence Variable

Name: Set Variable – TSDebug

Task Sequence Variable: GetOSInfo

Value:

ServiceUI.exe -process:TSProgressUI.exe C:\Windows\System32\cmd.exe

Type: Run Command Line

Name: Debug

Command Line:

%TSDebug%

Summary

After going down this path, I feel like I know more about what we can do with ConfigMgr Task Sequence Variables and scripts. I really wanted Option 3 to be far superior to the alternatives. If you have a script that you use all the time, this could make it easier to update it and use it on the fly without updating content. I think it just depends on your use case. If you want to easily edit long scripts in the Task Sequence, you can use variables to break it into pieces them string them all back together when you want to run them. Or you can take the values of existing variable and embed them into your script variables and have an even more dynamic experience. I’d love to know what you thought. Send me a tweet or post a comment here and let me know how you would use this, or if it’s just a dumb idea.