How I Went from MDT Integrated Task Sequence to Native SCCM

I started a new job six months ago as a EUC/SCCM Engineer where, after having delivered a couple of high profile objectives, one of the goals I have set myself is to combine 3 task sequences into one and to simplify the resulting Task Sequence.

I initially considered dumping the MDT integrated Task Sequence and going native SCCM, favouring the clean and simplified layout of the native TS in comparison to the MDT integrated one. But after a little deliberation I dismissed the idea since I was hoping to spin up a MDT database to make the Task Sequence and the build process truly dynamic, plus I really liked having all those extra task sequence variables that comes with MDT.

But then Gary Blok (from tweeted that he had moved away from MDT integrated to native SCCM and I was swayed again by his clean all-native task sequence. He even provided instructions on how to create a “MDT Lite” package which gave you access to all the additional task sequence variables without all the bloat.

So, taking inspiration from Gary I decided to go native SCCM.

In my MDT integrated task sequence I have all my PowerShell scripts in a folder called “ZTICustomScripts” which sits in the Scripts folder in the MDT Toolkit Files package. That way I can call the script by referencing the “%SCRIPTROOT%” variable, like below:


I knew that if I wanted to move to native SCCM task sequence then I will need to be able to still reference the “%SCRIPTROOT%” variable, which would mean I can copy and paste these steps from one TS to another.

I followed Gary’s blog post to create my MDT Lite package, except for copying the BGInfo files since I use the excellent OSD Background tool instead. Of course, I also added my ZTICustomScripts folder which contains all my PowerShell scripts:


I created a package called MDT Lite with the above contents. This is how I used it in my new all-native Task Sequence:

Created a group called “Download MDT Lite” right after the partitioning steps.

Added a “Download Package Content” task and called it “Download MDT Lite Package”. I selected the MDT Lite package I created and set the download location to “Task Sequence working directory”. I checked “Save path as a variable” and chose “MDTLite” as the variable name.


Note that to be able to reference the contents of this package we have to append “01” to the name of the variable, thus “MDTLite01”. (If we had a second package to download in the list then that package location would be referenced as “MDTLite02”.)

I can now reference the Scripts folder within this package as “%MDTLite01%\Scripts”, but that would mean updating each and every one of my Run Command Line steps with the new path which is something I did not want to do. So I set my own custom task sequence variable called “SCRIPTROOT” and set its value to “%MDTLite01%\Scripts”, shown below:


This meant that I can just copy and paste my Run Command Line steps (which run my PowerShell scripts) to the new native task sequence with very little to no changes. I can also create a nested Task Sequence with all the steps to run my scripts which I can use in the MDT integrated TS and the native TS which has not gone into production just yet (and thus avoid duplication of efforts).

Lastly I done the same for the %ToolRoot% variable as well.

The resulting task sequence is a lot leaner and much easier to take in.

Choose a Disk to Install Windows on using WPF and PowerShell

I recently tweeted a screenshot of a GUI I created using WPF and PowerShell to let engineers choose the disk to install Windows on, intended to be used in a SCCM Task Sequence. I was then asked by (none other than!) David Segura to share this with the rest of the community.

In my last post I wrote about how I found a workaround to a snag I hit upon while using the MahApps.Metro theme. That was almost 10 months ago. That post was meant to be a precursor to introducing this GUI but I got busy with life and my new job so the blog took a back seat. I’m glad that David’s reply has spurred me on to write this post and introduce the GUI. (I also have a few more posts lined up inspired by Gary Blok’s endeavour to break away from MDT and go native ConfigMgr. More on that soon.)

Update 1: I included steps in the Task Sequence to make the GUI appear only if more than one disk is present, as suggested by Marcel Moerings in the comments.

Update 2: I updated the script to exclude USB drives.


SCCM will install the OS on disk 0 by default. In my previous environment two disk configurations were very common so I created this GUI for Engineers to choose the disk to install Windows on. This works by leveraging the “OSDDiskIndex” task sequence variable. If you set this variable to your desired disk number then SCCM will install the OS on that disk.

This is what the GUI looks like when run in full OS:


And this is what it looks like when run in a Task Sequence:



You will need to add the following components to your Boot Image:

  • Windows PowerShell (WinPE-PowerShell)
  • Windows PowerShell (WinPE-StorageWMI)
  • Microsoft .Net (WinPE Dot3Svc)

How to Implement

Simples :)

  • Download the solution and extract the zip file
  • Create a standard package with the contents of the zip file. Do not create a program.
  • In your task sequence add a Group called “Choose Disk” before the partitioning steps
  • Within the group add a Run Command Line task and name it “Check if there’s more than one Hard Disk”. Enter the following one-liner:
PowerShell.exe -NoProfile -Command "If ((Get-Disk | Where-Object -FilterScript {$_.Bustype -ne 'USB'}).Count -gt 1) {$TSEnv = New-Object -COMObject Microsoft.SMS.TSEnvironment;$TSEnv.Value('MoreThanOneHD') = $true}"

one liner

  • Add another Run Command Line step and name it “Choose Disk to Install OS”, and choose the package you created. Add the following command line:
%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\powershell.exe -STA -NoProfile -ExecutionPolicy Bypass -File .\ChooseDiskWPF.ps1

choose disk

  • In the Options tab for the “”Choose Disk to Install OS” step, Click on Add Condition > Task Sequence Variable > type “MoreThanOneHD” in the variable field, set the condition to “equals” and the value to “TRUE”


Bear in mind that this does not exclude removable drives. This is because I always run a set of pre-flight checks as a first step which weed out any removable drives before this GUI is presented hence looking out for removable drives was not duplicated in this solution.

The Code



# Assign current script directory to a global variable

$Global:MyScriptDir = [System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.Definition)


# Load presentationframework and Dlls for the MahApps.Metro theme

[System.Reflection.Assembly]::LoadWithPartialName(“presentationframework”| Out-Null

[System.Reflection.Assembly]::LoadFrom($Global:MyScriptDir\assembly\System.Windows.Interactivity.dll”| Out-Null

[System.Reflection.Assembly]::LoadFrom($Global:MyScriptDir\assembly\MahApps.Metro.dll”| Out-Null


Temporarily close the TS progress UI

$TSProgressUI = New-Object COMObject Microsoft.SMS.TSProgressUI



# Set console size and title

$host.ui.RawUI.WindowTitle = “Choose hard disk…”


Function LoadForm {







    # Import the XAML code

    [xml]$Global:xmlWPF = Get-Content -Path $XamlPath


    Add WPF and Windows Forms assemblies

    Try {

        Add-Type AssemblyName PresentationCore,PresentationFramework,WindowsBase,


    Catch {

        Throw “Failed to load Windows Presentation Framework assemblies.”



    #Create the XAML reader using a new XML node reader

    $Global:xamGUI = [Windows.Markup.XamlReader]::Load((new-object System.Xml.XmlNodeReader $xmlWPF))


    #Create hooks to each named object in the XAML

    $xmlWPF.SelectNodes(“//*[@Name]”| ForEach {

        Set-Variable -Name ($_.Name) -Value $xamGUI.FindName($_.Name) -Scope Global




Function Get-SelectedDiskInfo {

    # Get the selected disk with the model which matches the model selected in the List Box

    $SelectedDisk = Get-Disk | Where-Object $_.Number eq $Global:ArrayOfDiskNumbers[$ListBox.SelectedIndex] }


    # Unhide the disk information labels

    $DiskInfoLabel.Visibility = “Visible”

    $DiskNumberLabel.Visibility = “Visible”

    $SizeLabel.Visibility = “Visible”

    $HealthStatusLabel.Visibility = “Visible”

    $PartitionStyleLabel.Visibility = “Visible”


    # Populate the labels with the disk information

    $DiskNumber.Content = $($SelectedDisk.Number)

    $HealthStatus.Content = $($SelectedDisk.HealthStatus)$($SelectedDisk.OperationalStatus)

    $PartitionStyle.Content = $SelectedDisk.PartitionStyle


    # Work out if the size should be in GB or TB

    If ([math]::Round(($SelectedDisk.Size/1TB),2lt 1) {

        $Size.Content = $([math]::Round(($SelectedDisk.Size/1GB),0)) GB”


    Else {

        $Size.Content = $([math]::Round(($SelectedDisk.Size/1TB),2)) TB”




# Load the XAML form and create the PowerShell Variables

LoadForm XamlPath $MyScriptDir\ChooseDiskXAML.xaml


# Create empty array of hard disk numbers

$Global:ArrayOfDiskNumbers = @()


# Populate the listbox with hard disk models and the array with disk numbers

Get-Disk | Where-Object -FilterScript {$_.Bustype -ne ‘USB’} | Sort-Object {$_.Number}| ForEach {

    # Add item to the List Box

    $ListBox.Items.Add($_.Model) | Out-Null


    # Add the serial number to the array

    $ArrayOfDiskNumbers += $_.Number



# EVENT Handlers


    If no disk is selected in the ListBox then do nothing

    If (-not ($ListBox.SelectedItem)) {

        # Do nothing


    Else {

        Else If a disk is selected then get the disk with matching disk number according to the ListBox selection

        $Disk = Get-Disk | Where-Object {$_.Number eq $Global:ArrayOfDiskNumbers[$ListBox.SelectedIndex]}


        Set the Task Sequence environment object

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


        # Populate the OSDDiskIndex variable with the disk number

        $TSEnv.Value(OSDDiskIndex= $Disk.Number


        # Close the WPF GUI






    # Call function to pull the disk informaiton and populate the details on the form




# Launch the window

$xamGUI.ShowDialog(| Out-Null

Unable to Import MahApps.Metro DLL Files using PowerShell in SCCM/MDT Boot Image

I’ve been working on converting some of my PowerShell scripts for SCCM into Graphical User Interfaces (GUIs), specifically using Windows Presentation Foundation (WPF). I’ve been a long admirer of the ConfigMgr OSD Front End from by Nickolaj Andersen who pointed me to the MahApps.Metro theme when I complimented him on the aesthetics of his Front End.

I’d recommend checking out the instructions by Kevin Rahetilahy on his blog at dev4sysblog and Damien Van Robaeys’s how-to video on creating WPF GUIs and applying the MahApps.Metro theme.

Now, coming back to this post. Getting the MahApps.Metro theme to work with the WPF GUI requires loading a couple of DLL files in the PowerShell script but annoyingly I ran into a problem where the DLLs wouldn’t import in the SCCM/MDT boot images In WindowsPE but works flawlessly in Windows 10. This is the error message I was getting:

“Could not load file or assembly MahApps.Metro.dll or one of its dependencies. Operation is not supported.”


Attempting to load “System.Windows.interactivity.dll” also threw the same error message. I also tried changing the DLL files from .Net 4.0 to 4.5 and also tried solutions from the community which presented me with the same error message each time. I knew these community solutions work in other environment so there was something definitely wrong from my end.

After trying a lot of different things I finally managed to resolve this with a workaround. Here’s what I done.

First, let’s take a look at the three DLLs I’ve been trying to load in my script.


The first DLL, the “presentationframework.dll”, imported successfully whereas the other two failed consistently no matter what I tried. After concentrating far too long on the two problematic DLLs and failing to come up with a solution I decided to take a closer look at the “presentationframework.dll”. When I load this DLL I get the following output in my console:


I saw that the DLL was being loaded from “X:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\presentationframework\v4.0_4.0.0.0__31bf3856ad364e35”.

So I decided to manually create this folder structure as shown below for each of my DLL files and copy them over:

Folder structure to create and location to copy MahApps.Metro.dll:


Folder structure to create and location to copy System.Windows.Interactivity.dll:


I then tried loading the DLL files using the following lines:


And voila! The two DLL files were loaded successfully!


The GUI and the theme works absolutely fine with this workaround. I spent a lot of time grappling with this problem so it was a relief to finally have it resolved, not to mention my GUI looks so much better with the theme. I just had to write this post not only for my own reference but also hoping it may help someone else out there too.

I’m refraining myself from inserting a screenshot of the GUI as I want to write a separate post introducing it to the community :)

January 2018 Cumulative Update for Windows 10 1703 is Not Installing in Task Sequence

I recently upgraded to SCCM 1710 and also updated MDT to v8450 and ADK to v1709. After the upgrade, I discovered Software Updates were broken in my Task Sequences. Specifically the Windows updates weren’t being installed (except for a Adobe Flash security update) whereas the Office 2016 updates were installing fine. At first I thought the update to 1710 had broken it but after a little digging I found that the January Cumulative update looks for the existence of the following registry item before installing the update:


Value Name="cadca5fe-87d3-4b96-b7fb-a231484277cc"



So if the registry item is not present then the January cumulative update will not install as documented by Microsoft.

Important: The requirement of the registry item is related to the recent Spectre and Meltdown vulnerabilities. Many Anti-Virus software use the kernel in an unsupported way which would cause Windows 10 to BSOD if the cumulative update was to be installed. Hence Microsoft requires AV vendors to fix their software and then create the above registry item as a signal that the update can be safely installed.

Update and Test Anti-Virus Software Installation

Most likely you will need to get in touch with your AV vendor and find out if they have updated their software to fix the issue with the kernel and that it creates the registry item. You will need to obtain the updated version of AV software and run a test install.

This is what I done in my testing:

  • Obtained the updated AV software
  • Did a test AV install and checked the registry item is created
  • I then tried and successfully installed the January cumulative update manually

Once I was confident the above works well I then reproduced this in my task sequence.

Testing in a Task Sequence

The first thing I tried was to set the registry item manually using a Run Command Line step without installing the AV software. This way I was able to verify that my Install Software Updates step was working since the January cumulative update installed fine.

REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\QualityCompat /v cadca5fe-87d3-4b96-b7fb-a231484277cc /t REG_DWORD /d 0 /f

Do not create this registry item manually in your Task Sequence unless you’re absolutely sure the antivirus software will create it anyway.

Next I moved on to see if the AV software would create the registry item. I made sure the Task Sequence installed the Anti-Virus software first and the Install Software Step would run afterwards. During my test run I added a condition to my Install Software Updates step to check for the existence of the registry item, just to be on the safe side.


Lock Down a ConfigMgr Task Sequence to an Active Directory Security Group

The ‘production’ Windows 10 build in our environment is v1607 while the Windows 10 1703 build is still undergoing testing and is yet to be given the green light. It was brought to the attention of the powers that be that 2L Engineers have been installing the test build for end user devices which has caused a fair amount of issues since it’s not ready for production yet.

My proposal is to lock down the Task Sequence to a select few members of staff. This is what I propose:

  • Prompt for username and password at the beginning of the Task Sequence
  • If the username is a member of an AD group then the user is authorised to install this build
  • Next step is to verify their authentication by attempting to map a network drive using their credentials
  • If the drive maps successfully then the user is authorised and authenticated and thus can proceed with installing the build

Important: I make use of the excellent Deployment Web Service by Maik Koster to carry out Active Directory related tasks during OSD in my lab. As such the web service is a pre-requisite to implementing the solution demonstrated in this post.

First, a demo on how this works

Here’s a video I uploaded to YouTube yesterday to demonstrate what my implementation looks like. It demonstrates the following scenarios:

  • Entering an incorrect username (which doesn’t exist in AD)
  • Entering a username which is not part of the AD group and hence is not authorised to install this build
  • Entering a username which IS authorised to install the build but an incorrect password was entered
  • Finally, the video shows entering a username and the correct password for an account authorised to install this build

Instructions on how to implement the solution

Now, let’s take a look at the instructions…

Create the Active Directory group

Create an AD security group called something like “Windows10_Build_Testers”. Add users to the group who are authorised to run the Task Sequence.

Create the shared folder

Create a shared folder called something like “TaskSequenceLockdownShare” on a server/computer and give the “Windows10_Build_Testers” group share and NTFS security permissions. This can be on any server which is guaranteed to be up and running 24/7 as it will need to be on the network when Engineers run deployments.

Edit the PowerShell script

Download the PowerShell script. Here’s what it looks like:

# Temporarily close the TS progress UI
$TSProgressUI = New-Object -COMObject Microsoft.SMS.TSProgressUI
Start-Sleep 1

# Set console size and title
[console]::WindowWidth=95; [console]::WindowHeight=25; [console]::BufferWidth=[console]::WindowWidth
$host.ui.RawUI.WindowTitle = “Checking authorisation to run this build…”
# Initialise variables
$URI = http://mni-sccm01/OSDWebService/AD.asmx?wsdl”
$WebService = New-WebServiceProxy -Uri $URINamespace OSDWebService
$SharedDriveToMap = “\\mni-sccm01\TaskSequenceLockdownShare$”
$ADGroupName = “Windows10_Build_Testers”
$DomainName = “EMENEYE”
$Global:TSEnv = New-Object -COMObject Microsoft.SMS.TSEnvironment
$TaskSequenceName = $Global:TSEnv.Value(“_SMSTSPackageName”)
# Inform user this build is locked down
Write-Host “`n$TaskSequenceNameBackgroundColor DarkBlue
Write-Host “`nThe build you selected has not been released to production yet”BackgroundColor Black
Write-Host “Authentication is required to install this build…”BackgroundColor Black
# Set registry entry to prompt for credentials at the command line instead of using a dialog box
# Set-ItemProperty “HKLM:\SOFTWARE\Microsoft\PowerShell\1\ShellIds” -Name “ConsolePrompting” -Value $True
Function Get-UserCredentials {
# Get credentials
Do {
Start-Sleep 3
$Global:TSCeds = $Host.ui.PromptForCredential(“Authentication is required…”, “Please enter your username”, “”, “”)
} Until ($Global:TSCeds.UserName -ne “”)

# Check if the domain was entered. The idea is to make this script work with or without the domain name entered by the engineer
If ($Global:TSCeds.UserName -like $DomainName\*”) {
# Strip the domain from the username
$Global:Username = ($Global:TSCeds.UserName).Split(“\”)[1]
Else {
$Global:Username = $Global:TSCeds.UserName

# Proceed to check if user exists in AD
Function Check-UserExists {
# Get user attributes using the Web Service
$Result = $WebService.GetUserAttribute($Global:Username, “userPrincipalName”)
# Check if the user exists in AD (the attribute should have been returned and thus the variable should be populated)
If ($Result) {
# User exists in AD
# Call function to check if the user is a member of the AD group
Else {
# User does not exist in AD
Write-Host “`nThis username $($Global:Username.ToUpper()) does not exist in Active Directory”ForegroundColor RedBackgroundColor Black
Write-Host “Please try again…”BackgroundColor Black
Write-Host “”
# Call function to get user to try again
Function Check-IfUserIsInGroup {
# Call Web Service to get list of user’s group memberships
$Result = $WebService.GetUserGroupsByName($Global:Username)
# Check if user is in SCCM_Admins group in AD
If ($Resultcontains $ADGroupName) {
# User is a SCCM admin
Write-Host “`n$($Global:Username.ToUpper()) is authorised to install this build…”BackgroundColor Black
# Call function to check their authentication
Else {
# User is not in AD group
Write-Host “`nThe username $($Global:Username.ToUpper()) is not authorised to install this build”ForegroundColor RedBackgroundColor Black
Write-Host “Please try again or speak to the EUC team if you believe you are authorised”BackgroundColor Black
Write-Host “”
#Call function to get user to try again

Function Check-Authentication {
# Attempt to map a drive
# If the drive maps successfully then the user is authenticated
# Map drive using credentials
$Drive = New-PSDriveName LPSProvider FileSystemRoot $SharedDriveToMapCredential $Global:TSCedsErrorAction SilentlyContinue
# Check if drive was mapped successfully
If (($Drive).Nameeq “L”) {
Write-Host “User authentication successful. Proceeding to pre-flight checks…”ForegroundColor GreenBackgroundColor Black
Remove-PSDrive -Name “L”
Start-Sleep 4
# Set the AllowInstall task sequence variable to TRUE
$Global:TSEnv.Value(“AllowInstall”) = $true
Else {
Write-Host “Error: Unable to authenticate $($Global:Username.ToUpper())ForegroundColor RedBackgroundColor Black
Write-Host “Please try again…”BackgroundColor Black
Write-Host “”

#Call function to get user to try again
Function Check-IsWebServiceOnline {
# Full credit for this function
# Create a HTTP request to the Web Service
$Request = [Net.HttpWebRequest]::Create($URI)
#Try to get the response from the Web Service. If a response is received then return true. If some error occurs then return false.
Try {
#Get the response from the request
$Response = [Net.HttpWebResponse]$Request.GetResponse()
return $true
Catch {
return $false
# Abort the request now
# Only run this script if the Web Service is online
If (Check-IsWebServiceOnlineeq $true) {
Else {
Write-Warning “Unable to contact the Web Service. Cannot proceed with this script. Please contact the EUC team.”

a) Change the variables in lines 12-16 to suit your own set up. This includes the web service URI, the shared drive to map (to authenticate the user against), the name of the Active Directory group and your domain name.

b) If you’d like to prompt for the credentials in the command line instead of a pop up then uncomment line 26.

c) The mapped drive is given the name “L” (for Lockdown). You can change this in lines 95, 98 and 100 if you want.

d) The name of the team to contact should something goes wrong is referred to as the “EUC team”. You can change this to suit your environment in lines 83 and 142.

Create a second shared folder to store the PowerShell script and files

a) Create a folder called “Task Sequence Lockdown” and save the CheckForAuthorisation.ps1 PowerShell script. Give your SCCM network access account share and NTFS permission to this folder. This shared folder should be somewhere in your software sources repository.

b) From a computer/server with MDT installed, copy ServiceUI.exe (from C:\Program Files\Microsoft Deployment Toolkit\Templates\Distribution\Tools\x64) to the “Task Sequence Lockdown” folder from step a) and rename it to ServiceUI_x64.exe

c) Copy ServiceUI.exe (from C:\Program Files\Microsoft Deployment Toolkit\Templates\Distribution\Tools\x86) to the “Task Sequence Lockdown” folder from step a) and rename it to ServiceUI_x86.exe

Configure the Task Sequence

In my case the Task Sequence I want to lock down is called “*** Test Build *** – Windows 10 – x64 (1703)”. Right-click on the task sequence and click on Edit.

a) Under Execute Task Sequence:

Click on Add > New Group. Name it “Lock down Task Sequence”.

b) Click on Add > General > Connect to Network Folder. In the ‘path’ field enter the full path to the “Task Sequence Lockdown” shared folder you created earlier. Important: if this folder and the shared folder to be mapped in the script are both located on the same server then use the IP address of the server in the path field in this step (instead of the hostname).

Optional: I renamed the step to “Preparing…” instead of leaving it the default


c) Choose a drive letter. I chose drive letter M. Enter a user account which has permission to map the drive.

d) Click on Add > General > Run Command Line. Enter the following line in step Run Command Line. Include the following line:

M:\ServiceUI_x64.exe -process:TSProgressUI.exe %SYSTEMROOT%\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File M:\CheckForAuthorization.ps1

Change the drive letter if you chose a different one in the previous step.


e) Select the group immediately after the “Lock down Task Sequence” group and click on the Options tab. Click on Add Condition > If statement > All conditions.

Again, click on Add condition > Task Sequence Variable. Enter the variable name “AllowInstall” and the value “TRUE”.


If authorisation and authentication is passed then the PowerShell script creates a “AllowInstall” task sequence variable and assigns the value “TRUE”. Therefore, checking for this condition in the next group means installation will not proceed without it. If the PowerShell console window is closed then the Task Sequence will fail.

Suitability of this solution in a live environment

I mentioned at the outset that this solution is intended to keep 2L Engineers from installing a test build. I know there are ways of deploying hidden task sequences but that requires editing the boot image. Personally, I find editing a task sequence simpler.

Also, some university departments have their own IT reps or admins who can be given the go-ahead to install or re-image computers. Because access is controlled via an Active Directory group, giving users that right and removing the right is as simple as adding and removing them from the AD group.

I hope someone, somewhere will find this useful. Remember that the Deployment Web Service by Maik Koster is required for this to work which makes very easy to do checks against an Active Directory domain. If any part of the instructions is not clear do let me know.

Dual Scan: The Undesirable Windows 10 Update Behaviour

The Windows 10 estate in our environment consists primarily of v1511 and v1607. All new computers (and any being re-imaged) get Windows 10 1607 installed, which is our ‘Production’ build. We have Windows 10 updates deployed to the All Unknown Computers collection which ensures that updates are installed during OSD. This is Windows 10 “Security Updates” and “Critical Updates” only and does NOT include feature updates.

The End User Computing team are yet to green light Windows 10 1703 hence the 1703 feature update has not been approved in the Windows 10 Servicing plans. However, we noticed recently that 2L Engineers click on “Check for updates” AFTER build has completed and not only are there additional updates to install but the Windows 10 1703 feature update get installed too. Now, this is interesting because, like I said, the feature update hasn’t been approved in the Windows 10 servicing plans. (Note, I’m specifically referring to the ‘Check for updates’ button and not the “Check online for updates from Microsoft Update” link.)

Now, I’ve done a lot of testing and reproduced the issue in my lab and confirmed the culprit was Dual Scan. Enabling certain Windows Update for Business (WUfB) group policy settings (or Registry or MDM settings) triggers the Dual Scan behaviour which scans both internal WSUS or SCCM SUP servers as well as Microsoft Update servers for patches and feature updates. Annoyingly, it ignores the local WSUS or SUP servers and gives precedence to Windows Update. This is exactly what was happening in our environment. Consider the following screenshot of WindowsUpdate.log from a newly installed Windows 10 1607 device after having clicked on the Check for updates button and installed the feature update. It confirms Windows 10 is reaching out to Microsoft Update servers:


So what actually triggered the Dual Scan behaviour? Generally speaking, using certain group policy settings related to WUfB enables Dual Scan. Microsoft mentions some of these settings in “Using ConfigMgr With Windows 10 WUfB Deferral Policies”. In our environment we had the Specify intranet Microsoft update service location setting set to our internal SUP server. In “Manage settings for software updates” Microsoft recommends that this setting should not be enabled using Group Policy because the machine policy received from SCCM will populate this setting in the local policy on the client computer and point to the SUP servers. Having the policy set using Group Policy in addition to the local policy being enabled by SCCM causes undesirable side effects, one of which is Dual Scan.

How do we stop this happening? For Windows 10 607 you will need to install the August cumulative update (or better yet KB4039396). For Windows 10 1703 you will need the October cumulative update installed. Then you will need to install the latest Windows 10 1709 administrative templates which includes a setting called “Do not allow update deferral policies to cause scans against Windows update” which will disable the Dual Scan behaviour. It’s important to note that simply updating the administrative templates will not be enough. You need the above cumulative updates/KB’s installed to be able to take advantage of the new group policy setting to disable Dual Scan.

After having taken the above actions in my lab I carried out further testing and found no additional updates, feature updates or otherwise, were available to install after OSD completed, thereby disabling Dual Scan successfully.

Extending the MDT Database – sp_refreshview Not Working

I recently had to add a few custom properties to the computer settings in the MDT database and discovered the custom properties weren’t being read using PowerShell or during a deployment simulation. A quick look into the MDT database on the SQL server revealed this was due to the database Views not being updated with the new settings, despite running the recommended stored procedures using sp_refreshview.

In this post we’ll take a look at how to extend the database with your custom properties, the issue I experienced and the resolution.

A Quick “How To” on Adding Your Own Custom Properties

Launch SQL Server Management Studio and connect to your SQL server, expand the MDT database > Tables > File Tables > dbo.Settings > right-click on Columns and select New Column. Add your properties in the Column field like so:


Once you’ve added your custom properties, it is recommended to run the following queries against the MDT database:

EXECUTE sp_refreshview ‘[dbo].[ComputerSettings]’
EXECUTE sp_refreshview ‘[dbo].[LocationSettings]’
EXECUTE sp_refreshview ‘[dbo].[MakeModelSettings]’
EXECUTE sp_refreshview ‘[dbo].[RoleSettings]’

If you then launch the Deployment Workbench you will find the new properties in the Details tab for any given Computer or Role.

The Problem

For testing purposes I configured a couple of the new properties as shown below:


However, when I tried to retrieve the properties using PowerShell the properties were missing:


Digging into the database on the SQL server revealed the “ComputerSettings” and “RoleSettings” Views haven’t been updated despite running the sp_refreshview stored procedures. The properties were missing from the Views entirely as shown below:


 The Resolution

A “View” is simply a collection of properties/columns from multiple tables in a database. The cmdlets in the MDTDB PowerShell module such as Get-MDTComputer and also the Gather step in MDT runs a query against these Views to retrieve computer properties. An incomplete View = broken OSD :).

Fortunately fixing the offending Views are quite simple.

In the SQL Server Management Studio and expand the MDT database > Views > right-click on “dbo.ComputerSettings” and choose “Script VIEW as” > Alter To > New Query Editor Window.

In the resulting SQL query simply add your custom properties at the end of the SELECT statement as shown below:


Once this is done, you can repeat this process for the views “RoleSettings”, “LocationSettings” and “MakeModelSettings”. In my case only the views “ComputerSettings” and “RoleSettings” needed updating.

Once complete right-click on your MDT database and click Refresh.

Now when I run Get-MDTComputer the properties are being returned just fine: