diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 6ad3adb7..87cab479 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -64,6 +64,7 @@
"Change IP format",
"Encode text",
"Decode text",
+ "PowerShell -e Encode/Decode",
"Text Encoding Brute Force",
"Swap endianness",
"To MessagePack",
diff --git a/src/core/operations/PowerShellEncodeDecode.mjs b/src/core/operations/PowerShellEncodeDecode.mjs
new file mode 100644
index 00000000..690d3cf2
--- /dev/null
+++ b/src/core/operations/PowerShellEncodeDecode.mjs
@@ -0,0 +1,67 @@
+/**
+ * @author neoreo
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import cptable from "codepage";
+import {toBase64, fromBase64} from "../lib/Base64.mjs";
+
+// PowerShell -EncodedCommand uses UTF-16LE (code page 1200)
+const UTF_16LE = 1200;
+
+/**
+ * PowerShell -e Encode/Decode operation
+ */
+class PowerShellEncodeDecode extends Operation {
+
+ /**
+ * PowerShellEncodeDecode constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "PowerShell -e Encode/Decode";
+ this.module = "Encodings";
+ this.description = [
+ "Encodes or decodes a PowerShell -EncodedCommand (-e) payload in a single operation.",
+ "
",
+ "PowerShell's -EncodedCommand parameter expects the command encoded as UTF-16LE and then Base64'd. ",
+ "This operation combines both steps so you don't have to chain 'Encode text' and 'To Base64' (or their decode equivalents) yourself.",
+ "
",
+ "Encode: whoami becomes dwBoAG8AYQBtAGkA, runnable as powershell -e dwBoAG8AYQBtAGkA.",
+ "
",
+ "Decode: dwBoAG8AYQBtAGkA becomes whoami."
+ ].join("\n");
+ this.infoURL = "https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_powershell_exe#-encodedcommand-base64encodedcommand";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ name: "Mode",
+ type: "option",
+ value: ["Encode", "Decode"]
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const [mode] = args;
+
+ if (mode === "Encode") {
+ const encoded = cptable.utils.encode(UTF_16LE, input);
+ return toBase64(new Uint8Array(encoded).buffer);
+ }
+ const bytes = fromBase64(input, "A-Za-z0-9+/=", "byteArray");
+ return cptable.utils.decode(UTF_16LE, new Uint8Array(bytes));
+ }
+
+}
+
+export default PowerShellEncodeDecode;
diff --git a/tests/node/tests/nodeApi.mjs b/tests/node/tests/nodeApi.mjs
index b65b9abc..43d1605d 100644
--- a/tests/node/tests/nodeApi.mjs
+++ b/tests/node/tests/nodeApi.mjs
@@ -168,7 +168,7 @@ TestRegister.addApiTests([
it("chef.help: returns multiple results", () => {
const result = chef.help("base 64");
- assert.strictEqual(result.length, 14);
+ assert.strictEqual(result.length, 15);
}),
it("chef.help: looks in description for matches too", () => {
diff --git a/tests/operations/tests/PowerShellEncodeDecode.mjs b/tests/operations/tests/PowerShellEncodeDecode.mjs
new file mode 100644
index 00000000..98922b85
--- /dev/null
+++ b/tests/operations/tests/PowerShellEncodeDecode.mjs
@@ -0,0 +1,86 @@
+/**
+ * PowerShell -e Encode/Decode tests.
+ *
+ * @author neoreo
+ *
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ name: "PowerShell -e Encode/Decode: encode nothing",
+ input: "",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ op: "PowerShell -e Encode/Decode",
+ args: ["Encode"],
+ },
+ ],
+ },
+ {
+ name: "PowerShell -e Encode/Decode: encode whoami",
+ input: "whoami",
+ expectedOutput: "dwBoAG8AYQBtAGkA",
+ recipeConfig: [
+ {
+ op: "PowerShell -e Encode/Decode",
+ args: ["Encode"],
+ },
+ ],
+ },
+ {
+ name: "PowerShell -e Encode/Decode: decode whoami",
+ input: "dwBoAG8AYQBtAGkA",
+ expectedOutput: "whoami",
+ recipeConfig: [
+ {
+ op: "PowerShell -e Encode/Decode",
+ args: ["Decode"],
+ },
+ ],
+ },
+ {
+ name: "PowerShell -e Encode/Decode: round trip",
+ input: "Get-Process | Where-Object {$_.CPU -gt 10}",
+ expectedOutput: "Get-Process | Where-Object {$_.CPU -gt 10}",
+ recipeConfig: [
+ {
+ op: "PowerShell -e Encode/Decode",
+ args: ["Encode"],
+ },
+ {
+ op: "PowerShell -e Encode/Decode",
+ args: ["Decode"],
+ },
+ ],
+ },
+ {
+ name: "PowerShell -e Encode/Decode: round trip with non-ASCII characters",
+ input: "Write-Host \"café ☕\"",
+ expectedOutput: "Write-Host \"café ☕\"",
+ recipeConfig: [
+ {
+ op: "PowerShell -e Encode/Decode",
+ args: ["Encode"],
+ },
+ {
+ op: "PowerShell -e Encode/Decode",
+ args: ["Decode"],
+ },
+ ],
+ },
+ {
+ name: "PowerShell -e Encode/Decode: decode ignores characters outside the Base64 alphabet",
+ input: "!!!@@@###",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ op: "PowerShell -e Encode/Decode",
+ args: ["Decode"],
+ },
+ ],
+ },
+]);