Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,52 @@ changedProperties:
- capabilities
```

## Install a feature on demand using an offline source

To allow DISM to install capabilities that are not present on the machine in an offline
environment, add the offline source path to the `sourcePaths` property.

```powershell
$instance = @{
sourcePaths = @('z:\sources\SxS')
capabilities = @(
@{
identity = 'NetFX3~~~~'
state = 'Installed'
}
)
} | ConvertTo-Json -Depth 3

dsc resource set --resource Microsoft.Windows/FeatureOnDemandList --input $instance
```

When the resource installs the capability, DSC returns the updated state:

```yaml
beforeState:
sourcePaths:
- z:\sources\SxS
capabilities:
- identity: NetFX3~~~~
state: NotPresent
displayName: ''
description: ''
downloadSize: 0
installSize: 487706170
afterState:
sourcePaths:
- z:\sources\SxS
capabilities:
- identity: NetFX3~~~~
state: Installed
displayName: ''
description: ''
downloadSize: 0
installSize: 487706170
changedProperties:
- capabilities
```

## Manage multiple capabilities in a single operation

You can install or remove multiple capabilities in a single **Set** call by specifying multiple
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ The following list describes the properties for the resource.

- [capabilities](#capabilities) - An array of capability entries.

- **Instance properties:** <a id="instance-properties"></a> The following properties are optional.
They define the desired state for an instance of the resource.

- [sourcePaths](#sourcePaths) - The location of the source files to use for installation if
necessary.

- **Read-only properties:** <a id="read-only-properties"></a> The resource returns the following
properties, but they aren't configurable. For more information about read-only properties, see
the "Read-only resource properties" section in [DSC resource properties][05].
Expand Down Expand Up @@ -277,6 +283,26 @@ IsReadOnly : true
The size in bytes that the capability occupies on disk after installation. This property is returned
by **Get** and **Export** operations.

### sourcePaths

<details><summary>Expand for <code>sourcePaths</code> property metadata</summary>

```yaml
Type : array
IsRequired : false
IsKey : false
IsReadOnly : false
```

</details>

Supplied at the top level of the **Set** operation, indicates the location of the source files to
use for installation if necessary. The DISM API will search these paths if the feature files are
not available in the local feature store. See the [feature on demand repository documentation][08]
for more information on valid sources.

This property is optional and will be omitted from the response if no value is provided.

### _restartRequired

<details><summary>Expand for <code>_restartRequired</code> property metadata</summary>
Expand Down Expand Up @@ -314,6 +340,12 @@ The following snippet contains the JSON Schema that validates an instance of the
"additionalProperties": true
}
},
"sourcePaths": {
"type": "array",
"items": {
"type": "string"
}
},
"capabilities": {
"type": "array",
"items": {
Expand Down Expand Up @@ -378,3 +410,4 @@ Common causes include:
[05]: ../../../../../concepts/resources/properties.md#read-only-resource-properties
[06]: ../OptionalFeatureList/index.md
[07]: /windows-hardware/manufacture/desktop/features-on-demand-v2--capabilities
[08]: /windows-hardware/manufacture/desktop/features-on-demand-v2--capabilities?view=windows-11#fod-repositories
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,50 @@ changedProperties:
- features
```

## Enable an optional feature using an offline source

To allow DISM to install features that are not present on the machine in an offline environment,
add the offline source path to the `sourcePaths` property.

```powershell
$instance = @{
sourcePaths = @('z:\sources\SxS')
features = @(
@{
featureName = 'NetFx3'
state = 'Installed'
}
)
} | ConvertTo-Json -Depth 3

dsc resource set --resource Microsoft.Windows/OptionalFeatureList --input $instance
```

When the resource enables the feature, DSC returns the updated state:

```yaml
beforeState:
sourcePaths:
- z:\sources\SxS
features:
- featureName: NetFx3
state: NotPresent
displayName: NET Framework 3.5 (includes .NET 2.0 and 3.0)
description: .NET Framework 3.5 (includes .NET 2.0 and 3.0)
restartRequired: No
afterState:
sourcePaths:
- z:\sources\SxS
features:
- featureName: NetFx3
state: Installed
displayName: NET Framework 3.5 (includes .NET 2.0 and 3.0)
description: .NET Framework 3.5 (includes .NET 2.0 and 3.0)
restartRequired: Possible
changedProperties:
- features
```

## Manage multiple features in a single operation

You can enable or disable multiple features in a single **Set** call by specifying multiple entries
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ The following list describes the properties for the resource.

- [features](#features) - An array of optional feature entries.

- **Instance properties:** <a id="instance-properties"></a> The following properties are optional.
They define the desired state for an instance of the resource.

- [sourcePaths](#sourcePaths) - The location of the source files to use for installation if
necessary.

- **Read-only properties:** <a id="read-only-properties"></a> The resource returns the following
properties, but they aren't configurable. For more information about read-only properties, see
the "Read-only resource properties" section in [DSC resource properties][05].
Expand Down Expand Up @@ -262,6 +268,26 @@ property is returned by **Get** and **Export** operations and cannot be set.
| `Possible` | A restart may be required depending on system conditions. |
| `Required` | A restart is required to complete the state change. |

### sourcePaths

<details><summary>Expand for <code>sourcePaths</code> property metadata</summary>

```yaml
Type : array
IsRequired : false
IsKey : false
IsReadOnly : false
```

</details>

Supplied at the top level of the **Set** operation, indicates the location of the source files to
use for installation if necessary. The DISM API will search these paths if the feature files are
not available in the local feature store. See the [DISM enable feature documentation][08] for more
information on valid sources.

This property is optional and will be omitted from the response if no value is provided.

### _restartRequired

<details><summary>Expand for <code>_restartRequired</code> property metadata</summary>
Expand Down Expand Up @@ -298,6 +324,12 @@ The following snippet contains the JSON Schema that validates an instance of the
"type": "object",
"additionalProperties": true
}
},
"sourcePaths": {
"type": "array",
"items": {
"type": "string"
}
},
"features": {
"type": "array",
Expand Down Expand Up @@ -364,4 +396,5 @@ Common causes include:
[04]: ./examples/export-optional-features.md
[05]: ../../../../../concepts/resources/properties.md#read-only-resource-properties
[06]: ../FeatureOnDemandList/index.md
[07]: /windows-server/administration/windows-commands/dism/dism-operating-system-package-servicing-command-line-options
[07]: /windows-hardware/manufacture/desktop/deployment-image-servicing-and-management--dism--command-line-options
[08]: /windows-hardware/manufacture/desktop/dism-operating-system-package-servicing-command-line-options#enable-feature
8 changes: 8 additions & 0 deletions resources/dism_dsc/featureondemand.dsc.resource.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@
}
}
}
},
"sourcePaths": {
"type": "array",
"title": "Source paths",
"description": "An array of paths to feature files if the files are not available in the local feature store. This is an optional property for set operations and is ignored for get operations.",
"items": {
"type": "string"
}
}
}
}
Expand Down
11 changes: 10 additions & 1 deletion resources/dism_dsc/optionalfeature.dsc.resource.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@
"PartiallyInstalled"
],
"title": "Feature state",
"description": "The current state of the optional feature. For set operations, only Installed, NotPresent, and Removed are accepted; other states are returned by get and export operations and are not valid inputs for set."},
"description": "The current state of the optional feature. For set operations, only Installed, NotPresent, and Removed are accepted; other states are returned by get and export operations and are not valid inputs for set."
},
"displayName": {
"type": "string",
"title": "Display name",
Expand All @@ -114,6 +115,14 @@
}
}
}
},
"sourcePaths": {
"type": "array",
"title": "Source paths",
"description": "An array of paths to feature files if the files are not available in the local feature store. This is an optional property for set operations and is ignored for get operations.",
"items": {
"type": "string"
}
}
}
}
Expand Down
38 changes: 32 additions & 6 deletions resources/dism_dsc/src/dism.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,17 +319,30 @@ impl DismSessionHandle {
}

/// Returns `Ok(true)` if DISM reports a reboot is required (HRESULT 3010).
pub fn enable_feature(&self, feature_name: &str) -> Result<bool, String> {
pub fn enable_feature(&self, feature_name: &str, source_paths: &Option<Vec<String>>) -> Result<bool, String> {
let wide_name = to_wide_null(feature_name);

let wide_source_paths: Option<Vec<Vec<u16>>> = source_paths
.as_ref()
.filter(|paths| !paths.is_empty())
.map(|paths| paths.iter().map(|p| to_wide_null(p)).collect());

let sources: Option<Vec<*const u16>> = wide_source_paths
.as_ref()
.map(|paths| paths.iter().map(|p| p.as_ptr()).collect());

let source_count = sources.as_ref().map_or(0, |paths| paths.len() as u32);
let sources_ptr = sources.as_ref().map_or(std::ptr::null(), |v| v.as_ptr());
Comment thread
Alex-shearing marked this conversation as resolved.

let hr = unsafe {
(self.api.enable_feature)(
self.handle,
wide_name.as_ptr(),
std::ptr::null(), // Identifier
DISM_PACKAGE_NONE, // PackageIdentifier
0, // LimitAccess = FALSE
std::ptr::null(), // SourcePaths
0, // SourcePathCount
sources_ptr, // SourcePaths
source_count, // SourcePathCount
0, // EnableAll = FALSE
std::ptr::null_mut(), // CancelEvent
std::ptr::null_mut(), // Progress
Expand Down Expand Up @@ -457,18 +470,31 @@ impl DismSessionHandle {
}

/// Returns `Ok(true)` if DISM reports a reboot is required (HRESULT 3010).
pub fn add_capability(&self, name: &str) -> Result<bool, String> {
pub fn add_capability(&self, name: &str, source_paths: &Option<Vec<String>>) -> Result<bool, String> {
let add_cap = self.api.add_capability
.ok_or_else(|| t!("dism.capabilitiesNotSupported").to_string())?;

let wide_name = to_wide_null(name);

let wide_source_paths: Option<Vec<Vec<u16>>> = source_paths
.as_ref()
.filter(|paths| !paths.is_empty())
.map(|paths| paths.iter().map(|p| to_wide_null(p)).collect());

let sources: Option<Vec<*const u16>> = wide_source_paths
.as_ref()
.map(|paths| paths.iter().map(|p| p.as_ptr()).collect());

let source_count = sources.as_ref().map_or(0, |paths| paths.len() as u32);
let sources_ptr = sources.as_ref().map_or(std::ptr::null(), |v| v.as_ptr());

let hr = unsafe {
add_cap(
self.handle,
wide_name.as_ptr(),
0, // LimitAccess = FALSE
std::ptr::null(), // SourcePaths
0, // SourcePathCount
sources_ptr, // SourcePaths
source_count, // SourcePathCount
std::ptr::null_mut(), // CancelEvent
std::ptr::null_mut(), // Progress
std::ptr::null_mut(), // UserData
Expand Down
2 changes: 1 addition & 1 deletion resources/dism_dsc/src/feature_on_demand/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ pub fn handle_export(input: &str) -> Result<String, String> {
}
}

let output = FeatureOnDemandList { restart_required_meta: None, capabilities: results };
let output = FeatureOnDemandList { restart_required_meta: None, source_paths: None, capabilities: results };
serde_json::to_string(&output)
.map_err(|e| t!("fod_export.failedSerializeOutput", err = e.to_string()).to_string())
}
6 changes: 5 additions & 1 deletion resources/dism_dsc/src/feature_on_demand/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ pub fn handle_get(input: &str) -> Result<String, String> {
results.push(info);
}

let output = FeatureOnDemandList { restart_required_meta: None, capabilities: results };
let output = FeatureOnDemandList {
restart_required_meta: None,
source_paths: None,
capabilities: results
};
Comment thread
Alex-shearing marked this conversation as resolved.
serde_json::to_string(&output)
.map_err(|e| t!("fod_get.failedSerializeOutput", err = e.to_string()).to_string())
}
8 changes: 6 additions & 2 deletions resources/dism_dsc/src/feature_on_demand/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub fn handle_set(input: &str) -> Result<String, String> {
CapabilityState::Installed => {
match current_state {
Some(CapabilityState::Installed) => false,
_ => session.add_capability(identity)?,
_ => session.add_capability(identity, &capability_list.source_paths)?,
}
}
CapabilityState::NotPresent => {
Expand Down Expand Up @@ -84,7 +84,11 @@ pub fn handle_set(input: &str) -> Result<String, String> {
None
};

let output = FeatureOnDemandList { restart_required_meta, capabilities: results };
let output = FeatureOnDemandList {
restart_required_meta,
source_paths: capability_list.source_paths,
capabilities: results
};
serde_json::to_string(&output)
.map_err(|e| t!("fod_set.failedSerializeOutput", err = e.to_string()).to_string())
}
2 changes: 2 additions & 0 deletions resources/dism_dsc/src/feature_on_demand/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub type CapabilityState = DismState;
pub struct FeatureOnDemandList {
#[serde(rename = "_restartRequired", skip_serializing_if = "Option::is_none")]
pub restart_required_meta: Option<Vec<Map<String, Value>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_paths: Option<Vec<String>>,
Comment thread
Alex-shearing marked this conversation as resolved.
pub capabilities: Vec<FeatureOnDemandInfo>,
}

Expand Down
2 changes: 1 addition & 1 deletion resources/dism_dsc/src/optional_feature/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ pub fn handle_export(input: &str) -> Result<String, String> {
}
}

let output = OptionalFeatureList { restart_required_meta: None, features: results };
let output = OptionalFeatureList { restart_required_meta: None, source_paths: None, features: results };
serde_json::to_string(&output)
.map_err(|e| t!("export.failedSerializeOutput", err = e.to_string()).to_string())
}
Loading
Loading