Skip to content
Merged
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
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ members = [
"lib/dsc-lib-registry",
"resources/runcommandonset",
"lib/dsc-lib-security_context",
"resources/dism_dsc",
"resources/sshdconfig",
"resources/WindowsUpdate",
"tools/dsctest",
Expand Down Expand Up @@ -44,6 +45,7 @@ default-members = [
"lib/dsc-lib-registry",
"resources/runcommandonset",
"lib/dsc-lib-security_context",
"resources/dism_dsc",
"resources/sshdconfig",
"resources/WindowsUpdate",
"tools/dsctest",
Expand Down Expand Up @@ -80,7 +82,8 @@ Windows = [
"grammars/tree-sitter-dscexpression",
"grammars/tree-sitter-ssh-server-config",
"y2j",
"xtask"
"xtask",
"resources/dism_dsc"
]
macOS = [
"dsc",
Expand Down
10 changes: 10 additions & 0 deletions resources/dism_dsc/.project.data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Name": "dism_dsc",
"Kind": "Resource",
"IsRust": true,
"SupportedPlatformOS": "Windows",
"Binaries": ["dism_dsc"],
"CopyFiles": {
"Windows": ["optionalfeature.dsc.resource.json"]
}
}
13 changes: 13 additions & 0 deletions resources/dism_dsc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "dism_dsc"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "dism_dsc"
path = "src/main.rs"

[dependencies]
rust-i18n = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
36 changes: 36 additions & 0 deletions resources/dism_dsc/locales/en-us.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
_version = 1

[main]
missingOperation = "Missing operation argument"
usage = "Usage: dism_dsc <get|set|export>"
windowsOnly = "This resource is only supported on Windows"
unknownOperation = "Unknown operation '%{operation}'"
errorReadingInput = "Error reading input: %{err}"

[get]
failedParseInput = "Failed to parse input: %{err}"
featuresArrayEmpty = "Features array cannot be empty for get operation"
featureNameRequired = "featureName is required for get operation"
failedSerializeOutput = "Failed to serialize output: %{err}"

[export]
failedParseInput = "Failed to parse input: %{err}"
failedSerializeOutput = "Failed to serialize output: %{err}"

[set]
failedParseInput = "Failed to parse input: %{err}"
featuresArrayEmpty = "Features array cannot be empty for set operation"
featureNameRequired = "featureName is required for set operation"
stateRequired = "state is required for set operation"
unsupportedDesiredState = "Unsupported desired state '%{state}'. Supported states for set are: Installed, NotPresent, Removed"
failedSerializeOutput = "Failed to serialize output: %{err}"

[dism]
failedLoadLibrary = "Failed to load dismapi.dll. Ensure DISM is available on this system."
functionNotFound = "Failed to find function '%{name}' in dismapi.dll"
initializeFailed = "DismInitialize failed: HRESULT %{hr}"
openSessionFailed = "DismOpenSession failed: HRESULT %{hr}"
getFeatureInfoFailed = "DismGetFeatureInfo failed for '%{name}': HRESULT %{hr}"
enableFeatureFailed = "DismEnableFeature failed for '%{name}': HRESULT %{hr}"
disableFeatureFailed = "DismDisableFeature failed for '%{name}': HRESULT %{hr}"
getFeaturesFailed = "DismGetFeatures failed: HRESULT %{hr}"
118 changes: 118 additions & 0 deletions resources/dism_dsc/optionalfeature.dsc.resource.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
{
"$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json",
"description": "Manage Windows Optional Features using the DISM API.",
"tags": [
"windows",
"dism",
"optionalfeature",
"feature"
],
"type": "Microsoft.Windows/OptionalFeatureList",
"version": "0.1.0",
"get": {
"executable": "dism_dsc",
"args": [
"get"
],
"input": "stdin",
"requireSecurityContext": "elevated"
},
"set": {
"executable": "dism_dsc",
"args": [
"set"
],
"input": "stdin",
"implementsPretest": false,
"return": "state",
"requireSecurityContext": "elevated"
},
"export": {
"executable": "dism_dsc",
"args": [
"export"
],
"input": "stdin",
"requireSecurityContext": "elevated"
},
"schema": {
"embedded": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/resources/Microsoft.Windows/OptionalFeatureList/v0.1.0/schema.json",
"title": "Windows Optional Feature",
"description": "Manage Windows Optional Features using the DISM API. Supports get, set, and export operations.\n\nhttps://learn.microsoft.com/powershell/dsc/reference/microsoft.windows/optionalfeaturelist/resource\n",
"markdownDescription": "The `Microsoft.Windows/OptionalFeatureList` resource enables you to manage Windows Optional Features using the DISM API. Supports enabling (`Installed`) and disabling (`NotPresent`, `Removed`) features.\n\n[Online documentation][01]\n\n[01]: https://learn.microsoft.com/powershell/dsc/reference/microsoft.windows/optionalfeaturelist/resource\n",
"type": "object",
"additionalProperties": false,
"required": [
"features"
],
"properties": {
"_restartRequired": {
"type": "array",
"title": "Restart required",
"description": "Indicates that a system restart is required to complete the state change. Returned by the set operation when DISM reports that a reboot is needed.",
"items": {
"type": "object",
"additionalProperties": true
}
},
"features": {
"type": "array",
"title": "Features",
"description": "An array of optional feature filters or feature information objects.",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"featureName": {
"type": "string",
"title": "Feature name",
"description": "The name of the Windows optional feature. Required for get operation. For export operation, this is optional and wildcards (*) are supported for case-insensitive filtering."
},
"_exist": {
"type": "boolean",
"title": "Exists",
"description": "Indicates whether the feature exists on the system. Set to false when the requested feature name is not recognized by DISM."
},
"state": {
"type": "string",
"enum": [
"NotPresent",
"UninstallPending",
"Staged",
"Removed",
"Installed",
"InstallPending",
"Superseded",
"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."},
"displayName": {
"type": "string",
"title": "Display name",
"description": "The display name of the optional feature. Returned by get and export operations. For export, wildcards (*) are supported for case-insensitive filtering."
},
"description": {
"type": "string",
"title": "Description",
"description": "The description of the optional feature. Returned by get and export operations. For export, wildcards (*) are supported for case-insensitive filtering."
},
"restartRequired": {
"type": "string",
"enum": [
"No",
"Possible",
"Required"
],
"title": "Restart required",
"description": "Whether a restart is required after enabling or disabling the feature. This is a read-only property returned by get and export operations."
}
}
}
}
}
}
}
}
71 changes: 71 additions & 0 deletions resources/dism_dsc/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#[cfg(windows)]
mod optional_feature;

use rust_i18n::t;
use std::io::{self, Read, IsTerminal};

rust_i18n::i18n!("locales", fallback = "en-us");

fn read_stdin(required: bool) -> Result<String, String> {
let mut buffer = String::new();
if required || !io::stdin().is_terminal() {
io::stdin()
.read_to_string(&mut buffer)
.map_err(|e| t!("main.errorReadingInput", err = e).to_string())?;
}
Ok(buffer)
}

fn dispatch(handler: impl FnOnce(&str) -> Result<String, String>, stdin_required: bool) {
let buffer = match read_stdin(stdin_required) {
Ok(b) => b,
Err(e) => {
eprintln!("{e}");
std::process::exit(1);
}
};

match handler(&buffer) {
Ok(output) => {
println!("{output}");
std::process::exit(0);
}
Err(e) => {
eprintln!("Error: {e}");
std::process::exit(1);
}
}
}

#[cfg(not(windows))]
fn main() {
eprintln!("Error: {}", t!("main.windowsOnly"));
std::process::exit(1);
}

#[cfg(windows)]
fn main() {
let args: Vec<String> = std::env::args().collect();

if args.len() < 2 {
eprintln!("Error: {}", t!("main.missingOperation"));
eprintln!("{}", t!("main.usage"));
std::process::exit(1);
}

let operation = args[1].as_str();

match operation {
"export" => dispatch(optional_feature::handle_export, false),
"get" => dispatch(optional_feature::handle_get, true),
"set" => dispatch(optional_feature::handle_set, true),
_ => {
eprintln!("{}", t!("main.unknownOperation", operation = operation));
eprintln!("{}", t!("main.usage"));
std::process::exit(1);
}
}
}
Loading
Loading