ConfigMgr – Windows 10 Servicing – Step by Step

Today I would like to show you, how you can implement an Upgrade of a Windows 10 Installation through the Servicing Option.
I always think of two things, when I have to decide to use an Upgrade Task Sequence or the Servicing Model, and those are:

  • Do I want to use the Option that the clients will download the Windows 10 Sources from the Microsoft Update Servers?
  • When I use an Upgrade Task Sequence, I can copy most of the steps from the regular OSD Task Sequence
  • But for this blog post, I would like to show my setup for the servicing model.

    First of all, I have already written a post about the Update process in a slightly overview manner, you will find this post here: My old post

    The first step would be to determine which packages you would like to implement for the upgrade. Based on the fact, that I use English as base language, I will add the Feature on Demand (FoD) packages, and I will also add the feature package NetFx3. My Setup does also contain the German Langauge, which requires me to add the corresponding Language Pack and FoD Packages. The following picture shows the folder of my Packages which I want to include within the Windows 10 Installation:

    When this is done, you will have to decide, where you would like to store the packages and also some script files on the System Disk of the current Client. I have decided to create a folder named C:\LP, which is hidden, and two subfolders called LP and Scripts, I would also recommend to modify the default User Permissions, if you don’t want that standard users can change the scripts.

    To achieve the copy jobs which are required, I will come back to those scripts later, I have decided to use Configuration Items, rather than an Application or an Old-School Package within the Software Library. But this will require that my clients are able to access those files, because they will not download it from a Distribution Point. I am using Distributed File Services (DFS) to have a single UNC Path, where the clients can download the packages (with BITS of course).
    I have decided where my package location resides and have copied all the required packages and scripts to this location:

    Then I went on and created a Configuration item named “Windows 10 Servicing CI”:

    I only selected Windows 10 (x64):

    Within the next step of the Wizard, click on “New…” to add a setting, and the following Windows appears:

    Make sure to have selected the “Setting Type” Script, and as “Data Type” “String”, as shown in the picture above. Afterwards click on “Add Script…” within the Discovery Script Section, and the following script block windows will appear:

    Make sure that “Windows Powershell” is selected as script language. The script will simply check both file locations, and if the size is not the same, the CI will be reported as non-compliant. You may want to extend the detection script to your needs, as example if you have multiple languages and want to copy only some languages to the client. I have used the following script for this purpose (you will find a zip Download at the end of the blog as well):
    #Variables
    $LocalFolderLP = "C:\LP\LP"
    $PathToLanguagePacks = "\\deheim.hosebei.ch\hosebeiDFSroot\Deployment\OS\Windows 10\LanguagePacks\1703"

    $Compliance = "Non-Compliant"

    #Receive transferred jobs, but not yet finished
    $bitstransfers = Get-BitsTransfer -AllUsers
    foreach($bitsjob in $bitstransfers) {
    if($bitsjob.Jobstate -eq "Transferred") {
    #End old Transfers due script execution errors
    Complete-BitsTransfer -BitsJob $bitsjob
    }
    }

    #Start Main Script
    If((Test-Path $LocalFolderLP) -eq $false) {
    $Compliance = "Non-Compliant"
    }
    else {
    #Measure both Locations
    $SizeOfLPsSource = Get-ChildItem -Path $PathToLanguagePacks -Filter *.cab | Measure-Object -property length -sum
    $SizeOfLPsDestination = Get-ChildItem -Path $LocalFolderLP -Filter *.cab | Measure-Object -property length -sum
    If($SizeOfLPsSource.sum -ne $SizeOfLPsDestination.Sum) {
    $Compliance = "Non-Compliant"
    }
    else {
    $Compliance = "Compliant"
    }
    }
    #Return the result
    $Compliance

    Close this Windows by a click on “OK”. Then go ahead and click on “Add Script…” witin he Remedation Script section, and add the following script to the script block:

    #Copy the Language Pack Files
    Function Start-LPBitsJob([System.IO.FileInfo]$CABFileToCopy, [string]$LocalLPFolder) {
    $BitsJob = $null
    $BitsJob = Start-BitsTransfer -Source $CABFileToCopy.FullName -Destination $LocalLPFolder -TransferType Download -Asynchronous -DisplayName "CAB Downloads"
    }

    #Variables
    $LocalFolderLP = "C:\LP\LP"
    $PathToLanguagePacks = "\\deheim.hosebei.ch\hosebeiDFSroot\Deployment\OS\Windows 10\LanguagePacks\1703"

    #Start Main Script
    if((Test-Path $LocalFolderLP) -eq $false) {
    New-Item $LocalFolderLP -ItemType Directory
    }
    $MakeHidden = Get-Item "C:\LP" -Force
    $MakeHidden.Attributes="Hidden"
    if((Test-Path $LocalFolderScripts) -eq $false) {
    New-Item $LocalFolderScripts -ItemType Directory
    }

    $CABFilesToCopy = Get-ChildItem -Path $PathToLanguagePacks\* -Include *.cab
    foreach($cabFile in $CABFilesToCopy) {

    if($cabFile.length -ne (Get-Item ($LocalFolderLP + "\" + $cabFile.Name)).Length) {
    #Write-Host "not same"
    Start-LPBitsJob $cabFile $LocalFolderLP
    }
    else {#Write-Host "same"
    }
    }

    $AllBitsDownloads = Get-BitsTransfer -Name "CAB Downloads"
    $DLFinishedLoop = $false
    while ($DLFinishedLoop -eq $false) {
    $DLFinished =$true
    foreach($BITSDownload in $AllBitsDownloads) {
    if($BITSDownload.JobState -ne "Transferred") {
    $DLFinished = $false
    }
    }
    if($DLFinished -ne $false) {
    $DLFinishedLoop = $true
    }

    sleep 1
    #write-host "loop"
    }

    #Now finish all Transfers
    $AllBitsDownloads | Complete-BitsTransfer

    As you can see, I’m using a BITS Transfer to download the packages and all packages are checked (based on the size), and a specific package is only downloaded if required. For more information about starting a BITS download through Powershell consult this Microsoft document: Using Windows PowerShell to Create BITS Transfer Jobs
    Your remediation script block should look like this:

    When this is done, we can switch to the “Compliance Rule” Tab and click on “New…”:

    Fill the opened window with the required information, make sure to check the Box at “Run the specified remediation script when the setting is noncompliant”:

    By clicking on OK twice, the first setting within the configuration item is finished. Now it is time to add the setting to copy the setupconfig.ini file, which is required to instruct the Windows Upgrade to use the downloaded packages and to execute a script after the installation. For more information about the setupconfig.ini refer to this Microsoft Document: Use Setupconfig.ini to install Windows
    My SetupConfig.ini is quite simple, I’m still using the same as outlined in the old post.
    Now a click on “New…” will open again a new setting:

    In the opened window, fill out the settings as shown below:

    Add the following script to the Discovery section:

    #Path to setupconfig File
    $SourceFileLocation = "\\deheim.hosebei.ch\hosebeiDFSroot\Deployment\OS\Windows 10\LanguagePacks\1703\SetupConfig.ini"

    $Compliance = "Not-Compliant"
    if((Test-Path -Path "$env:SystemDrive\Users\Default\AppData\Local\Microsoft\Windows\WSUS\setupconfig.ini") -eq $false) {
    $Compliance = "Not-Compliant"
    }
    elseif((Get-Item "\\deheim.hosebei.ch\hosebeiDFSroot\Deployment\OS\Windows 10\LanguagePacks\1703\SetupConfig.ini").Length -ne (Get-Item "$env:SystemDrive\Users\Default\AppData\Local\Microsoft\Windows\WSUS\setupconfig.ini").Length) {
    $Compliance = "Not-Compliant"
    }
    else {
    $Compliance = "Compliant"
    }
    $Compliance

    My remediation script, which will copy the setupconfig.ini to the correct place does not change the folder permissions of the WSUS folder. I would recommend for your environment to change the folder, or at least the file permission, that a regular user can’t modify the setupconfig.ini File. This is my remediation script for the ini file:

    if((Test-Path "C:\Users\Default\AppData\Local\Microsoft\Windows\WSUS") -eq $false) {
    New-Item -Path "C:\Users\Default\AppData\Local\Microsoft\Windows\WSUS" -ItemType Directory
    }
    Copy-Item -Path "\\deheim.hosebei.ch\hosebeiDFSroot\Deployment\OS\Windows 10\LanguagePacks\1703\SetupConfig.ini" -Destination "$env:SystemDrive\Users\Default\AppData\Local\Microsoft\Windows\WSUS\SetupConfig.ini" -Force

    When you have added both scripts, switch to the Compliance Rules Tab, and add a new rule by a click on “New…”, fill out the opened rule window as in the following picture:

    Make sure again to have ticked the box to run the remediation script.
    And the last CI setting will copy the scripts that are required to be executed after the Windows Upgrade is done, but before a user can logon. The process is the same like for the two settings we already have created. Here is the Discovery Script which I’m using:

    $LocalFolderScripts = "C:\LP\Scripts"
    $SourceFileLocation = "\\deheim.hosebei.ch\hosebeiDFSroot\Deployment\OS\Windows 10\LanguagePacks\1703"

    $Compliance = "Not-Compliant"

    $AllLocalScripts = Get-ChildItem -Path $LocalFolderScripts\* -Include *.bat,*.vbs,*.ps1
    $AllSourceScript = Get-ChildItem -Path $SourceFileLocation\* -Include *.bat,*.vbs,*.ps1
    if($AllLocalScripts.Count -ne $AllSourceScript.Count) {
    $Compliance = "Not-Compliant"
    }
    $SizeOfScriptsSource = Get-ChildItem -Path $SourceFileLocation\* -Include *.bat,*.vbs,*.ps1 | Measure-Object -property length -sum
    $SizeOfScriptsDestination = Get-ChildItem -Path $LocalFolderScripts\* -Include *.bat,*.vbs,*.ps1 | Measure-Object -property length -sum
    if($SizeOfScriptsSource.sum -ne $SizeOfScriptsDestination.sum) {
    $Compliance = "Not-Compliant"

    }
    else {
    $Compliance = "Compliant"
    }
    $Compliance

    And this is the remediation script:

    Function Start-BitsJob([System.IO.FileInfo]$FileToCopy, [string]$LocalFolder) {
    $BitsJob = $null
    $BitsJob = Start-BitsTransfer -Source $FileToCopy.FullName -Destination $LocalFolder -TransferType Download -Asynchronous

    while (($BitsJob.JobState -eq "Transferring") -or ($BitsJob.JobState -eq "Connecting")) { sleep 5;} # Poll for status, sleep for 5 seconds, or perform an action.

    Switch($BitsJob.JobState)
    {
    "Transferred" {Complete-BitsTransfer -BitsJob $BitsJob}
    "Error" {$BitsJob | Format-List } # List the errors.
    default {"Other action"} # Perform corrective action.
    }
    }

    $LocalFolderScripts = "C:\LP\Scripts"
    $PathToLanguagePacks = "\\deheim.hosebei.ch\hosebeiDFSroot\Deployment\OS\Windows 10\LanguagePacks\1703"

    if((Test-Path "C:\LP\Scripts") -eq $false) {
    New-Item -Path "C:\LP\Scripts" -ItemType Directory
    }

    $ScriptFilesToCopy = Get-ChildItem -Path $PathToLanguagePacks\* -Include *.ps1,*.vbs,*.bat
    foreach($ScriptFile in $ScriptFilesToCopy) {
    if($ScriptFile.length -ne (Get-Item ($LocalFolderScripts + "\" + $ScriptFile.Name)).Length) {
    #Write-Host "not same"
    Start-BitsJob $ScriptFile $LocalFolderScripts
    }
    else {#Write-Host "same"}
    }

    When you have finished adding the third setting, your Configuration Item should look like this:

    If you click on Next, the wizard will show you the created Compliances Rules according to the settings, make sure that “Remediate” is on Yes:

    Finish the wizard by clicking on Next twice and close it afterwards. Now we need to create a Baseline and just add our Compliance Item to it:

    After a click on OK, the baseline is created and ready to be deployed on a collection. Within the deployment wizard of the baseline, make sure to tick the check box at “Remediate noncompliant rules when supported”:

    Now everything is quite good and you can create a collection based on the baseline:

    And here is my batch file which I run after the Windows 10 Upgrade:
    mkdir C:\LP\1703_OK
    powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\LP\Scripts\Remove-Apps.ps1"

    The PowerShell Script “Remove-Apps.ps1” removes Universal apps which I don’t want on the clients. This is the content of Remove-Apps.ps1:

    #Initalize Array
    $AppstoRemove = New-Object System.Collections.ArrayList

    #Add Apps to Array
    $AppstoRemove.Add("Microsoft.NetworkSpeedTest_1.0.0.23_x64__8wekyb3d8bbwe") #NetworkSpeedTest
    $AppstoRemove.Add("46928bounde.EclipseManager_2.2.1.31_neutral__a5h4egax66k6y") #Eclipse Manager
    $AppstoRemove.Add("6Wunderkinder.Wunderlist_3.6.25.0_x64__b4cwydgxqx59r") #Wunderlist
    $AppstoRemove.Add("Microsoft.MicrosoftOfficeHub_17.8017.5925.0_x64__8wekyb3d8bbwe") #Get Office Sneak App
    $AppstoRemove.Add("Microsoft.BingTranslator_4.7.0.0_x64__8wekyb3d8bbwe") #Bing Translator
    $AppstoRemove.Add("D5EA27B7.Duolingo-LearnLanguagesforFree_2017.112.1.0_x64__yx6k7tf7xvsea") #Language Learning App
    #$AppstoRemove.Add("Microsoft.SkypeApp_11.12.112.0_x64__kzf8qxf38zg5c") #Skype for Business App
    #$AppstoRemove.Add("Microsoft.Office.OneNote_17.7967.57741.0_x64__8wekyb3d8bbwe") #OneNote App

    $AppstoRemove | Remove-AppxPackage

    You might want also to configure the Language settings as they were before. Then you can use the solution from Roger Zander, to apply Language Settings with an XML: Windows 10 MUI challenge
    Here you will find a Zip-File with all my scripts used within this blog: Windows 10 Upgrade Scripts

    Now you can deploy the upcoming release to your clients, just like a regular update.

    If you think this post was useful, please leave a comment, and if you see issues or you can recommend better techniques, leave a comment as well, or get in touch with me on twitter.

    Leave a Reply

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out / Change )

    Twitter picture

    You are commenting using your Twitter account. Log Out / Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out / Change )

    Google+ photo

    You are commenting using your Google+ account. Log Out / Change )

    Connecting to %s