diff --git a/dsc/tests/dsc.exit_code.tests.ps1 b/dsc/tests/dsc.exit_code.tests.ps1
new file mode 100644
index 00000000..05bec814
--- /dev/null
+++ b/dsc/tests/dsc.exit_code.tests.ps1
@@ -0,0 +1,18 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+Describe 'exit code tests' {
+    It 'non-zero exit code in manifest has corresponding message' {
+        $result = dsc resource get -r Test/ExitCode --input "{ exitCode: 8 }" 2>&1
+        $result | Should -Match 'ERROR.*?[Exit code 8].*?manifest description: Placeholder from manifest for exit code 8'
+    }
+    It 'non-zero exit code not in manifest has generic message' {
+        $result = dsc resource get -r Test/ExitCode --input "{ exitCode: 1 }" 2>&1
+        $result | Should -Match 'ERROR.*?Error.*?[Exit code 1]'
+    }
+    It 'success exit code executes without error' {
+        $result = dsc resource get -r Test/ExitCode --input "{ exitCode: 0 }" | ConvertFrom-Json
+        $result.actualState.exitCode | Should -Be 0
+        $LASTEXITCODE | Should -Be 0
+    }
+}
diff --git a/dsc_lib/src/discovery/command_discovery.rs b/dsc_lib/src/discovery/command_discovery.rs
index 8c431bec..c507f4f3 100644
--- a/dsc_lib/src/discovery/command_discovery.rs
+++ b/dsc_lib/src/discovery/command_discovery.rs
@@ -221,7 +221,7 @@ impl ResourceDiscovery for CommandDiscovery {
                 let mut adapter_resources_count = 0;
                 // invoke the list command
                 let list_command = manifest.adapter.unwrap().list;
-                let (exit_code, stdout, stderr) = match invoke_command(&list_command.executable, list_command.args, None, Some(&adapter.directory), None)
+                let (exit_code, stdout, stderr) = match invoke_command(&list_command.executable, list_command.args, None, Some(&adapter.directory), None, &manifest.exit_codes)
                 {
                     Ok((exit_code, stdout, stderr)) => (exit_code, stdout, stderr),
                     Err(e) => {
diff --git a/dsc_lib/src/dscerror.rs b/dsc_lib/src/dscerror.rs
index 12ab5c1f..e0dfca4f 100644
--- a/dsc_lib/src/dscerror.rs
+++ b/dsc_lib/src/dscerror.rs
@@ -20,6 +20,9 @@ pub enum DscError {
     #[error("Command: Executable '{0}' [Exit code {1}] {2}")]
     CommandExit(String, i32, String),
 
+    #[error("Command: Resource '{0}' [Exit code {1}] manifest description: {2}")]
+    CommandExitFromManifest(String, i32, String),
+
     #[error("CommandOperation: {0} for executable '{1}'")]
     CommandOperation(String, String),
 
diff --git a/dsc_lib/src/dscresources/command_resource.rs b/dsc_lib/src/dscresources/command_resource.rs
index fd2d997a..28472599 100644
--- a/dsc_lib/src/dscresources/command_resource.rs
+++ b/dsc_lib/src/dscresources/command_resource.rs
@@ -57,7 +57,7 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul
     }
 
     info!("Invoking get '{}' using '{}'", &resource.resource_type, &get.executable);
-    let (_exit_code, stdout, stderr) = invoke_command(&get.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env)?;
+    let (_exit_code, stdout, stderr) = invoke_command(&get.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, &resource.exit_codes)?;
     if resource.kind == Some(Kind::Resource) {
         debug!("Verifying output of get '{}' using '{}'", &resource.resource_type, &get.executable);
         verify_json(resource, cwd, &stdout)?;
@@ -156,8 +156,8 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
     let args = process_args(&get.args, desired);
     let command_input = get_command_input(&get.input, desired)?;
 
-    info!("Getting current state for {} by invoking get '{}' using '{}'", operation_type, &resource.resource_type, &get.executable);
-    let (exit_code, stdout, stderr) = invoke_command(&get.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env)?;
+    info!("Getting current state for set by invoking get '{}' using '{}'", &resource.resource_type, &get.executable);
+    let (exit_code, stdout, stderr) = invoke_command(&get.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, &resource.exit_codes)?;
 
     if resource.kind == Some(Kind::Resource) {
         debug!("Verifying output of get '{}' using '{}'", &resource.resource_type, &get.executable);
@@ -187,7 +187,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te
     }
 
     info!("Invoking {} '{}' using '{}'", operation_type, &resource.resource_type, &set.executable);
-    let (exit_code, stdout, stderr) = invoke_command(&set.executable, args, input_desired, Some(cwd), env)?;
+    let (exit_code, stdout, stderr) = invoke_command(&set.executable, args, input_desired, Some(cwd), env, &resource.exit_codes)?;
 
     match set.returns {
         Some(ReturnKind::State) => {
@@ -280,7 +280,7 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re
     let command_input = get_command_input(&test.input, expected)?;
 
     info!("Invoking test '{}' using '{}'", &resource.resource_type, &test.executable);
-    let (exit_code, stdout, stderr) = invoke_command(&test.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env)?;
+    let (exit_code, stdout, stderr) = invoke_command(&test.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, &resource.exit_codes)?;
 
     if resource.kind == Some(Kind::Resource) {
         debug!("Verifying output of test '{}' using '{}'", &resource.resource_type, &test.executable);
@@ -394,7 +394,7 @@ pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str) -> Re
     let command_input = get_command_input(&delete.input, filter)?;
 
     info!("Invoking delete '{}' using '{}'", &resource.resource_type, &delete.executable);
-    let (_exit_code, _stdout, _stderr) = invoke_command(&delete.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env)?;
+    let (_exit_code, _stdout, _stderr) = invoke_command(&delete.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, &resource.exit_codes)?;
 
     Ok(())
 }
@@ -425,7 +425,7 @@ pub fn invoke_validate(resource: &ResourceManifest, cwd: &str, config: &str) ->
     let command_input = get_command_input(&validate.input, config)?;
 
     info!("Invoking validate '{}' using '{}'", &resource.resource_type, &validate.executable);
-    let (_exit_code, stdout, _stderr) = invoke_command(&validate.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env)?;
+    let (_exit_code, stdout, _stderr) = invoke_command(&validate.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, &resource.exit_codes)?;
     let result: ValidateResult = serde_json::from_str(&stdout)?;
     Ok(result)
 }
@@ -446,7 +446,7 @@ pub fn get_schema(resource: &ResourceManifest, cwd: &str) -> Result<String, DscE
 
     match schema_kind {
         SchemaKind::Command(ref command) => {
-            let (_exit_code, stdout, _stderr) = invoke_command(&command.executable, command.args.clone(), None, Some(cwd), None)?;
+            let (_exit_code, stdout, _stderr) = invoke_command(&command.executable, command.args.clone(), None, Some(cwd), None, &resource.exit_codes)?;
             Ok(stdout)
         },
         SchemaKind::Embedded(ref schema) => {
@@ -501,7 +501,7 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str>
         args = process_args(&export.args, "");
     }
 
-    let (_exit_code, stdout, stderr) = invoke_command(&export.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env)?;
+    let (_exit_code, stdout, stderr) = invoke_command(&export.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, &resource.exit_codes)?;
     let mut instances: Vec<Value> = Vec::new();
     for line in stdout.lines()
     {
@@ -547,7 +547,7 @@ pub fn invoke_resolve(resource: &ResourceManifest, cwd: &str, input: &str) -> Re
     let command_input = get_command_input(&resolve.input, input)?;
 
     info!("Invoking resolve '{}' using '{}'", &resource.resource_type, &resolve.executable);
-    let (_exit_code, stdout, _stderr) = invoke_command(&resolve.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env)?;
+    let (_exit_code, stdout, _stderr) = invoke_command(&resolve.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, &resource.exit_codes)?;
     let result: ResolveResult = serde_json::from_str(&stdout)?;
     Ok(result)
 }
@@ -565,7 +565,7 @@ pub fn invoke_resolve(resource: &ResourceManifest, cwd: &str, input: &str) -> Re
 ///
 /// Error is returned if the command fails to execute or stdin/stdout/stderr cannot be opened.
 #[allow(clippy::implicit_hasher)]
-pub fn invoke_command(executable: &str, args: Option<Vec<String>>, input: Option<&str>, cwd: Option<&str>, env: Option<HashMap<String, String>>) -> Result<(i32, String, String), DscError> {
+pub fn invoke_command(executable: &str, args: Option<Vec<String>>, input: Option<&str>, cwd: Option<&str>, env: Option<HashMap<String, String>>, exit_codes: &Option<HashMap<i32, String>>) -> Result<(i32, String, String), DscError> {
     debug!("Invoking command '{}' with args {:?}", executable, args);
     let mut command = Command::new(executable);
     if input.is_some() {
@@ -629,6 +629,11 @@ pub fn invoke_command(executable: &str, args: Option<Vec<String>>, input: Option
     };
 
     if exit_code != 0 {
+        if let Some(exit_codes) = exit_codes {
+            if let Some(error_message) = exit_codes.get(&exit_code) {
+                return Err(DscError::CommandExitFromManifest(executable.to_string(), exit_code, error_message.to_string()));
+            }
+        }
         return Err(DscError::Command(executable.to_string(), exit_code, cleaned_stderr));
     }
 
diff --git a/tools/dsctest/dscexitcode.dsc.resource.json b/tools/dsctest/dscexitcode.dsc.resource.json
new file mode 100644
index 00000000..554dc494
--- /dev/null
+++ b/tools/dsctest/dscexitcode.dsc.resource.json
@@ -0,0 +1,28 @@
+{
+    "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json",
+    "type": "Test/ExitCode",
+    "version": "0.1.0",
+    "get": {
+        "executable": "dsctest",
+        "args": [
+            "exit-code",
+            {
+                "jsonInputArg": "--input"
+            }
+        ]
+    },
+    "exitCodes": {
+        "0": "Success",
+        "8": "Placeholder from manifest for exit code 8"
+    },
+    "schema": {
+        "command": {
+            "executable": "dsctest",
+            "args": [
+                "schema",
+                "-s",
+                "exit-code"
+            ]
+        }
+    }
+}
diff --git a/tools/dsctest/src/args.rs b/tools/dsctest/src/args.rs
index 51b69e83..e394a8a8 100644
--- a/tools/dsctest/src/args.rs
+++ b/tools/dsctest/src/args.rs
@@ -8,6 +8,7 @@ pub enum Schemas {
     Delete,
     Echo,
     Exist,
+    ExitCode,
     Sleep,
     Trace,
     WhatIf,
@@ -41,6 +42,12 @@ pub enum SubCommand {
         input: String,
     },
 
+    #[clap(name = "exit-code", about = "Return the exit code specified in the input")]
+    ExitCode {
+        #[clap(name = "input", short, long, help = "The input to the exit code command as JSON")]
+        input: String,
+    },
+
     #[clap(name = "schema", about = "Get the JSON schema for a subcommand")]
     Schema {
         #[clap(name = "subcommand", short, long, help = "The subcommand to get the schema for")]
diff --git a/tools/dsctest/src/exit_code.rs b/tools/dsctest/src/exit_code.rs
new file mode 100644
index 00000000..478f903c
--- /dev/null
+++ b/tools/dsctest/src/exit_code.rs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
+#[serde(deny_unknown_fields)]
+pub struct ExitCode {
+    #[serde(rename = "exitCode")]
+    pub exit_code: i32,
+}
diff --git a/tools/dsctest/src/main.rs b/tools/dsctest/src/main.rs
index cd3790d2..91760736 100644
--- a/tools/dsctest/src/main.rs
+++ b/tools/dsctest/src/main.rs
@@ -5,6 +5,7 @@ mod args;
 mod delete;
 mod echo;
 mod exist;
+mod exit_code;
 mod sleep;
 mod trace;
 mod whatif;
@@ -15,11 +16,13 @@ use schemars::schema_for;
 use crate::delete::Delete;
 use crate::echo::Echo;
 use crate::exist::{Exist, State};
+use crate::exit_code::ExitCode;
 use crate::sleep::Sleep;
 use crate::trace::Trace;
 use crate::whatif::WhatIf;
 use std::{thread, time::Duration};
 
+#[allow(clippy::too_many_lines)]
 fn main() {
     let args = Args::parse();
     let json = match args.subcommand {
@@ -60,6 +63,20 @@ fn main() {
 
             serde_json::to_string(&exist).unwrap()
         },
+        SubCommand::ExitCode { input } => {
+            let exit_code = match serde_json::from_str::<ExitCode>(&input) {
+                Ok(exit_code) => exit_code,
+                Err(err) => {
+                    eprintln!("Error JSON does not match schema: {err}");
+                    std::process::exit(1);
+                }
+            };
+            if exit_code.exit_code != 0 {
+                eprintln!("Exiting with code: {}", exit_code.exit_code);
+                std::process::exit(exit_code.exit_code);
+            }
+            input
+        },
         SubCommand::Schema { subcommand } => {
             let schema = match subcommand {
                 Schemas::Delete => {
@@ -71,6 +88,9 @@ fn main() {
                 Schemas::Exist => {
                     schema_for!(Exist)
                 },
+                Schemas::ExitCode => {
+                    schema_for!(ExitCode)
+                },
                 Schemas::Sleep => {
                     schema_for!(Sleep)
                 },