Roberts Blog

The House of SCCM and Intune on System Center Street

Win7 to Win10 IPU – With ConfigMgr but without Desktop Analytics

With the race on to replace Windows 7 I recently had a request backed out from a customer to implement Desktop Analytics, while still requiring us to move forwards with their Windows 7 to Windows 10 IPU (In-Place Upgrade).

I think most people know that 14th of January 2020, Windows 7 support ended.

Losing Desktop Analytics from the project was a set back, it takes out a lot of leg work for an IPU, the service is used to light-up the upgrade issue areas on your Windows 7 estate and give you an insight in to upgrade readiness, in conjunction with ConfigMgr its a complete boon, bringing that service to bear on several thousand devices would have revealed all of the setup blockers and application incompatibilities that would snag a successful upgrade to Windows 10.

Desktop Analytics is a cloud-based service that integrates with
Configuration Manager. The service provides insight and intelligence for
you to make more informed decisions about the update readiness of your
Windows clients. It combines data from your organization with data
aggregated from millions of devices connected to Microsoft cloud

We still needed insights on upgrade readiness to see what kind of Windows 10 setup blockers are out there, which meant falling back to running Windows 10 setup in Compatibility Scan mode against the targets, and then farming the resulting data for errors and warnings.

In this post I’m going to share a PowerShell script I wrote to ‘bridge’ the gap between this ConfigMgr-based solution, and the analysis of the resulting data set consisting of LOG and XML files.

Before we get underway it is worth noting that once the Windows 10 Setup Compatibility scan has completed, there are a bunch of log and xml files left laying around, and some registry keys created. These files and keys reference what issues were found and the exit code of setup.

The solution linked above sets you up to deploy a task sequence from ConfigMgr which initially references a Package\Program, containing a CMD file, that UNC’s back to an SMB share so as to launch Windows 10 setup in the right mode, and once setup has finished scanning some additional steps are then run to store the resulting log\xml payload on the SMB share for later analysis.

The task sequence could just as easily export the registry keys to the SMB share for you, and the PowerShell script I wrote could be adjusted to parse those. We’ll go with the log and xml files and do something with them using SETUPDIAG. As an aside you could also collect those registry keys by extending your Hardware Inventory out which is yet another route you can take to arrive in Rome.

I created an SMB share called RetroAnalytics, created a folder called Media in there to store the Windows 10 setup media, which is referenced in the Package\Program’s CMD script, also created another folder to receive the data from setup called Logs, another for SetupDiag, and finally another to contain the results from SetupDiag aptly named as Results.

Take heed that when you create the CMD script for the Package\Program you should make sure that the path in the SMB share points to where you’ve stored the Windows 10 setup media, in this case \\Server\RetroAnalytics\Media and call it from there.

When it comes to monitoring and initial harvesting of results from your IPU compatibility scan only task sequence deployment, you can immediately turn to deployment monitoring in ConfigMgr and monitor the task sequence’s deployment, which will represent the error codes returned by Windows 10 setup after its done a compatibility scan on each target device.

This will in a sense give you an immediate green and red light view on the upgrade readiness of the estate.

Where Windows 10 has found a problem you’ll get an exit code to troubleshoot with, however we can dig in and find out more information about the blocker by analysing the setup log and xml files that are copied to the SMB share.

The first part of the solution is documented over here, which entails setting up a share to host the full Windows 10 media, which will then be referenced from a ConfigMgr Package’s CMD script tucked into a task sequence, which should look like this:

* Note that I added an extra step to the TS to create a completion flag file in the machine folder, on the SMB share once the file copies are completed, this stops the PowerShell script from processing machine folders that are still being written too. You really should do this as well

The target devices will run the task sequence which will then run windows 10 setup over-the-wire, no caching, nothing stored in ConfigMgr’s SIS. This will mean that circa 360MB will traverse the network from the SMB share to each target device.

If you have low-bandwidth network links here and there, you can put some thought into how to overcome possible congestion with a localised SMB share or something.

Once you’ve got the above solution in place, and its returning data back to the SMB share, you can do something with it. I have a simple PowerShell script to do just that.

Parsing the log and xml files for each machine is a chore, best carried out using Windows 10 SetupDiag.

Using SetupDiag we can distil a single log file for each file-set, and the resulting log file can easily be parsed for errors and warnings.

Windows 10 setup compatibility scan results:

Results from SetupDiag:

And here’s a snippet from the SetupDiag log:

So in summary to get some valuable information back from all this data, all we have to do is implement the ConfigMgr the solution shown here, make a slight modification to it (Complete.flag and the UNC path set correctly), deploy that out to the target machines, then with the results on the SMB share literally loop around the data in every machine folder and call SetupDiag, finally, process the setup log file and print out the results to a CSV file. Not that difficult at all!

We can then bring the CSV file into Excel later on, pretty it up, then mine it for valuable information on upgrade readiness.

The PowerShell script shown below does just that, it fires off SETUPDIAG then it harvests the data from the resulting log into information which is stored in a CSV file.

The script has to be run on the server hosting the SMB share as it references fixed volumes in the paths, you can tweak this if you want to run it remote from the SMB share.

Before you can run this you’ll need the SetupDiag tool which is linked for download from here, and store it away in the SMBShare\SetupDiag folder.


# Robert Marshall - V1 - 17/12/2019

# Robert Marshall - V2 - 13-01-2020

# Identify undiscovered IPU target devices and process them

# $host.PrivateData.ErrorForegroundColor = 'White'


$MatchFound = $false

# Create CSV if it does not exist

if (!(Test-Path -Path "E:\RetroAnalytics\Results\IPU-Repository.csv" -PathType Leaf)) {

    New-Item "E:\RetroAnalytics\Results\IPU-Repository.csv" -type file > $null

# read CSV hostname column into our array

$hostnameArray = @()

Import-Csv -Path "E:\RetroAnalytics\Results\IPU-Repository.csv" | ForEach-Object {
    # Poke hostname column into the array

    $hostnameArray += $($_.Machine)


gci E:\RetroAnalytics\Logs | select Name | sort Name | ForEach-Object {
    $FolderSourceMachine = $($_.Name)

    $MatchFound = $false                      

    # Check if the folder source machine is in the array

    if ($hostnameArray | where { $_ -eq  $($_.Machine)}) {

        $MatchFound = $true

    if (!$MatchFound) {

        Write-Host "No match found for $FolderSourceMachine, begin processing ..."
        # Check if flag file is present, skip if not

        if (Test-Path -Path "E:\RetroAnalytics\Logs\$FolderSourceMachine\COMPLETE.FLAG" -PathType Leaf)
            Write-Host "COMPLETE flag found, logs are ready, launching SETUPDIAG against this devices logs"

            # Create new directory $FolderSourceMachine

            if (!(Test-Path -Path "E:\RetroAnalytics\Results\$FolderSourceMachine")) {

                New-Item -ItemType directory -Path "E:\RetroAnalytics\Results\$FolderSourceMachine" > $null
            # Launch Windows 10 SetupDiag.exe and analyse the resulting log file, we are going to wait for it to return control back to this script

            Start-Process -Wait -FilePath "E:\RetroAnalytics\SetupDiag\SetupDiag.exe" -ArgumentList "/Output:E:\RetroAnalytics\Results\$FolderSourceMachine\$FolderSourceMachine.log","/LogsPath:E:\RetroAnalytics\Logs\$FolderSourceMachine"

            # Process the resulting log to retrieve the lines containing strings "warnings" and "errors", storing them in an array
            $storageArray = @()

            foreach($line in [System.IO.File]::ReadLines("E:\RetroAnalytics\Results\$FolderSourceMachine\$FolderSourceMachine.log")) {
                if ($line.StartsWith("Warning:"))
                    $storageArray += $line

                if ($line.StartsWith("Error:"))
                    $storageArray += $line
            # Empty the array into the CSV repository
            for ($i=0; $i -lt $storageArray.length; $i++) {

                Add-Content "E:\RetroAnalytics\Results\IPU-Repository.csv" "$FolderSourceMachine,$($storageArray[$i])"

            # Just for neatness add the machine to the in-memory list of devices to keep in sync with whats in the CSV
            $hostnameArray += $FolderSourceMachine            

            Clear-Variable -Name "storageArray"
            Write-Host "Completion flag not present so skipping, cause most likely is that the task sequence is still writing to this log folder for $FolderSourceMachine"

It’s a bit of a clunky-looking solution I must admit, but once you lay all this down and get it working, once things are settle down with the deployment hitting most machines, you can kick-back and open the CSV file (will reside in the Results folder) in Excel to find out what kind of errors and warnings are being reported by the Windows 10 setup compatibility scan.

A poor man’s Desktop Analytics, Retro Analytics.

Buzz me on twitter @RobMVP if you want to talk about this solution further, I assume most are done upgrading from Windows 7 to 10 already 🙂

I’d like to thank Arnab Mitra and team for the source article on Microsoft Docs, which describes the heavy-lifting needed to get ConfigMgr rigged out right.

ConfigMgr Build 1910– Run Command Line–Output to task sequence variable

ConfigMgr Build 1910 released with a wholesome load of features, one in particular I’ll cover here, the addition of Output to task sequence variable in the existing task sequence Run Command Line and Run PowerShell steps.

This is a really handy feature.

I’ve been achieving the same result in a clunky manner for a while now. Execute something client-side during the task sequence, store the (cleaned up) output in a task sequence variable, do something with that variable further into the task sequence. Good example would be evaluating a registry key value. Not so easy pre-Build 1910.

If I wanted specific output that I can manipulate within the task sequence I’d write a bespoke script to get at it first, then I’d add to the tail end of the script some code to inject the output into a task sequence variable, then yield back to the task sequence engine.

Back in the ‘day you’d do it this way using either atypical scripting channels PowerShell or retro VBScript.


set env = CreateObject("Microsoft.SMS.TSEnvironment")
env("HelloWorld") = "SomeValue"


$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment
$tsenv.Value("HelloWorld") = “SomeValue”

The drawback with this approach is that every script needs modification to clean up the output a little if needed, and then to store it away in a task sequence variable before returning back to the task sequence engine.

There is a way to overcome this with a reusable bare-bones wrapper\launcher script that does a couple of things, launches what you want to launch, be it a script or executable, and then stores the result in a task sequence variable and yields back.

On the whole this latter approach opens up the door to reuse of existing scripts et al without having to resort to making changes to them, you just wrap them and handle their output. It is still very clunky though, not ideal, all we want to do is bring back some output and look and do something with it.

We can now dispense with one layer of that solution, bringing the value back in a task sequence variable automatically, which means most of that script-bridging that I talk of above can be done away with. Great.

We can still run custom scripts or command lines to tailor output in-situ (filter it down), or resort to executing a script or process and returning its output in full. Returned in full the output will be trimmed to the 1,000th character.

What we do not need to do is run a few lines of script each time so as to gain access to the task sequence engines variables to store something away. That’s the difference here. Which turns out to be very enabling.

Worth noting that if we tailor the output in-script we can return a single value to do comparisons against further into the task sequence, nice and tidy, or return all the output, assuming whatever you want to find is within the first 1,000 characters, and use comparisons operators such as like to tease out what it is we’re looking for.

Here’s the Run PowerShell and Run Command Line steps with the Output to task sequence variable addition:

Once we have a value stored away we can evaluate it using the built-in task sequence engine logic, such as via a task sequence step’s Conditions:


That’s an explicit or absolute comparison (equals), which would mean that the output has to be something you can do a direct comparison against, whatever is outputting has to output a predictable singular value or predictable sentence as a result of the operation or via filtering.

If the output is unprocessed\unfiltered its no hassle, we can just as easily perform a wildcard match on the variables value, using the like operator for the condition:

There’s more that we can do, if we turn to the Set Dynamic Variable we can transform the existing value or spawn a new variable based on some complex-enough conditional checks, such as if variable is or is like X then set this or another variable to Y as an example:

So to summarise, if whatever you are executing returns a string of predictable characters, a single value, a sentence, you are good to go, just launch it and steer the output into a task sequence variable using this feature, evaluate it later on.

However, if you need to do some string manipulation you can do it using piping, filtering and the FOR commands /F switch within the CMD shell, or do it slightly differently with PowerShell. I’ll go at this within the CMD shell and use the Run Command Line step.

Let’s take SC QUERY as an example, here’s the output when pointed at a specific service:

        TYPE               : 30  WIN32
        STATE              : 4  RUNNING
                                (STOPPABLE, PAUSABLE, ACCEPTS_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0

We want to know if this service is running, its state.

There’s two options here, run SC QUERY WINMGMT, return all its output and then use the like operator to see if Running is present, or, we can filter the output and return the state whether it is running, paused or stopped.

I’ve got a couple of examples for the Run Command Line step that use that classic FOR /F command and switch along with FIND /I  to pick at specific elements in the output, these stand as examples that you can modify and cast out from.

Bring back the STATE of a specific Windows service:

FOR /F "tokens=1,2,3,* delims= " %A in ('sc query Winmgmt ^| Find /I "state"') do @echo %DRetrieve 


SC Query Winmgmt | FOR /F "tokens=1,2,3,* delims= " %A in ('Find /I "state"') do @echo %D

Either of these approaches will bring back the state value of a specific service cleanly.

Retrieve a specific Registry value:

FOR /f "tokens=1,2,* delims= " %A in ('reg.exe query HKLM\Software\7-zip /v Path') do echo %C

The 7-zip Path value will be returned as-is.

Check if the device is Azure AD joined:

FOR /F "tokens=1,* delims=: " %A in ('dsregcmd /status ^| Find /I "AzureAdJoined"') do @echo %B

It’ll return a YES or a NO to evaluate later on.

Check if a process running:

FOR /F "tokens=1,* delims= " %A in ('tasklist /NH /FI "ImageName eq notepad.exe"') do @echo %A

This will bring back the processes name if it is running, a like or equal comparison on notepad.exe or notepad will flag it up as running, and an action can be taken in the task sequence.

I think I’ll leave this here, as you can see this is a very enabling feature that could easily be underestimated or undervalued in amongst all the other bolder and brassier features that Build 1910 is laden with.

I tweeted about this feature recently, put out a few interesting but clumsy examples. Would be interesting to see some examples of how you use this new feature, feel free to tweet them there or leave in the comments here:

ConfigMgr HA and losing the Content Library

You’d think completely losing your ConfigMgr Content Library (no backup) would be quite a dramatic event from a bumpy road perspective, I found that it isn’t that traumatic at all, there are only two key activities, the first being some brief file system jiggery-pokery, and the second that the network is going to get a bit of a hammering, as all content will need to be resent (not redistribute) out to the DP’s to get ConfigMgr to put it back into the Content Library.

If you have a backup, restore that puppy and get out of jail totally. But with no backup, well, read on.

A while back I lost a HDD that was a member of a pair of 2TB disks in a lazy striped RAID disk, I was presenting that disk as a Shared Disk to two Hyper-V VM’s running SQL for my High Availability XL High Availability lab.

Losing that raided disk was a bit of a problem, as I said it was presented to two VM’s on the same Hyper-V host as a shared disk, they both used it to create a clustered SMB share to which I had moved the content library as part of the prep work to switch to HA.

My content library was literally gone!

It’s only a lab, so when things like this happen, hmmm, interesting, time to investigate!

So only recently I put another disk in to replace the faulted disk and brought the clustered SMB share back to life. Obviously there’s no content there but the clustered SMB share was back online and writable.

While the Content Library is unavailable no new content can be added to ConfigMgr, not the end of the world but worth noting from a HA perspective.

Left the lab alone for a day, came back and saw that ConfigMgr had attempted to distribute the built-in Configuration Manager Client package to the Content Library (CL) without being prompted, but had failed to complete the task.

It failed because ConfigMgr hadn’t recreated the CL top-level folder structure, it had created the DataLib folder but failed to create the FileLib folder and stalled right there.

Here’s the textual transcript:

  • Started package
    processing thread for package ‘HA200002‘, thread ID = 0x159C (5532)
  • Sleep 60 seconds…
    SYS=L3CMN4.LAB1.COM SITE=HA2 PID=5364 TID=5532 GMTDATE=Sun Oct 06
    20:10:46.881 2019 ISTR0=”HA200002″ ISTR1=””
    ISTR2=”” ISTR3=”” ISTR4=”” ISTR5=””
    ISTR6=”” ISTR7=”” ISTR8=”” ISTR9=””
    NUMATTRS=1 AID0=400 AVAL0=”HA200002″
  • Retrying package HA200002
  • Start updating the package HA200002…
  • CDistributionSrcSQL::UpdateAvailableVersion
    PackageID=HA200002, Version=18, Status=2300
  • Taking package snapshot for package
    HA200002 from source \\\SMS_HA2\Client
  • GetDiskFreeSpaceEx failed for
  • GetDriveSpace failed; 0x80070003
  • Failed to find space for 104867131 bytes.
  • CFileLibrary::FindAvailableLibraryPath
    failed; 0x8007050f
  • CFileLibrary::AddFile failed; 0x8007050f
CContentDefinition::AddFile failed;
  • Failed to add the file. Please check if
    this file exists. Error 0x8007050F
  • SnapshotPackage() failed. Error =
    SYS=L3CMN4.LAB1.COM SITE=HA2 PID=5364 TID=5532 GMTDATE=Sun Oct 06
    20:10:47.362 2019 ISTR0=”\\\SMS_HA2\Client”
    ISTR1=”Configuration Manager Client Package”
    ISTR2=”HA200002″ ISTR3=”30″ ISTR4=”32″
    ISTR5=”” ISTR6=”” ISTR7=”” ISTR8=””
    ISTR9=”” NUMATTRS=1 AID0=400 AVAL0=”HA200002″
  • CDistributionSrcSQL::UpdateAvailableVersion
    PackageID=HA200002, Version=17, Status=2302
    SYS=L3CMN4.LAB1.COM SITE=HA2 PID=5364 TID=5532 GMTDATE=Sun Oct 06
    20:10:47.415 2019 ISTR0=”Configuration Manager Client Package”
    ISTR1=”HA200002″ ISTR2=”” ISTR3=””
    ISTR4=”” ISTR5=”” ISTR6=”” ISTR7=””
    ISTR8=”” ISTR9=”” NUMATTRS=1 AID0=400
  • Failed to process package HA200002 after
    68 retries, will retry 32 more times
  • Exiting package processing thread for
    package HA200002.

I created the FileLib and PkgLib folders and kept an eye on DistMgr, magic began to happen. The client source was snapshotted and put into the CL, and then it went out to the DP’s just fine.

From there I needed to find all the content I wanted to put back in the CL in my own time, and perform an Update DP’s action on them. Content flowed from the source to the CL and then onto the DP’s as expected.

In the ‘real’ you’d just leave the content alone, its no doubt already on the DP’s and clients can retrieve it still, new content can be added as the content library is back online, so content would only really need to be resent to the DP’s if the source has changed, so the re-distribution of content to the DP’s to get it back into the CL doesn’t need to happen until that content has actually been iterated.

So, what can you take away from this, if you lose your content library and for some reason you find yourself without a backup the outcome isn’t that bad besides controllable network utilisation. As for recreating some folders, I’ve asked the PG to look at the code as they do recreate one folder (DataLib) but fail to create the other folder (FileLib), I didn’t check to see if it’d fail to create the last remaining folder (PkgLib), if they can make that piece of code consistent in folder creation then recovery will singly focus on network utilisation, as long as you haven’t lost your content source as well!

In an ideal world ConfigMgr would recover from this rare and sloppily-managed (no backup) event without redistributing all that content to the DP’s, so that the problem pivots solely around the content source and the content library alone, perhaps this can be made to happen now I’m not sure, and besides in a lab this can be brushed off, in production, backups … are … everything.

Windows 10 Automation–Changing Language–B1903

A customer of mine is in the process of bringing the image factory back in-house, leveraging their ConfigMgr installation, hosted in Azure, to deliver Windows 10 task sequences (build and upgrade) to intranet and eventually via their CMG, internet based devices.

The quality bar is relatively basic from the MSP that is responsible for purchasing, preparing and shipping their devices to their end-users, so it hasn’t taken long to spin up OSD in ConfigMgr, and match the build results, producing a better tooled, more customised build that meets their needs and goes several steps further, while they handle purchasing via the MSP and do the delivery themselves.

Managing the Windows 10 image factory using ConfigMgr is an interim measure for this specific customer, at some point they will swing towards using AutoPilot as part of their modernisation and cost reduction plan that we’ve come up with, which includes the ultimate objective that a company can have nowadays, or an IT pro can have on their radar, the biggy, winding up Active Directory.

Part of this customers image factory requirements is that the build starts out life in English (en-GB), so that their build engineers can customise the OS further with a bunch of tasks that haven’t been brought into the task sequence at this point, due to time restraints or Windows 10 B1903 related bugs (VPN settings annoyances when setup in SYSTEM context, SCCM delivered Wifi profile password woe’s …). Finally the customer wants to be able to switch the builds language to that required for the target user, just before they close the lid and begin shipping.

In this post I’m going to show how I handled the customers language requirements in Windows 10 using SCCM OSD, leaving some footprints on ground already well-trodden by notable others.

Straight out of the gate I was experiencing issues setting the language reliably in Build 1903.

I tried to approach using the unattended setup file, that ‘trusty’ old horse, and when that wouldn’t play ball, I turned to using brutality with DISM and PowerShell applets at the tail end of the task sequence, in an attempt to coerce the operating system into doing my bidding. Failure is a spur towards success for the less weak-of-heart, is what I say when things just don’t work. Surely there has to be a way.

I cruised the net. Saw much chatter about language issues in various Windows 10 builds, most of it seemingly unrelated noise, I noticed a post by Dan Padgett where he uses a different method, RUNDLL32 and an XML file (or two), passed it by, I recall at the time thinking that it most likely was for an older version of Windows 10 and looked pig ugly Smile

At my whit’s end, I reached out to Paul Winstanley, who promptly pointed me back at Dan’s post as the only reliable way he could get it all to work at present.

Dan’s post is actually quite comprehensive and is in part a derivative of some of the ground work carried out by Nicolas Lacours [Link here], there isn’t much more for me to add if anything, a Stirling job indeed, instead I’ll show how I leveraged the proposed method to switch languages during and after OSD.

So yeah, I implemented Dan’s write-up on using the RUNDLL32 method, and viola, after a bit of tinkering to match up with the task sequence variables in use, and after ironing out SillinessFromMe™, I was able to produce a build in any of the list of languages the customer needed.

Now that language in the newly built OS was controllable (thanks Dan and Nicolas, and Paul for circling me back there!) the next step was to force it to build with en-GB, while storing away in the registry what was chosen as the destination language when UI++ launches, so that it can be read in and processed another time to do the final language switch.

At this point the build engineer has an en-GB build to log into, and do whatever they want in readiness for the user.

The next piece was the final language switch, I used another task sequence, with all the language steps from the main task sequence copied across and some additional bits added, and then deployed as Available to the OSD build collection.

This then showed up in Software Center, and could be run as the final task before the device is powered off and shipped.

I’ll now go over the OSD build parts where it differs from Dan’s, and has notes worth pointing out, as I said there wasn’t much need for any change from what he has already etched out.

At the front-end of the task sequence, UI++ runs and interviews the build engineer:

  1. Launch UI++, buzz the engineer for build details and store selections in task sequence variables
  2. Stored the resulting OSDUILanguage value in a new task sequence variable called StoredOSDUILanguage, which is then used at the tail-end of the task sequence as part of the branding\tattooing (not MDT tattoo) of the device
  3. OSDUILanguage is forced to become en-GB to model the experience needed, this is the override that will force all builds to be en-GB initially

After the Setup Windows and ConfigMgr step, we break into the steps to handle the language.

Pretty much how Dan does it. I think the only difference is that I put the Language pack logic on the steps, and added the UK keyboard instead of US.

The final part of the solution in my task sequence runs just before the task sequence finishes up, and is used to poke the value stored away in the task sequence variable StoredOSDUILanguage, into the registry for later use alongside a few other settings.

Aside from my modifications, if you follow Dan’s guide, and you’ll get perfect a result every time. Very nice.

The task sequence to do the final language switch is part-clone of the OSD build task sequence steps, along with some customisations.

As you can see the structure of the change language task sequence is a copy\pasta of the OSD build task sequence with some added bits:

What’s happening:

  1. OSDUILanguage, the task sequence variable doing all the language donkey work, is set to en-GB as a default in case anything goes awry
  2. The registry key OSDUILanguage is retrieved from the registry and poked into OSDUILanguage, I do this using a PowerShell script stored in the same package hosting the language injection script from Dan
  3. A task sequence variable that I use to confirm if the language can be changed, BeginProcessing, is initialised as False
  4. OSDUILanguage is used to drive the dynamic variable step, each rule sets the BeginProcessing variable to True

5. The Begin group has logic on it that will skip the group if BeginProcessing isn’t true, which essentially ends the task sequences execution.

The rest is identical to the OSD build task sequence, the language is laid down, three reboots occur, and bosh the language has changed for new users (who have not already logged in). I will no doubt finesse this out a bit more to do error handling and an existential check on the registry key to trigger an abort if missing.

The PoSh to retrieve the registry key, which most likely can be done better, is here:

$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment
$regValue = Get-ItemProperty -Path ‘HKLM:\Software\<COMPANY NAME HERE>\Branding’ -Name OSDUILanguage | Select OSDUILanguage
$tsenv.Value(“OSDUILanguage”) = $regValue.OSDUILanguage
Write-host (“Found language ” + $regValue.OSDUILanguage)

We can probably collapse that into a single line executed using a Run Command Line step and do away with the script. Be ideal if ConfigMgr let us read directly from a ‘repository’ such as the registry as a task sequence step, and poke the value into a task sequence variable.

With this mechanism in place, simply changing the registry key from say fr-FR to de-DE and running the task sequence from Software Center will swap languages for new users only.

I couldn’t get it to switch the language for any profiles that were already created on the device.

The build engineers profile remains en-GB, however the destination user once they log in for the first time will have the correct language set.

Now the customer has a handle on their Windows 10 builds, and language is slotted into place to fit their needs, initially configured as en-GB, then configured with one of several other languages supported by the company in readiness for delivery.

All in all a job well done I thought.

I might take a look into changing the language for all users not just new users, and whittle up a post on it at some point if its doable without the user having to be logged in.

ConfigMgr CB1906–Management Insights–NTLM Fallback

I remember a few years back at an MVP Summit watching a PG member showing us the mock up’s they had prepared for how Management Insights would “look” in the console, while gauging our response and taking in feedback.

The feature certainly has come along way from then, if’ you’ve not paid much attention to Management Insights, now would be a good time to visit the feature and see what insights it gives you for your site.

From what I can recall the motivating reason for Management Insights was driven by the desire to make administrators lives easier overall, bringing to light the “house chores” needed to keep SCCM running fluidly, highlighting or giving insights into operational capability of a site (Empty collections, Fast Evaluation rules etc), and it has extended out to highlight best practices for certain parts of the product (example being the Site’s current Client Push NTLM Fallback state).

There’s a new Management Insight (MI) in CB 1906, called “NTLM Fallback disabled” which I’ll quickly run over now.

This MI will check the ConfigMgr Site, to see if Client Push Installation property Allow connection fallback to NTLM is enabled:


Enabled, the MI will report Action Needed:


When Allow connection fallback to NTLM is disabled in Client Installation properties, and when the MI is re-evaluated (right click) the MI reports a Completed state, which means we’re compliant:


The reason why you would disable Client Push attempts using NTLM is to force site to client authentication to take place using Kerberos, so as to fall in place with modern security practices, which see NTLM as insecure (rightly so) and something we should all be drifting away from, as partially noted in the docs:


When using the client push method of installing the Configuration Manager client, the site can require Kerberos mutual authentication. This enhancement helps to secure the communication between the server and the client. For more information, see How to install clients with client push.

At a lower lever you can disable NTLM fallback for the Operating System itself, with consequences that should be thought out first, using either domain or local GPO settings. This isn’t something you do without wising up on the consequences.

The new Management Insight is not checking the OS, it isn’t checking IIS or SQL for relevance and state, which have their own options for handling NTLM fallback.

Here’s a shot of local GPO on the site server for tinkering with restricting NTLM:

Note that GPO changes are made, remote devices attempting RDP to the site server that are not patched may encounter the “Encryption Oracle Remediation” issue.

Changing any NTLM setting requires some preparation work, at the least an understanding of what might break in your environment.

ConfigMgr CB1906–Site Maintenance

Now that Current Branch 1906 (5.0.8853.1000) is out, and I have some spare time to chase through some of the features, I thought I’d whack out some mini-posts highlighting various features.

One of the less impactful but interesting features due to what they are tinkering with, is Site Maintenance. This one isn’t going to woo your manager or users, or change your work routine one jot, but it is interesting nonetheless as it touches ‘old ground’.

Other than additions to the site maintenance task list over the last two decades, there hasn’t been any other change made in this space visually.

That changes as of CB 1906.

There hasn’t been much of a need to change something that isn’t broken, we hardly ever visit site maintenance, but it was long overdue a visual overhaul so as to scrub away that Win32 dialog look, as shown below:


Now, we have no need for a pop-up dialog, Site Maintenance tasks are listed in the details pane when you visit the Site Configuration > Sites node, and select a new tab called Maintenance Tasks.


Double click (Properties) on any of the listed tasks and you get taken back to familiar territory with the tasks properties sheet:


A very simple change to an oft-ignored part of the product, I like subtle changes like this, enabled by the fast cadence of ConfigMgr.

I noticed that post-upgrade of CB to 1906 the Last Start Time and Last Completion Time columns were rendering blank.

I chose and ran the Rebuild Indexes maintenance task (changed its scheduled +5 mins into the future) to see if the values are shown once a task runs post-upgrade.

Here’s the task running (SMSDBMON log):


And as I thought, well, I got lucky “thinking”, that a nudge would sort it, much like the classic “Have you tried turning it off and on again?” approach, much adored by the mightiest of admins:


A simple and as I said subtle change, that has modernised a very boring surface. Nice.

DP_Locality flags or bitmask in SMS_UpdatesAssignment

I recently had to figure out how to properly set the DP_Locality property in the WMI SMS_UpdatesAssignment server SMS Provider class, as part of some work being done on PatchMaster, which automates the deployment of patches.

Eventually I figured it out, but at first I couldn’t quite grasp what was going on. I had to query the PG to verify my thinking (Thank you Hugo!), and in this post I’m going to go over as much detail as possible so as to spam the bejesus out of the subject.

To frame things better, every time you create a new Software Updates deployment in SCCM, a new SMS_UpdatesAssignment instance is created in WMI on the Site server to represent it.

Here’s a shot of the property sheet of a deployment, note the Neighbour DP and fall-back DP panels at the top and middle, and the WUMU\Metering panel at the bottom:

This is what it looks like behind the wizards curtain, using WBEMTEST to take a peek, DPLocality is an unsigned 32-bit integer:

* Note that you can do the same with PowerShell: get-wmiobject -namespace “root\sms\site_<YOUR SITES SITE CODE>” -query “select * from SMS_UpdateGroupAssign
ment where AssignmentID = <DeploymentID>” | Format-List -Property DPLocality

The DPLocality properties value shows 786512 (0xC0050). We’ll decode this in a moment.

That integer actually represents a bit mask, or a set of flags. This is noted in the documentation under qualifiers for that property as bits.

When I set about to decode DPLocality, I came up short. The documentation on Microsoft Docs doesn’t quite explain the bit positions properly, we’ll go over this now.

Firstly, the information you need is spread across two pages, shown below is the class, and then the class that class is inherited from:

SMS_UpdatesAssignment Server WMI Class

The property we’re looking for is DPLocality:

As is shown above, this class is actually derived from the SMS_CIAssignmentBaseClass.

Deeper into the doc for the SMS_UpdatesAssignment class we get some information on the property we want to set, DPLocality:

First note that Qualifiers calls out that this is an integer formed using a bitmask (bits), concocted using flags.

Qualifiers: [not_null, bits]

It is also noted that the DPLocality property defaults to the flag combination DP_DOWNLOAD_FROM_LOCAL | DP_DOWNLOAD_FROM_REMOTE (0x50).

We have an assertion here, it states that when these two flags are combined they weigh in at 0x50 (hex), or 80 in decimal.


So we know that DP_DOWNLOAD_FROM_LOCAL and DP_DOWNLOAD_FROM_REMOTE are both set by default, and if we build ourselves a bit-table in excel, which I do a little bit further into this post, we can essentially figure out those two flags bit positions.

When these two flags are set the following radio button switches to “Download…”: