Optional Feature CSP

One of the common things with Windows is to activate or deactivate while provisioning a machine are Features.
Good for us there is a CSP available since Windows 1903 to manage those.

Disclaimer: While this CSP is in Windows 10, this is an undocumented feature, I can’t guarantee that this will 100% work in the future, also, all my testing were done with Workspace ONE UEM which allow you to send any command to the devices and this worked perfectly.

Get the feature name

With Windows 10, there is 2 types of feature, Optional Feature and Feature On Demand.
To use the CSP we need the real/internal name of those feature

Optional Features

Optional features are part of the OS, like IIS, Hyper-V and so on.
To get the list of Optional Feature available and their name, we use this PowerShell command

Get-WindowsOptionalFeature -Online | Format-Table

Features On Demand

Feature On Demand can be added at anytime like an application, this currently include language pack (OCR, Text to Speech, Font, etc.), RSAT Tools, .NET Fx3 among others.
On demand features don’t use the concept of parent like the optional does, i.e IIS is the parent of the FTP server, it use the concept of satellites and dependencies (introduced in 1809) which are handled by the CSP. E.g: RSAT have satellites and dependencies.
To get the list of Feature On Demand, use the following PowerShell command.

Get-WindowsCapability -Online | Format-Table

It returns the name and the current state of the feature.
More detail also here : https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/features-on-demand-non-language-fod

Description of the CSP

OptionalFeature CSP is not available in the online documentation. However the DDF file is available. This the definition file of the CSP so we know how to use it.
There is 3 node in the CSP, which straight forward to understand.

  • Enable: To enable a feature
  • Disable: To disable a feature
  • Inventory: To return a list of feature.

We will see that the behavior of the CSP is the same as the PowerShell commands and their switches. PowerShell equivalent are detailed when they exist.

The CSP store the information in the following registry key.

HKEY_LOCAL_MACHINE\SYSTEM\Setup\CSP\OptionalFeatures

Enable a Feature

In the Enable node we have, FeatureName which have to be changed by the feature name, optional or on demand, then inside we have:

  • Enable
  • LimitWUAccess
  • EnableParentFeatures
  • Status
  • ErrorCode

The node use the following registry key to store information

HKEY_LOCAL_MACHINE\SYSTEM\Setup\CSP\OptionalFeatures\Enable

Enable

./Device/Vendor/MSFT/OptionalFeatures/Enable/<FeatureName>/Enable

This leaf notify the device to begin the installation of the feature
No notification is sent back to the MDM server after the feature have been enabled. It supportExec.

PowerShell equivalent: Enable-WindowsOptionalFeature -online and Add-WindowsCapability -online

LimitWUAccess

./Device/Vendor/MSFT/OptionalFeatures/Enable/<FeatureName>/LimitWUAccess

This leaf define if Windows Update can be reached or not to download the feature. The type is Boolean and it support Get, Add, Delete, and Replace.

  • True: Use the Alternate Source defined in the GPO to download.
  • False: Use the Alternate Source defined in the GPO then use Windows Update to download.

The group policy setting Specify settings for optional component installation and component repair is located in Computer Configuration > Administrative Templates > System
This policy allow to define an Alternate Source for the package to be downloaded usefull on closed or filtered network.
You can follow this article Configure a Windows Repair Source to know more about it.

Powershell equivalent: -LimitAccess switch for Enable-WindowsOptionalFeature and Add-WindowsCapability

EnableParentFeature

./Device/Vendor/MSFT/OptionalFeatures/Enable/<FeatureName>/EnableParentFeature

EnableParentFeature control the installation of the parent feature. The type is Boolean and it support Get, Add, Delete, and Replace.

  • true: Install parent feature and all of its default dependencies.
  • false: Only enable the feature.

If the feature have dependencies on the parent, e.g: FTP server requires IIS, then you need to enable the parent feature while enabling the feature itself.
When enabling the parent, it will install the default dependencies of each parent, so you may have more than what it is required. This behavior is the same between PowerShell and the CSP.

Below is an example for FTP Server installation, on the left done via GUI, on the right with PowerShell or the CSP:

Feature Installed via GUI Feature Installed via CSP or PS
IIS-WebServerRole IIS-WebServerRole
IIS-FTPServer IIS-WebServer
IIS-FTPSvc IIS-CommonHttpFeatures
IIS-HttpErrors
IIS-ApplicationDevelopment
IIS-HealthAndDiagnostics
IIS-HttpLogging
IIS-Security
IIS-RequestFiltering
IIS-Performance
IIS-WebServerManagementTools
IIS-StaticContent
IIS-DefaultDocument
IIS-DirectoryBrowsing
IIS-HttpCompressionStatic
IIS-ManagementConsole
IIS-FTPServer
IIS-FTPSvc

Powershell equivalent: -All for Enable-WindowsOptionalFeature, Add-WindowsCapability don’t use the concept of parent.

Status

./Device/Vendor/MSFT/OptionalFeatures/Enable/<FeatureName>/Status

This leaf return the status of the last enable executed command. Type is integer support only Get.The value are:

  • 1: Success
  • 2: Pending
  • 16: Action failed

ErrorCode

./Device/Vendor/MSFT/OptionalFeatures/Enable/<FeatureName>/ErrorCode

This leaf return the HRESULT (Win32 error) of the last enable executed command. Type is integer and support only Get.

Examples

Telnet Client

Telnet client is a simple optional feature and don’t have any parents, we can omit the EnableParent leaf. We can use the following SyncML.

<Exec>
 <CmdID>3</CmdID>
 <Item>
  <Target>
   <LocURI>
    ./Device/Vendor/MSFT/OptionalFeatures/Enable/TelnetClient/Enable
   </LocURI>
  </Target>
  <Meta>
   <Format xmlns="syncml:metinf">chr</Format>
   <Type>text/plain</Type>
  </Meta>
  <Data></Data>
 </Item>
</Exec>

.Net Framework 3.5

The .NET Framework 3.5 is not part of Windows 10 but many times it is required by applications. It is available as a feature on demand.
Note: the ~ are part of the feature name.

<Exec>
 <CmdID>3</CmdID>
 <Item>
  <Target>
   <LocURI>
    ./Device/Vendor/MSFT/OptionalFeatures/Enable/NetFX3~~~~/Enable
   </LocURI>
  </Target>
  <Meta>
   <Format xmlns="syncml:metinf">chr</Format>
   <Type>text/plain</Type>
  </Meta>
  <Data></Data>
 </Item>
</Exec>

Keyboard Filter

Client-KeyboardFilter is part of Device Lockdown optional feature so we need to specify the EnableParentFeatures leaf, we put it at true.

<Exec>
 <CmdID>3</CmdID>
 <Item>
  <Target>
   <LocURI>
    ./Device/Vendor/MSFT/OptionalFeatures/Enable/Client-KeyboardFilter/Enable
   </LocURI>
  </Target>
  <Meta>
   <Format xmlns="syncml:metinf">chr</Format>
   <Type>text/plain</Type>
  </Meta>
  <Data></Data>
 </Item>
</Exec>
<Replace>
 <CmdID>7</CmdID>
 <Item>
  <Target>
   <LocURI>
    ./Device/Vendor/MSFT/OptionalFeatures/Enable/Client-KeyboardFilter/EnableParentFeatures
   </LocURI>
  </Target>
  <Meta>
   <Format xmlns="syncml:metinf">bool</Format>
   <Type>text/plain</Type>
  </Meta>
  <Data>true</Data>
 </Item>
</Replace>

Disable a Feature

In the Disable node we have, FeatureName which have to be changed by the feature name, optional or on demand, then inside we have:

  • Disable
  • Status
  • ErrorCode

The node use the following registry key to store information

HKEY_LOCAL_MACHINE\SYSTEM\Setup\CSP\OptionalFeatures\Disable

Disable

./Device/Vendor/MSFT/OptionalFeatures/Disable/<FeatureName>/Disable

This leaf notify the device to begin the removal of the feature
No notification is sent back to the MDM server after the feature have been disabled. It support Exec.

PowerShell equivalent: Disable-WindowsOptionalFeature -online and Remove-WindowsCapability -online

Status

./Device/Vendor/MSFT/OptionalFeatures/Disable/<FeatureName>/Status

This leaf return the status of the last enable executed command. Type is integer support only Get.The value are:

  • 1: Success
  • 2: Pending
  • 16: Action failed

ErrorCode

./Device/Vendor/MSFT/OptionalFeatures/Disable/<FeatureName>/ErrorCode

This leaf return the HRESULT (Win32 error) of the last enable executed command. Type is integer and support only Get.

Examples

SMB 1.0/CIFS File Sharing Support

SMBv1 is one of the feature that a lot of customer look to disable for security purpose. While new Windows 10, from 1709, have SMBv1 disabled by default, system installed with earlier version which have been upgraded still have SMBv1 enabled.
To disable the feature completely, we are targeting the parent feature, SMB1Protocol, to remove child features at the same time.

<Exec>
 <CmdID>3</CmdID>
 <Item>
  <Target>
   <LocURI>
    ./Device/Vendor/MSFT/OptionalFeatures/Disable/SMB1Protocol/Disable
   </LocURI>
  </Target>
  <Meta>
   <Format xmlns="syncml:metinf">chr</Format>
   <Type>text/plain</Type>
  </Meta>
  <Data></Data>
 </Item>
</Exec>

Inventory

In the Inventory node we have:

  • OnlyEnabledList
  • GetList
  • FeatureList
  • Status
  • ErrorCode

This node use the following registry key to store information

HKEY_LOCAL_MACHINE\SYSTEM\Setup\CSP\OptionalFeatures\Inventory

OnlyEnabledList

./Device/Vendor/MSFT/OptionalFeatures/Inventory/OnlyEnabledList

This leaf is to configure the inventory to return. Type is Boolean and support Get and Replace.

  • Get: Get the current value of the setting, return true or false.
  • Replace: Modify the setting
  • true: Return the list of enabled optional features and feature on demands.
  • false: Return all features available (optional and on demands) for the device.

The parameter is stored in the registry, as a DWORD value, here:

HKEY_LOCAL_MACHINE\SYSTEM\Setup\CSP\OptionalFeatures\Inventory\EnabledListOnly

GetList

./Device/Vendor/MSFT/OptionalFeatures/Inventory/GetList

This leaf generate the list of features, it use OnlyEnabledList setting to generate the list accordingly. It support Exec

FeatureList

./Device/Vendor/MSFT/OptionalFeatures/Inventory/FeatureList

This leaf return the feature list created by GetList. If a feature is installed with Enable and GetList haven’t been called then the feature list won’t be up-to-date.

The feature list separated with UTF-8 Unicode character U+F000 which is EF 80 80 in hex.

You can decode the feature list with PowerShell using the split method.

[XML] $SyncML = Get-Content .\SyncML.xml -Encoding UTF8
$Data = $SyncML.SyncML.SyncBody.Results.Item.Data
$Data.Split([regex]::Unescape(""))

The list is stored in the registry, as a REG_SZ value, here:

HKEY_LOCAL_MACHINE\SYSTEM\Setup\CSP\OptionalFeatures\Inventory\FeatureList

Usage

The process to use the Inventory node is:

  1. Set the type of list to All or Installed Only with OnlyEnabledList
  2. Generate the list with GetList
  3. Retrieve the list with FeatureList

Status

./Device/Vendor/MSFT/OptionalFeatures/Inventory/Status

This leaf return the status of the last enable executed command. Type is integer support only Get.The value are:

  • 1: Success
  • 2: Pending
  • 16: Action failed

ErrorCode

./Device/Vendor/MSFT/OptionalFeatures/Inventory/ErrorCode

This leaf return the HRESULT (Win32 error) of the last enable executed command. Type is integer and support only Get.

Example

Here’s a SyncML example of the process to generate the list of installed feature

Set the type of list to Installed Only

<Replace>
 <CmdID>1</CmdID>
 <Item>
  <Target>
   <LocURI>
    ./Device/Vendor/MSFT/OptionalFeatures/Inventory/OnlyEnabledList
   </LocURI>
  </Target>
  <Meta>
   <Format xmlns="syncml:metinf">bool</Format>
   <Type>text/plain</Type>
  </Meta>
  <Data>true</Data>
 </Item>
</Replace>

Generate the list

<Exec>
 <CmdID>2</CmdID>
 <Item>
  <Target>
   <LocURI>
    ./Device/Vendor/MSFT/OptionalFeatures/Inventory/GetList
   </LocURI>
  </Target>
  <Meta>
   <Format xmlns="syncml:metinf">chr</Format>
   <Type>text/plain</Type>
  </Meta>
  <Data></Data>
 </Item>
</Exec>

Retrieve the list

<Get>
 <CmdID>3</CmdID>
 <Item>
  <Target>
   <LocURI>
    ./Device/Vendor/MSFT/OptionalFeatures/Inventory/FeatureList
   </LocURI>
  </Target>
 </Item>
</Get>