From 76b17d0542741a0fe07d23b72d2be92af19ea3c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 3 Jul 2026 14:46:06 -0400 Subject: [PATCH 1/2] Support PowerShell apphost payload layouts Resolve local pwsh apphost payload directories so adjacent and runtimes//native layouts both host the intended PowerShell payload. Update MCP hosting and docs/metadata for the supported layouts. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- README.md | 4 +- crates/multi-pwsh/src/main.rs | 129 +++++++++++++++++++--- crates/multi-pwsh/src/mcp.rs | 11 +- docs/feature-matrix.md | 4 +- docs/host-and-venv.md | 4 +- nuget/Devolutions.MultiPwsh.Cli/README.md | 2 +- scripts/Build-NativeNuGetPackages.ps1 | 4 +- 7 files changed, 126 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 105bd40..f63d3f4 100644 --- a/README.md +++ b/README.md @@ -276,13 +276,13 @@ The current LTS line is encoded in the tool; at the moment that is `7.6`. - Use `-mcp -McpCommands [command ...]` to expose selected PowerShell commands as stdio MCP tools from the chosen hosted version. - Use `multi-pwsh doctor --repair-aliases` to repair host shims and named aliases. -Advanced local replacement mode is also supported: if `multi-pwsh` is renamed to `pwsh`/`pwsh.exe` and placed beside `pwsh.dll` plus `pwsh.runtimeconfig.json`, it runs that adjacent payload directly from the executable directory instead of resolving the managed `pwsh` alias or searching `PATH`. +Advanced local replacement mode is also supported: if `multi-pwsh` is renamed to `pwsh`/`pwsh.exe`, it runs a local SDK payload directly instead of resolving the managed `pwsh` alias or searching `PATH`. The payload can be adjacent to the executable, or shared from the publish root when the executable is under `runtimes//native/`. See [docs/host-and-venv.md](docs/host-and-venv.md) for host shims, local replacement mode, venv layout, import/export, managed paths, and current limitations. See [docs/mcp.md](docs/mcp.md) for MCP host mode. ## CLI NuGet package and AppHost mode -`Devolutions.MultiPwsh.Cli` packages RID-specific `multi-pwsh` binaries under `runtimes//native/` for .NET projects. It also exposes neutral MSBuild metadata for downstream packages that need to consume the same binaries as PowerShell apphosts. The package supplies only the native launcher; downstream packages must place it beside their own `pwsh.dll` and `pwsh.runtimeconfig.json`. +`Devolutions.MultiPwsh.Cli` packages RID-specific `multi-pwsh` binaries under `runtimes//native/` for .NET projects. It also exposes neutral MSBuild metadata for downstream packages that need to consume the same binaries as PowerShell apphosts. The package supplies only the native launcher; downstream packages must provide their own `pwsh.dll` and `pwsh.runtimeconfig.json` either beside the renamed launcher or at the shared publish root above `runtimes//native/`. Package authors can consume the launchers privately and map them into their own package layout: diff --git a/crates/multi-pwsh/src/main.rs b/crates/multi-pwsh/src/main.rs index 2c39f86..60981ed 100644 --- a/crates/multi-pwsh/src/main.rs +++ b/crates/multi-pwsh/src/main.rs @@ -864,6 +864,21 @@ fn run_known_host_executable( layout: Option<&InstallLayout>, selector_input: &str, pwsh_args: Vec, +) -> Result { + let pwsh_dir = executable.parent().ok_or_else(|| { + MultiPwshError::Host(format!( + "failed to determine the PowerShell home directory from {}", + executable.display() + )) + })?; + run_known_host_executable_for_pwsh_dir(pwsh_dir, layout, selector_input, pwsh_args) +} + +fn run_known_host_executable_for_pwsh_dir( + pwsh_dir: &Path, + layout: Option<&InstallLayout>, + selector_input: &str, + pwsh_args: Vec, ) -> Result { let os = HostOs::detect()?; let HostDispatchOptions { launch, mcp } = preprocess_host_args(pwsh_args)?; @@ -893,7 +908,7 @@ fn run_known_host_executable( )); } - return mcp::run_stdio_mcp_server(executable, &mcp.commands).map_err(|error| { + return mcp::run_stdio_mcp_server_for_pwsh_dir(pwsh_dir, &mcp.commands).map_err(|error| { MultiPwshError::Host(format!( "failed to start MCP host for selector '{}': {}", selector_input, error @@ -907,7 +922,7 @@ fn run_known_host_executable( (pwsh_args, None) }; - pwsh_host::run_pwsh_command_line_for_pwsh_exe(executable, pwsh_args).map_err(|error| { + pwsh_host::run_pwsh_command_line_for_pwsh_dir(pwsh_dir, pwsh_args).map_err(|error| { MultiPwshError::Host(format!( "failed to start native host for selector '{}': {}", selector_input, error @@ -982,16 +997,48 @@ fn is_exact_pwsh_executable_name(executable_path: &Path) -> bool { .unwrap_or(false) } -fn is_local_pwsh_apphost(executable_path: &Path) -> bool { +fn has_pwsh_payload_markers(pwsh_dir: &Path) -> bool { + pwsh_dir.join("pwsh.dll").is_file() && pwsh_dir.join("pwsh.runtimeconfig.json").is_file() +} + +fn path_file_name_eq_ignore_ascii_case(path: &Path, expected: &str) -> bool { + path.file_name() + .and_then(|value| value.to_str()) + .map(|value| value.eq_ignore_ascii_case(expected)) + .unwrap_or(false) +} + +fn resolve_runtime_native_apphost_payload_dir(native_dir: &Path) -> Option { + if !path_file_name_eq_ignore_ascii_case(native_dir, "native") { + return None; + } + + let rid_dir = native_dir.parent()?; + let runtimes_dir = rid_dir.parent()?; + if !path_file_name_eq_ignore_ascii_case(runtimes_dir, "runtimes") { + return None; + } + + let shared_payload_dir = runtimes_dir.parent()?; + if has_pwsh_payload_markers(shared_payload_dir) { + return Some(shared_payload_dir.to_path_buf()); + } + + None +} + +fn resolve_local_pwsh_apphost_payload_dir(executable_path: &Path) -> Option { if !is_exact_pwsh_executable_name(executable_path) { - return false; + return None; } - let Some(executable_dir) = executable_path.parent() else { - return false; - }; + let executable_dir = executable_path.parent()?; - executable_dir.join("pwsh.dll").is_file() && executable_dir.join("pwsh.runtimeconfig.json").is_file() + if has_pwsh_payload_markers(executable_dir) { + return Some(executable_dir.to_path_buf()); + } + + resolve_runtime_native_apphost_payload_dir(executable_dir) } fn infer_layout_from_host_shim(os: HostOs, executable_path: &Path) -> Option { @@ -1031,10 +1078,11 @@ fn run_implicit_host_mode_if_needed() -> Result> { let executable_path = env::current_exe()?; let args: Vec = env::args_os().skip(1).collect(); - if is_local_pwsh_apphost(&executable_path) { + if let Some(pwsh_dir) = resolve_local_pwsh_apphost_payload_dir(&executable_path) { let os = HostOs::detect()?; let venv_layout = default_current_user_layout(os)?; - let exit_code = run_known_host_executable(&executable_path, Some(&venv_layout), "local pwsh apphost", args)?; + let exit_code = + run_known_host_executable_for_pwsh_dir(&pwsh_dir, Some(&venv_layout), "local pwsh apphost", args)?; return Ok(Some(exit_code)); } @@ -3884,7 +3932,10 @@ mod tests { fs::write(temp_dir.path().join("pwsh.dll"), "").unwrap(); fs::write(temp_dir.path().join("pwsh.runtimeconfig.json"), "{}").unwrap(); - assert!(is_local_pwsh_apphost(&executable_path)); + assert_eq!( + resolve_local_pwsh_apphost_payload_dir(&executable_path).as_deref(), + Some(temp_dir.path()) + ); } #[test] @@ -3895,18 +3946,44 @@ mod tests { fs::write(temp_dir.path().join("pwsh.dll"), "").unwrap(); fs::write(temp_dir.path().join("pwsh.runtimeconfig.json"), "{}").unwrap(); - assert!(is_local_pwsh_apphost(&executable_path)); + assert_eq!( + resolve_local_pwsh_apphost_payload_dir(&executable_path).as_deref(), + Some(temp_dir.path()) + ); + } + + #[test] + fn is_local_pwsh_apphost_accepts_runtime_native_shared_payload() { + let temp_dir = TempDir::new().unwrap(); + let native_dir = temp_dir.path().join("runtimes").join("win-x64").join("native"); + fs::create_dir_all(&native_dir).unwrap(); + let executable_path = native_dir.join("pwsh.exe"); + fs::write(&executable_path, "").unwrap(); + fs::write(temp_dir.path().join("pwsh.dll"), "").unwrap(); + fs::write(temp_dir.path().join("pwsh.runtimeconfig.json"), "{}").unwrap(); + + assert_eq!( + resolve_local_pwsh_apphost_payload_dir(&executable_path).as_deref(), + Some(temp_dir.path()) + ); } #[test] - fn is_local_pwsh_apphost_rejects_alias_name_with_adjacent_payload() { + fn is_local_pwsh_apphost_rejects_alias_name_with_local_payloads() { let temp_dir = TempDir::new().unwrap(); let executable_path = temp_dir.path().join("pwsh-preview.exe"); fs::write(&executable_path, "").unwrap(); fs::write(temp_dir.path().join("pwsh.dll"), "").unwrap(); fs::write(temp_dir.path().join("pwsh.runtimeconfig.json"), "{}").unwrap(); - assert!(!is_local_pwsh_apphost(&executable_path)); + assert!(resolve_local_pwsh_apphost_payload_dir(&executable_path).is_none()); + + let native_dir = temp_dir.path().join("runtimes").join("win-x64").join("native"); + fs::create_dir_all(&native_dir).unwrap(); + let runtime_native_executable_path = native_dir.join("pwsh-preview.exe"); + fs::write(&runtime_native_executable_path, "").unwrap(); + + assert!(resolve_local_pwsh_apphost_payload_dir(&runtime_native_executable_path).is_none()); } #[test] @@ -3915,14 +3992,32 @@ mod tests { let executable_path = temp_dir.path().join("pwsh.exe"); fs::write(&executable_path, "").unwrap(); - assert!(!is_local_pwsh_apphost(&executable_path)); + assert!(resolve_local_pwsh_apphost_payload_dir(&executable_path).is_none()); + + fs::write(temp_dir.path().join("pwsh.dll"), "").unwrap(); + assert!(resolve_local_pwsh_apphost_payload_dir(&executable_path).is_none()); + + fs::remove_file(temp_dir.path().join("pwsh.dll")).unwrap(); + fs::write(temp_dir.path().join("pwsh.runtimeconfig.json"), "{}").unwrap(); + assert!(resolve_local_pwsh_apphost_payload_dir(&executable_path).is_none()); + } + + #[test] + fn is_local_pwsh_apphost_rejects_runtime_native_missing_shared_payload() { + let temp_dir = TempDir::new().unwrap(); + let native_dir = temp_dir.path().join("runtimes").join("linux-x64").join("native"); + fs::create_dir_all(&native_dir).unwrap(); + let executable_path = native_dir.join("pwsh"); + fs::write(&executable_path, "").unwrap(); + + assert!(resolve_local_pwsh_apphost_payload_dir(&executable_path).is_none()); fs::write(temp_dir.path().join("pwsh.dll"), "").unwrap(); - assert!(!is_local_pwsh_apphost(&executable_path)); + assert!(resolve_local_pwsh_apphost_payload_dir(&executable_path).is_none()); fs::remove_file(temp_dir.path().join("pwsh.dll")).unwrap(); fs::write(temp_dir.path().join("pwsh.runtimeconfig.json"), "{}").unwrap(); - assert!(!is_local_pwsh_apphost(&executable_path)); + assert!(resolve_local_pwsh_apphost_payload_dir(&executable_path).is_none()); } #[test] diff --git a/crates/multi-pwsh/src/mcp.rs b/crates/multi-pwsh/src/mcp.rs index 193c5bb..1c8e342 100644 --- a/crates/multi-pwsh/src/mcp.rs +++ b/crates/multi-pwsh/src/mcp.rs @@ -176,13 +176,10 @@ struct CommandParameterMetadata { type_name: String, } -pub fn run_stdio_mcp_server(executable: &Path, commands: &[String]) -> Result> { - let pwsh_dir = executable.parent().ok_or_else(|| { - format!( - "failed to determine the PowerShell home directory from {}", - executable.display() - ) - })?; +pub fn run_stdio_mcp_server_for_pwsh_dir( + pwsh_dir: &Path, + commands: &[String], +) -> Result> { let server = HostMcpServer::new(pwsh_dir, commands)?; let runtime = tokio::runtime::Builder::new_current_thread() .enable_io() diff --git a/docs/feature-matrix.md b/docs/feature-matrix.md index 687f577..667f6d7 100644 --- a/docs/feature-matrix.md +++ b/docs/feature-matrix.md @@ -14,7 +14,7 @@ This matrix reflects the current command surface and known gaps for `multi-pwsh` | List | `multi-pwsh list [--scope ] [--root ] [--available] [--include-prerelease]` | Installed listing shows paths, resolved aliases, named alias policies, and minor pins. Available listing queries GitHub releases; installed listings include prerelease versions automatically. | | Offline cache | `multi-pwsh cache warm [--os ] [--arch ]` | Creates relocatable offline release bundles containing PowerShell archives, checksums, manifests, and optional `multi-pwsh` release artifacts. | | Alias | `multi-pwsh alias set/unset` for `major.minor`, `pwsh`, `pwsh-preview`, and `pwsh-lts` | Minor aliases can be pinned or follow latest in line. Named aliases store policies and resolve only to installed versions. | -| Host | `multi-pwsh host [pwsh arguments...]` | Runs through the native host. Alias shims can invoke host mode implicitly from the managed bin directory; a renamed local `pwsh`/`pwsh.exe` can also host an adjacent `pwsh.dll` plus `pwsh.runtimeconfig.json` SDK payload. | +| Host | `multi-pwsh host [pwsh arguments...]` | Runs through the native host. Alias shims can invoke host mode implicitly from the managed bin directory; a renamed local `pwsh`/`pwsh.exe` can also host adjacent or `runtimes//native/` SDK payload layouts. | | MCP host bridge | `multi-pwsh host -mcp -McpCommands [command ...]` | Starts a stdio MCP server over a hosted PowerShell runspace and exposes selected commands as tools. Extra `pwsh` arguments are rejected in MCP mode; `-venv` is supported. | | Virtual environments | `multi-pwsh venv create/delete/export/import/list` plus host `-VirtualEnvironment` / `-venv` | Provides a managed module root for hosted PowerShell launches. | | Doctor | `multi-pwsh doctor --repair-aliases` | Repairs host shims, alias files, and managed named alias policy resolutions. | @@ -77,7 +77,7 @@ Install, update, uninstall, and `doctor --repair-aliases` all reconcile aliases. | --- | --- | --- | | Native host launch | Yes | `multi-pwsh host` resolves selectors to installed executables and runs through `pwsh-host`. | | Implicit shim host mode | Yes | Alias shims detect their own name and layout, then run the matching selector. | -| Local `pwsh` apphost replacement | Yes | Exact `pwsh`/`pwsh.exe` beside `pwsh.dll` and `pwsh.runtimeconfig.json` bypasses alias policy and hosts that adjacent payload directly. | +| Local `pwsh` apphost replacement | Yes | Exact `pwsh`/`pwsh.exe` bypasses alias policy and hosts either adjacent `pwsh.dll`/`pwsh.runtimeconfig.json` markers or the shared payload three directories above `runtimes//native/`. | | Reusable AppHost NuGet mode | Yes | `Devolutions.MultiPwsh.Cli` packages RID-specific binaries and opt-in `buildTransitive` targets for downstream apphost replacement. | | MCP stdio server | Yes | Exposes explicitly selected PowerShell commands as MCP tools using the selected hosted version; tool names normalize to `powershell_*`. | | Virtual environment module path | Yes | Host mode sets startup-hook environment variables and bootstraps module cmdlet aliases for `-Command` and stdin `-File -` scenarios. | diff --git a/docs/host-and-venv.md b/docs/host-and-venv.md index 627c2db..d83f399 100644 --- a/docs/host-and-venv.md +++ b/docs/host-and-venv.md @@ -19,9 +19,9 @@ As an advanced replacement workflow, `multi-pwsh` can be renamed to `pwsh`/`pwsh.exe` and placed directly in a PowerShell SDK/apphost output directory. This mode is intentionally separate from managed alias-shim mode. -Detection uses the executable path reported by the OS, not the current working directory and not `PATH`. It activates only when the executable name is exactly `pwsh` or `pwsh.exe` and the same directory contains both `pwsh.dll` and `pwsh.runtimeconfig.json`. Additional files such as `System.Management.Automation.dll`, `Microsoft.PowerShell.ConsoleHost.dll`, and `Modules/` are expected in complete PowerShell payloads but are not required as marker files. +Detection uses the executable path reported by the OS, not the current working directory and not `PATH`. It activates only when the executable name is exactly `pwsh` or `pwsh.exe` and either the same directory contains both `pwsh.dll` and `pwsh.runtimeconfig.json`, or the executable is under `runtimes//native/` and those marker files exist three directories up at the shared publish root. Additional files such as `System.Management.Automation.dll`, `Microsoft.PowerShell.ConsoleHost.dll`, and `Modules/` are expected in complete PowerShell payloads but are not required as marker files. -When this local payload probe succeeds, `multi-pwsh` bypasses the managed `pwsh` alias policy and layout-shim inference, then hosts the adjacent `pwsh.dll` directly. Host-side preprocessing still applies, including `-venv` / `-VirtualEnvironment`, `-NamedPipeCommand`, stdin command rewriting, MCP mode, startup-hook setup, and PowerShell update-check suppression. +When this local payload probe succeeds, `multi-pwsh` bypasses the managed `pwsh` alias policy and layout-shim inference, then hosts `pwsh.dll` from the resolved payload directory. Host-side preprocessing still applies, including `-venv` / `-VirtualEnvironment`, `-NamedPipeCommand`, stdin command rewriting, MCP mode, startup-hook setup, and PowerShell update-check suppression. Hostfxr loading is app-local first. If `hostfxr` is not present beside the payload, `pwsh-host` falls back to the .NET hosting layer via `nethost`/global .NET roots, which supports framework-dependent SDK build output. Self-contained payloads still need their app-local hosting files such as `hostfxr` and `hostpolicy`. diff --git a/nuget/Devolutions.MultiPwsh.Cli/README.md b/nuget/Devolutions.MultiPwsh.Cli/README.md index b9a6f2e..d703195 100644 --- a/nuget/Devolutions.MultiPwsh.Cli/README.md +++ b/nuget/Devolutions.MultiPwsh.Cli/README.md @@ -37,4 +37,4 @@ AppHost mode is inert by default; set `MultiPwshAppHostEnabled=true` to copy the ``` -Downstream SDK packages can copy the binary as `pwsh` or `pwsh.exe` beside their own `pwsh.dll` and `pwsh.runtimeconfig.json`. In that layout, `multi-pwsh` runs the adjacent payload directly instead of resolving `pwsh` from PATH. +Downstream SDK packages can copy the binary as `pwsh` or `pwsh.exe` beside their own `pwsh.dll` and `pwsh.runtimeconfig.json`, or place it under `runtimes//native/` with the shared PowerShell payload at the publish root. In both layouts, `multi-pwsh` runs the local payload directly instead of resolving `pwsh` from PATH. diff --git a/scripts/Build-NativeNuGetPackages.ps1 b/scripts/Build-NativeNuGetPackages.ps1 index f66a737..867c55a 100644 --- a/scripts/Build-NativeNuGetPackages.ps1 +++ b/scripts/Build-NativeNuGetPackages.ps1 @@ -118,8 +118,10 @@ function New-AppHostManifest { packageId = $PackageId packageVersion = $PackageVersion supportedRuntimeIdentifiers = $RuntimeIdentifiers + requiredPayloadFiles = @('pwsh.dll', 'pwsh.runtimeconfig.json') requiredAdjacentPayloadFiles = @('pwsh.dll', 'pwsh.runtimeconfig.json') - notes = 'This package supplies only the native PowerShell apphost executable. Consumers must place it beside their own PowerShell managed payload.' + supportedPayloadLayouts = @('adjacent', 'runtimeNativeSharedPayload') + notes = 'This package supplies only the native PowerShell apphost executable. Consumers must provide their PowerShell managed payload either beside the executable or at the shared root above runtimes//native.' assets = @($assets) } From 726d938b1bec07cb3f71d58def4f2e1bdc1a62c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Fri, 3 Jul 2026 14:50:14 -0400 Subject: [PATCH 2/2] Bump version to 0.14.1 Update Rust package metadata, lockfile entries, and documentation examples for the 0.14.1 release. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- Cargo.lock | 4 ++-- README.md | 10 +++++----- crates/multi-pwsh/Cargo.toml | 2 +- crates/pwsh-host/Cargo.toml | 2 +- docs/host-and-venv.md | 2 +- nuget/Devolutions.MultiPwsh.Cli/README.md | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d6dde9..c9b99c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -780,7 +780,7 @@ dependencies = [ [[package]] name = "multi-pwsh" -version = "0.14.0" +version = "0.14.1" dependencies = [ "flate2", "home", @@ -929,7 +929,7 @@ dependencies = [ [[package]] name = "pwsh-host" -version = "0.14.0" +version = "0.14.1" dependencies = [ "base64 0.13.1", "cfg-if 0.1.10", diff --git a/README.md b/README.md index f63d3f4..44d4167 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,14 @@ curl -fsSL https://github.com/Devolutions/multi-pwsh/releases/latest/download/in irm https://github.com/Devolutions/multi-pwsh/releases/latest/download/install-multi-pwsh.ps1 | iex ``` -Install a specific tag (example `v0.14.0`): +Install a specific tag (example `v0.14.1`): ```bash -curl -fsSL https://github.com/Devolutions/multi-pwsh/releases/download/v0.14.0/install-multi-pwsh.sh | bash -s -- v0.14.0 +curl -fsSL https://github.com/Devolutions/multi-pwsh/releases/download/v0.14.1/install-multi-pwsh.sh | bash -s -- v0.14.1 ``` ```powershell -& ([scriptblock]::Create((irm https://github.com/Devolutions/multi-pwsh/releases/download/v0.14.0/install-multi-pwsh.ps1))) -Version v0.14.0 +& ([scriptblock]::Create((irm https://github.com/Devolutions/multi-pwsh/releases/download/v0.14.1/install-multi-pwsh.ps1))) -Version v0.14.1 ``` Uninstall bootstrap scripts: @@ -288,7 +288,7 @@ Package authors can consume the launchers privately and map them into their own ```xml - + @@ -310,7 +310,7 @@ For a simple single-RID project, AppHost mode can copy the selected binary direc ```xml - + diff --git a/crates/multi-pwsh/Cargo.toml b/crates/multi-pwsh/Cargo.toml index f2280ed..bd54d41 100644 --- a/crates/multi-pwsh/Cargo.toml +++ b/crates/multi-pwsh/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "multi-pwsh" -version = "0.14.0" +version = "0.14.1" edition = "2018" license = "MIT/Apache-2.0" homepage = "https://github.com/Devolutions/multi-pwsh" diff --git a/crates/pwsh-host/Cargo.toml b/crates/pwsh-host/Cargo.toml index 30e37be..92635df 100644 --- a/crates/pwsh-host/Cargo.toml +++ b/crates/pwsh-host/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pwsh-host" -version = "0.14.0" +version = "0.14.1" edition = "2018" license = "MIT/Apache-2.0" homepage = "https://github.com/Devolutions/pwsh-host-rs" diff --git a/docs/host-and-venv.md b/docs/host-and-venv.md index d83f399..8d13f2f 100644 --- a/docs/host-and-venv.md +++ b/docs/host-and-venv.md @@ -33,7 +33,7 @@ Typical downstream vendored-SDK usage: ```xml - + diff --git a/nuget/Devolutions.MultiPwsh.Cli/README.md b/nuget/Devolutions.MultiPwsh.Cli/README.md index d703195..89b0311 100644 --- a/nuget/Devolutions.MultiPwsh.Cli/README.md +++ b/nuget/Devolutions.MultiPwsh.Cli/README.md @@ -6,7 +6,7 @@ The normal CLI payload is copied under `runtimes//native/` for build and pu ```xml - + @@ -28,7 +28,7 @@ AppHost mode is inert by default; set `MultiPwshAppHostEnabled=true` to copy the ```xml - +