SCCM OSD with multiple disks

OSD with systems that have multiple disks can be troublesome. Out of the box, SCCM TS steps don’t offer much in regards to this. You can make Format Drive steps for each disk, but there is no immediate way to identify which disk is which!
Even going into BIOS to check which disk is on which channel won’t help. At each boot, Windows may interpret the disk channels differently:

BUT, all hope is not lost. SCCM has a TS variable named OSDDiskIndex. The docs ( describe it as “Specifies the physical disk number to be partitioned.”. Hm, specify it how? Well, basically you find the number ID of the disk you want to specify for formatting, and you give that number to the OSDDiskIndex variable. How do we do this? You guessed it, with Powershell of course!

Since Server 2012/Windows 8, there exists the cmdlet Get-PhysicalDisk. You’d think it’d be a part of Powershell 3.0 for Windows 7/2008 R2, but for some reason the storage modules were not included in that ( I don’t know if it’s included in PS 4.0 and higher. But don’t fret, I will show how to accomplish the same thing with a WMI query.


Get-PhysicalDisk is a nice cmdlet. It gives all the relevant information you could wish for from a disk. For OSD, you probably want to differentiate disks on two criteria, SSD/HDD and size.
Getting disk type is easy. The MediaType property of a disk object from the cmdlet describes if the disk is an SSD or HDD. Size is decribed in the… size property! What else do we want? Oh, right the most important one, the ID! This is in DeviceID property.
Output example:

So, in a two disk scenario, where one disk is SSD and you want to select it, the command is as simple as

Get-PhysicalDisk | Where-Object MediaType -eq 'SSD' | Select-Object -ExpandProperty DeviceID

This will give us the ID of the disk that is an SSD.
Defining on size basis is easy as well (300GB):

Get-PhysicalDisk | Where-Object {$_.Size/1GB -gt 300} | Select-Object -ExpandProperty DeviceID

For combination of size and SSD:

Get-PhysicalDisk | Where-Object {$_.MediaType -eq 'SSD' -and  $_.Size/1GB -gt 300} | Select-Object -ExpandProperty DeviceID


This is all fine and dandy if you’re on at least Win8/2012/PE4.0 (or possibly Powershell 4.0 or higher on Win7/2008), but what if you’re not? Good old WMI it is then! Here’s the last example in WMI:

Get-WmiObject Win32_DiskDrive | Where {$_.Model -match "SSD" -and $_.Size/1GB -gt 300} | Select-Object -ExpandProperty Index

Basically, we use Win32_DiskDrive WMI class to get disk info. Sadly, there is no definite property in WMI that will tell if disk is SSD or not… But in my experience, all SSDs have “SSD” in the model name, so we’ll have to make do with that. Lastly, the ID of the disk is in the Index property.

So now we should know how to find the disk we want to partition. Next, let’s pass this information to the SCCM TS variable OSDDiskIndex, so the TS partition step will select the disk we want!

I find using Run Command Line TS step, with powershell.exe with the Command argument is the easiest way to run one-liners.

In the one-liner, we simply give the OSDDiskIndex variable the ID of the disk.

PowerShell -ExecutionPolicy Bypass -Command " (New-Object -COMObject Microsoft.SMS.TSEnvironment).Value('OSDDiskIndex') = Get-PhysicalDisk | Where-Object {$_.MediaType -eq 'SSD' -and  $_.Size/1GB -gt 300} | Select-Object -ExpandProperty DeviceID "

Of course, if you want to use WMI, replace the second portion of the command argument with the WMI equivalent.

When you run this, that disk will now be selected for later partitioning step.

Capturing SCCM TS logs – The PowerShell way

Ever had a task sequence fail? Wouldn’t it be nice to view the logs from a central location? Then this is the tutorial for you.

There are several posts out there how to capture logs, but they’re all a bit cumbersome for my tastes… involving several additional steps in your task sequences and relying on CMD commands (yuck!).

Here’s how to do it all in a single PowerShell script.


As you probably know, when a task sequence fails it stops and usually show you this a countdown like this:

So, instead of just stopping when a failure occurs, how do we force the TS to proceed to copy logs instead? Answer lies in taking advantage of the “Continue on error” TS step property:

By nesting the TS under one group, checking its “Continue on error” property and lastly setting the capture log step last in the TS, we make sure logs will always be copied, regardless of outcome.

Now for the script.

The script

For the impatient and experienced, you can skip further down for the full script. Verbose description follows:

First we need to initialize the task sequence environment to an object stored in a variable called $TSEnv.

$TSEnv = New-Object -COMObject Microsoft.SMS.TSEnvironment

Next, let’s store the computer name in the variable $TSComputerName

$TSComputerName = $TSEnv.Value("OSDComputerName")

If OSDComputerName doesn’t exist, we will use _SMSTSMachineName instead.

If (!$TSComputerName) { $TSComputerName = $TSEnv.Value("_SMSTSMachineName") }

Let’s set the TS logpath to a variable.

$TSLogPath = $TSEnv.Value("_SMSTSLogPath")

Now for more involved stuff. For a good organization of your TS logs, it’s preferable to sort them by Success and Failure. How do we determine if a TS was either? The self-explanatory boolean _SMSTSLastActionSucceeded variable tells us! If it’s false, the last step was a failure, if true, all’s ok.

So, our IF statement, in case of TS failure:

If ($TSEnv.Value("_SMSTSLastActionSucceeded") -eq "false")

For better visibility of our later custom error message, it’s best close the TS progress bar. We do this by invoking the TSProgressUI object and calling its CloseProgressDialog method.

(New-Object -COMObject Microsoft.SMS.TSProgressUI).CloseProgressDialog()

At this point, let’s set the central log location we want to write the logs to. We’ll use the computer name as the last folder.

$LogLocation = "\\YourServer\ConfigMgr\Logs\Tasksequence\Failure\$TSComputerName"

Let’s remove old logs for this model, recursively, forcefully and without error (in case previous logs don’t exist).

Remove-Item $LogLocation -Recurse -Force -ErrorAction SilentlyContinue

Let’s then copy the logs!

Copy-Item $TSLogPath $LogLocation -Recurse

Finally, let’s display an error message using WshShell popup method! This method has some possibilities. In this example, we display a popup that will say that an error occurred, log location and prompt the user if the want to shut down the system or continue (and exit the TS). You can read about the syntax more here:

Start by creating a WshShell object and setting it to the $WshShell variable.

$WshShell = New-Object -ComObject WScript.Shell

Create popup. Insert your text as in the example.

$PopUp = $WshShell.Popup("An error occurred! Check the logs on the server in this location: `n\Logs\Failure\$TSComputerName.`n`nTurn off the computer?",0,"Error occured",0x4 + 0x10 + 0x1000)

It will look like this.

A useful pop-up for once.

Again, I recommend you read the documentation if you want do make further customization to the prompt.

For the next step, we process the input to the prompt. The docs state, an answer of Yes equals Return value of 6, 7 for No. In this case, we only need to do something if the user chose to shut down the system.
Since shutdown.exe is not provided in WinPE, we have to use the command Wpeutil ShutDown instead. As for full OS, we use shutdown.exe, but we use it through the SMSTSPostAction variable instead. SMTSPostAction is basically an optional command line that is run after a TS ends, after TS environment is closed. In this case it’s preferable to shut down that way, ensuring that the TS environment does not end abruptly, possibly causing the client to be stuck in provisioning mode (a hassle!).

If ($PopUP -eq "6") {

    If ($TSEnv.Value("_SMSTSInWinPE") -eq "true") { wpeutil shutdown }
    Else { $TSEnv.Value("SMSTSPostAction") = "shutdown /s /t 0 /f" }

That’s it for the failure scenario! Next, let’s get dressed for success. Basically we just copy the logs, pretty much like in the failure scenario.
Also, if you want to set a SMSTSPostAction, you can do it here. In this case we have a restart.

Else {

    $LogLocation = "\\ville02\ConfigMgr\Logs\Tasksequence\Success\$TSComputerName"
    Remove-Item $LogLocation -Recurse -Force -ErrorAction SilentlyContinue
    Copy-Item $TSLogPath $LogLocation -Recurse
    $TSEnv.Value("SMSTSPostAction") = "shutdown /r /t 5 /f"

Full script

Apply script in TS

Now it’s time to put the script in the TS. Due to SCCM task sequences being executed in Session 0 and user console session being in Session 1, we need some assistance to move execution of the script to the user session to show the prompt.
Enter ServiceUI.exe. ServiceUI.exe is an executable found in MDT (Microsoft Deployment Toolkit) and its purpose is to break out processes from Session 0 into Session 1. You can find the executable in an installed MDT instance. Insert ServiceUI.exe in the same folder as the script.

Anonymous access to network share

For the script to work in both full OS and PE, you need to have the log folders to allow Anonymous write access. To avoid packages, you can also insert the script into a folder where Anonymous has read access.
Here’s a fine guide written by Vitorio Delage how to accomplish this:

Script in TS

Finally, insert a “Run command line” step as the last one in the TS. The command line should be:

ServiceUI64.exe -process:TSProgressUI.exe %SYSTEMROOT%\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File SCCM-CopyLogs.ps1

In the “Start in:” field, add the location of the script. Also check to Disable 64-bit redirection, no need for that.

That’s it!