Deploying Windows 10 Feature Updates in your organisation can be approached in multiple ways. As we merge the perimeter with the Cloud some of us are trying to understand the best way to deliver Feature Updates to our devices. Should we deploy an in-place upgrade using a ConfigMgr task sequence? Perhaps we should push the Feature Update directly from ConfigMgr to our devices. Have we considered leveraging our CMG and using Microsoft as a source for the update binaries to save our VPNs? Maybe some of us are on a co-management journey and are looking at using WUfB. Which ever technology we are leveraging one of the questions that normally gets asked is “Can we control the update process in a way that lets us ‘Run Stuff’ after the Feature Update has completed”.
SetupConfig.ini
When a Feature Update is deployed using the Windows Update Service a file called SetupConfig.ini can be used to control some aspects of the update process. The SetupConfig.ini file can be used by admins in scenarios where there is the inability to pass specific switch parameters to setup.exe during a Windows 10 Feature Update.
Below is an example of the what the content of SetupConfig.ini might look like
[SetupConfig]
NoReboot
ShowOobe=None
Telemetry=Enable
InstallDrivers=
ReflectDrivers=
The example above is taken from
https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-setup-automation-overview
For a more comprehensive list of the commands that can be used during Windows Setup check out the following Microsoft article
https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-setup-command-line-options
The Feature Update process will look for SetupConfig.ini in the following location: –
SystemDrive\Users\Default\AppData\Local\Microsoft\Windows\WSUS\SetupConfig.ini
PostOOBE
We can specify the POSTOOBE parameter in the SetupConfig.ini file to tell the Windows Update Service to run a command after the Feature Update has been installed. If you have previously used a ConfigMgr Task Sequence to manage in-place upgrades, you may have noticed that ConfigMgr would typically reference a batch file called SetupComplete.cmd to run after the update process. It makes sense to use the same batch file name for familiarity when we use the Windows Update Service to deploy a Feature Update. The batch file can be placed anywhere on the device as long as it isn’t removed during the Feature Update process. For this reason, placing it outside of the Windows directory is quite sensible.
Below is an example of what the POSTOOBE reference might look like in the SetupConfig.ini
[SetupConfig]
NoReboot
ShowOobe=None
Telemetry=Enable
InstallDrivers=
ReflectDrivers=
POSTOOBE=C:\ProgramData\FeatureUpdate\SetupComplete.cmd
A Batch File…Really?
SetupComplete.cmd is simply used to kick off any script or process we want to run after the Feature Update has been installed. You can specify multiple commands within the batch file or call your other custom scripts. An example of what SetupComplete.cmd may contain could be: –
PowerShell.exe -ExecutionPolicy Bypass -File C:\ProgramData\FeatureUpdate\SetupComplete.ps1 -WindowStyle Hidden
Practical Example
“Feature Update – Post OOBE Script“
Because we are using the Windows Update Service to deliver the Feature Update (not a Task Sequence), I would typically deploy my custom POSTOOBE scripts and files as an application in ConfigMgr or Win32app in Intune. In the example that follows I will show you how to leverage SetupConfig.ini to run a PowerShell script during POSTOOBE and copy some files back into the Windows directory that may have been overwritten during the Feature Update.
Ensure the application is deployed to the client before the client is targeted for a Feature Update
The example application we will create is called Feature Update – Post OOBE Script – 1.09.04. We will deploy the application with ConfigMgr. The application uses version control and installs files used for POSTOOBE to C:\ProgramData\FeatureUpdate\<version>. The reference to the POSTOOBE command in the SetupConfig.ini will change to reflect the version of the application we deploy from ConfigMgr e.g.
[SetupConfig] POSTOOBE=C:\ProgramData\FeatureUpdate\1.09.04\SetupComplete.cmd
Application Intent
Our application will do the following: –
- Create/Replace SetupConfig.ini at SystemDrive\Users\Default\AppData\Local\Microsoft\Windows\WSUS\SetupConfig.ini
- Create a folder structure at: – SystemDrive\ProgramData\FeatureUpdate\<version>
- Create SetupComplete.cmd and add a reference to run SetupComplete.ps1. Both of these files will be saved in: – SystemDrive\ProgramData\FeatureUpdate\<version>
- Add a reference in SetupConfig.ini for POSTOOBE to run SystemDrive\ProgramData\FeatureUpdate\<version>\SetupComplete.cmd
- Version.txt should contain the version number of the application being deployed. This is useful if you change the application intent and need to ensure your devices have the up-to-date files/scripts in place before they attempt a Feature Update. The version number in this file is used to dynamically build the different elements of the solution, including the actual path where the solution will be installed. In our example, Version.txt will contain one line of text: 1.09.04
- Add a reference in SetupConfig.ini to set the Priority of Windows Setup to “Normal”. Some parts of a Feature Update are set to run with a low priority. Microsoft have published best practice for Feature Update priority here: – https://docs.microsoft.com/en-us/windows/deployment/update/feature-update-user-install)
- Copy the Files folder to SystemDrive\ProgramData\FeatureUpdate\<version>. This folder contains the files we want to copy back to the SystemDrive during POSTOOBE. The Files folder structure, in your application content source, should resemble the existing Windows disk layout. In this example, we are copying back some Default User Account Pictures to C:\ProgramData\Microsoft\User Account Pictures and a new wallpaper to C:\Windows\Web\Wallpaper
Application Installation Flow
Below is an overview of the installation flow for the Feature Update – Post OOBE Script application we will deploy from ConfigMgr
Application Scripts
We will use 3 scripts to install / maintain our “Feature Update – Post OOBE Script” application
- Install_SetupComplete.ps1
- Uninstall_SetupComplete.ps1
- Detect_SetupComplete.ps1
<# .Synopsis Created on: 10/04/2021 Created by: Ben Whitmore Filename: Install_SetupComplete.ps1 .Description Script to install a custom SetupComplete.cmd which will be used POSTOOBE when installing a Feautre Update using Windows Update. Files in the "Files" source folder should resemble the structure from the root $env:SystemDrive e.g. If you want to copy custom account pictures to your device after the OOBE they should be placed in ..\Files\ProgramData\Microsoft\User Account Pictures Version.txt should contain the current version of SetupComplete.cmd. The application is designed with version control in mind. The version value will determine the path in $env:SystemDrive\ProgramData which the "FeatureUpdate" folder is created and the reference in SetupConfig.ini will also point to $env:SystemDrive\ProgramData\FeatureUpdate\<version>\SetupComplete.cmd #> #Setup environment $ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition $SetupCompleteVersionFile = Join-Path -Path $ScriptPath -ChildPath "Version.txt" #Get intended Version of SetupComplete Try { If (Test-Path -Path $SetupCompleteVersionFile) { $SetupCompleteVersion = Get-Content $SetupCompleteVersionFile $SetupCompleteLocation = Join-Path -Path "$($env:SystemDrive)\ProgramData\FeatureUpdate" -ChildPath $SetupCompleteVersion $SetupCompleteCMD = Join-Path -Path $SetupCompleteLocation -ChildPath "SetupComplete.cmd" } } Catch { Write-Host "Error getting SetupComplete Version from Script Source Directory. Does version.txt exist?" } Try { #Setup Directory and create SetupComplete.cmd New-Item $SetupCompleteLocation -ItemType Directory -Force New-Item $SetupCompleteCMD -ItemType File -Force #Add correct version of SetupComplete.ps1 to run post Feature Update Add-Content -Path $SetupCompleteCMD -Value "powershell.exe -executionpolicy bypass -file $($SetupCompleteLocation)\SetupComplete.ps1 -WindowStyle Hidden" } Catch { Write-Host "Error creating file ""$($SetupCompleteCMD)""" } Try { #Declare items to copy to Feature Update staging folder $SetupFiles = @("SetupComplete.ps1", "Files", "Version.txt") #Copy Files from Script Root to Feature Update staging folder Foreach ($File in $SetupFiles) { $FiletoCopy = Join-Path -Path $ScriptPath -ChildPath $File -ErrorAction SilentlyContinue Try { Copy-Item -Path $FiletoCopy -Destination $SetupCompleteLocation -Force -Recurse } Catch { Write-Warning: "Error copying item ""$($File)"" to ""$($SetupCompleteLocation)""" } } } Catch { Write-Warning "Error setting up the Feature Update staging folder" } #Create SetupConfig.ini #Source https://docs.microsoft.com/en-us/windows/deployment/update/feature-update-user-install#step-2-override-the-default-windows-setup-priority-windows-10-version-1709-and-later #Variables for SetupConfig $iniFilePath = "$env:SystemDrive\Users\Default\AppData\Local\Microsoft\Windows\WSUS\SetupConfig.ini" $PriorityValue = "Normal" $iniSetupConfigSlogan = "[SetupConfig]" $iniSetupConfigKeyValuePair = @{"Priority" = $PriorityValue; "PostOOBE" = $SetupCompleteCMD } #Init SetupConfig content $iniSetupConfigContent = @" $iniSetupConfigSlogan "@ Try { #Setup SetupConfig Directory #Build SetupConfig content with settings foreach ($k in $iniSetupConfigKeyValuePair.Keys) { $val = $iniSetupConfigKeyValuePair[$k] $iniSetupConfigContent = $iniSetupConfigContent.Insert($iniSetupConfigContent.Length, "`r`n$k=$val") } #Write content to file New-Item $iniFilePath -ItemType File -Value $iniSetupConfigContent -Force } Catch { Write-Warning "Error creating ""$($iniFilePath)""" }
<# .Synopsis Created on: 10/04/2021 Created by: Ben Whitmore Filename: Uninstall_SetupComplete.ps1 .Description Script to uninstall a custom SetupComplete.cmd which will be used POSTOOBE when installing a Feautre Update using Windows Update. $env:SystemDrive\ProgramData\FeatureUpdate\<version>\ will be removed $env:SystemDrive\Users\Default\AppData\Local\Microsoft\Windows\WSUS\SetupConfig.ini will be removed if the following line is present in SetupConfig.ini "PostOOBE=$env:SystemDrive\ProgramData\FeatureUpdate\<version>\SetupComplete.cmd" #> #Setup environment $ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition $SetupCompleteVersionFile = Join-Path -Path $ScriptPath -ChildPath "Version.txt" #Get intended Version of SetupComplete Try { If (Test-Path -Path $SetupCompleteVersionFile) { $SetupCompleteVersion = Get-Content $SetupCompleteVersionFile $SetupCompleteLocation = Join-Path -Path "$($env:SystemDrive)\ProgramData\FeatureUpdate" -ChildPath $SetupCompleteVersion } } Catch { Write-Host "Error getting SetupComplete Version from Script Source Directory. Does version.txt exist?" } Try { #Remove Directory Remove-Item $SetupCompleteLocation -Force -Recurse } Catch { Write-Host "Error removing ""$($SetupCompleteLocation)""" } #Remove SetupConfig.ini Try { $iniFilePath = "$($env:SystemDrive)\Users\Default\AppData\Local\Microsoft\Windows\WSUS\SetupConfig.ini" if (Test-Path -Path $iniFilePath) { $SetupConfigini_Content = Get-Content $iniFilePath foreach ($line in $SetupConfigini_Content) { If ($line -like "PostOOBE=$($SetupCompleteLocation)\SetupComplete.cmd") { Remove-Item $iniFilePath -Force } } } } Catch { Write-Host "Error removing ""$($iniFilePath)""" }
<# .Synopsis Created on: 10/04/2021 Created by: Ben Whitmore Filename: Detect_SetupComplete.ps1 .Description Detection script to be deployed with ConfigMgr to detect SetupComplete.cmd in $env:SystemDrive\ProgramData\FeatureUpdate\<version>\SetupComplete.cmd and to check SetupCOnfig.ini references the correct version of the application i.e. $env:SystemDrive\Users\Default\AppData\Local\Microsoft\Windows\WSUS\SetupConfig.ini POSTOOBE will reference SetupComplete.cmd as outlined above #> $SetupCompleteVersion = "1.09.04" $SetupCompleteLocation = Join-Path -Path "$($env:SystemDrive)\ProgramData\FeatureUpdate" -ChildPath $SetupCompleteVersion $FUFilesInComplete = $Null $SetupConfigini_Valid = $Null Try { If (!(Test-Path -Path $SetupCompleteLocation)) { $FUFilesInComplete = $True } } Catch { $FUFilesInComplete = $True } Try { $iniFilePath = "$($env:SystemDrive)\Users\Default\AppData\Local\Microsoft\Windows\WSUS\SetupConfig.ini" if (Test-Path -Path $iniFilePath) { $SetupConfigini_Content = Get-Content $iniFilePath foreach ($line in $SetupConfigini_Content) { If ($line -like "PostOOBE=$($SetupCompleteLocation)\SetupComplete.cmd") { $SetupConfigini_Valid = $True } } } else { $FUFilesInComplete = $True } } Catch { $FUFilesInComplete = $True } If (($SetupConfigini_Valid) -and (!($FUFilesInComplete))) { Write-Output "Installed" }
The installation and removal scripts will reference Version.txt to install/remove the correct version of the application. We cannot reference Version.txt to get the application version when we use a script for the detection method so the version number must be manually updated within the detection script
The version number in the Detection Method script must be updated manually to reflect the content of Version.txt
Application Content Source
You should have the following files present in your application content source folder. (The Install/Uninstall and Detect scripts will not get copied to SystemDrive\ProgramData\FeatureUpdate\<version>).
- Files (Folder containing any files you wish to restore after a Feature Update
- Detect_SetupComplete.ps1
- Install_SetupComplete.ps1
- SetupComplete.ps1 (This is the script that will be launched from SetupComplete.cmd during POSTOOBE)
- Uninstall_SetupComplete.ps1
- Version.txt
Build the Application
1. Create a new Application in Configuration Manager
2. Choose to Manually specify the application information
3. Name the application Feature Update – Post OOBE Script – 1.09.04 and add a Software version: 1.09.04
4. Add a Deployment Type of type Script Installer
5. Name the Deployment Type Feature Update – Post OOBE Script – 1.09.04
6. Specify the content location for application source files as mentioned in the previous section “Application Content Source”
7. Set the Installation program to
powershell.exe -executionpolicy bypass -file "install_setupcomplete.ps1"
8. Set the Uninstallation program to
powershell.exe -executionpolicy bypass -file "uninstall_setupcomplete.ps1"
9. Set the Detection Method to use the Detect_SetupComplete.ps1 PowerShell script. Remember the version in the detection script must match the value within Version.txt
10. Set the user experience settings to
Installation Behaviour: Install for System
Logon requirement: Whether or not a user is logged on
Installation program visibility: Hidden
Maximum allowed run time (minutes): 15
11. Add a Requirement for the Operating System to equal Windows 10
12. Deploy the application as Required to the device collection that will be targeted for the Feature Update
Review Installation
You should observe the following on your targeted devices:-
- SetupConfig.ini exists at SystemDrive\Users\Default\AppData\Local\Microsoft\Windows\WSUS\SetupConfig.ini and has the Priority and POSTOOBE values have been populated as expected
- The folder SystemDrive\ProgramData\FeatureUpdate\1.09.04 exists and contains the expected files and folders
- SetupComplete.cmd contains the following line
powershell.exe -executionpolicy bypass -file C:\ProgramData\FeatureUpdate\1.09.04\SetupComplete.ps1 -WindowStyle Hidden
- SetupComplete.ps1 contains the following
$ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition #Copy Files from Script Root to Feature Update staging folder Try { $FilestoCopy = Join-Path -Path $ScriptPath -ChildPath "Files" Robocopy.exe $FilestoCopy $env:systemdrive\ /e /z /r:5 /w:1 /eta } Catch { Write-Warning: "Error copying files to ""$($env:systemdrive)""" } #Do other stuff here...
POSTOOBE Review
After the Feature Update has completed, you can review Setupact.log in the C:\Windows\Panther folder and see that your script was used during POSTOOBE
And in our example, the default User Account Pictures were restored and the new wallpaper copied to the Windows\Web\Wallpaper directory (and set by policy)
Summary
In this blog post we looked at how we could manipulate the Feature Update process when it is being deployed using the Windows Update Service. Now you understand the concept of SetupConfig.ini and POSTOOBE you can very easily adapt the application to be deployed as a Win32app in Intune. There are other actions you can leverage using SetupConfig.ini to give your users the best experience during a Feature Update.
SetupConfig.ini is not as versatile as using a Task Sequence but it gives us some degree of control over the update process. I am really excited to see the new feature in ConfigMgr 2103 where we can deliver Feature Updates in a Task Sequence!
Further reading and best practice can be found below
Thank you Ben for this example!
My inquiry is missing the direct point of this post.
You sparked my interest again in updating the default account picture. Are you aware if it’s possible to set this starting Default Account Picture to a company logo however, allow users O365 profile picture if they’ve set one to overrule that default picture displayed only impacting Windows10.
Thank you!
Hi Ben,
Thank you for the great article. Exploring your solution to go away from Task Sequences. I noticed that the SetupConfig.ini was overwritten with the new values of the Install_SetupComplete.ps1 and wanted to know if there was any consideration to only updating the file with the new PostOOBE and Priority options since there are other important options like ReflectDrivers that will be lost?
Thanks Gordon – that’s a good call. Do you have a scenario with custom settings in the SetupConfig.ini?
We use WSUS instead of SCCM for patching (just makes more sense in our environment).
And the “InstallDrivers= ” path is very convenient in combination with the “PreCache” function of your Modern Driver Management System. Basically we just PreCache the drivers on every system to the same Windows Temp path, before we release the next Feature Upgrade in WSUS. 🙂
Great use of the feature 🙂
Really great article, very easy to follow. Thank you.