Skip to content

feat: Powershell integration#421

Merged
jdx merged 4 commits intojdx:mainfrom
nbfritch:powershell
Apr 18, 2026
Merged

feat: Powershell integration#421
jdx merged 4 commits intojdx:mainfrom
nbfritch:powershell

Conversation

@nbfritch
Copy link
Copy Markdown
Contributor

See earlier discussion: #412

This PR adds Powershell support for the activate/deactivate/hook-env commands, allowing support for auto loading like all the existing shells.

Copilot was used to generate a starting point by referencing the src/shell/bash.rs file from fnox and the src/shell/pwsh.rs file from mise. From there, I cleaned up the escaping and added support for displaying the env diff (fnox: +1 FOO / fnox: -1 FOO).

There are 2 possible names for the powershell binary, powershell and pwsh. I elected to use pwsh in the help, in order to avoid lines wrapping around on smaller terminal windows.

For bash/zsh/fish the flow is: eval the output of the activate command, which sets up a hook, which evaluates the output of the hook-env command, which generates code to both set the env variables and also print the env diff. I wanted the powershell to stick to this general flow. However, due to the way powershell evaluates code from the output of a command ( (& cmd) | Out-String | Invoke-Expression) the printed diff gets lost because printing in evaluated commands doesn't bubble up to the parent shell. To solve this hook-env creates a function __fnox_print_changes that contains the Write-Host statements. The powershell code generated by the activate/deactivate command then checks for the existence of that function, evaluates it if it exists, then deletes it.

I tested the powershell integration on the following platforms:

  • MacOS 26.4.1 aarch64, powershell 7.6.0
  • Windows 10 x86_64, powershell 7.6.0
  • Windows 10 x86_64, powershell 5.1.0

Since powershell is different enough from the usual shells (bash,fish,zsh) I left it off the quick start page and lumped it in with nushell in pointing towards the Shell Integration page.

Thank you for your work on this great project!

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for PowerShell (pwsh) integration, including documentation updates, CLI help text modifications, and a new Pwsh shell implementation. The review feedback highlights several critical improvements for the PowerShell implementation: correcting environment variable syntax to support non-alphanumeric keys, fixing the escaping logic for single-quoted literals where backticks are not valid escape characters, ensuring the original shell prompt is restored upon deactivation, and using -LiteralPath for robust environment variable removal.

Comment thread src/shell/pwsh.rs
Comment thread src/shell/pwsh.rs
Comment thread src/shell/pwsh.rs
Comment thread src/shell/pwsh.rs
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 17, 2026

Greptile Summary

This PR adds PowerShell (pwsh/powershell) as a supported shell for the activate/deactivate/hook-env commands, including a new src/shell/pwsh.rs implementation, docs, and bats tests. The implementation correctly handles prompt save/restore in deactivate, single-quote escaping for environment variable values, and the creative __fnox_print_changes function workaround for surfacing Write-Host output through Invoke-Expression.

Confidence Score: 5/5

Safe to merge; all findings are P2 style suggestions that do not affect correctness for typical installation paths.

The implementation is well-structured, tested on three platforms, and the previously flagged deactivate cleanup issue has been resolved. All remaining comments are minor style or defensive-coding suggestions with no present runtime defects.

src/shell/pwsh.rs — minor issues with exe path quoting and Invoke-Expression error suppression worth reviewing before the next major release.

Important Files Changed

Filename Overview
src/shell/pwsh.rs New PowerShell shell implementation; core logic is sound with correct prompt save/restore in deactivate(), single-quote escaping in set_env/unset_env, and the __fnox_print_changes workaround for Write-Host output. Minor issues: exe path embedded in double-quoted strings without expansion-safety, and -ErrorAction SilentlyContinue on Invoke-Expression silently swallows errors.
src/shell/mod.rs Adds pwsh and powershell as aliases to the shell dispatch table; clean and consistent with the existing pattern.
test/shell-integration.bats Adds four new bats tests covering activate, activate --no-hook-env, hook-env output, and single-quote escaping for pwsh; well-structured and match actual generated output format.
docs/guide/shell-integration.md Adds a PowerShell tab to the shell-integration guide with the correct activation incantation and link to profile documentation.
src/commands/activate.rs Doc comment updated to list pwsh as a supported shell; no functional changes.
src/commands/hook_env.rs Doc comment updated to list pwsh as a supported shell; no functional changes.

Sequence Diagram

sequenceDiagram
    participant PS as PowerShell prompt
    participant FnoxFn as fnox() wrapper
    participant FnoxBin as fnox binary
    participant Hook as _fnox_hook()

    Note over PS: User sources activate script
    PS->>FnoxBin: fnox activate pwsh
    FnoxBin-->>PS: Sets $env:FNOX_SHELL, defines fnox(), _fnox_hook(), patches prompt

    Note over PS: Each prompt render
    PS->>Hook: global:prompt() calls _fnox_hook
    Hook->>FnoxBin: hook-env -s pwsh
    FnoxBin-->>Hook: PowerShell env-set code + optional __fnox_print_changes fn
    Hook->>Hook: Invoke-Expression (sets env vars)
    Hook->>Hook: __fnox_print_changes() calls Write-Host diff
    Hook->>Hook: Remove-Item Function:\__fnox_print_changes

    Note over PS: User runs: fnox deactivate
    PS->>FnoxFn: fnox deactivate
    FnoxFn->>FnoxBin: fnox deactivate (passthrough)
    FnoxBin-->>FnoxFn: PowerShell cleanup code
    FnoxFn->>FnoxFn: Invoke-Expression (restores prompt, removes functions/env vars)
Loading

Reviews (3): Last reviewed commit: "Fix formatting for bats test" | Re-trigger Greptile

Comment thread src/shell/pwsh.rs
Comment thread src/shell/pwsh.rs Outdated
…er '-LiteralPath' for invoking 'Remove-Item', simplify escape function since it only escapes values, use instead of and
@jdx jdx merged commit c1119c8 into jdx:main Apr 18, 2026
14 checks passed
jdx pushed a commit that referenced this pull request Apr 21, 2026
### 🚀 Features

- Powershell integration by [@nbfritch](https://github.com/nbfritch) in
[#421](#421)

### 🐛 Bug Fixes

- **(Windows)** Nushell integration by
[@john-trieu-nguyen](https://github.com/john-trieu-nguyen) in
[#425](#425)
- **(Windows)** Command resolution for executables by
[@john-trieu-nguyen](https://github.com/john-trieu-nguyen) in
[#427](#427)

### 📚 Documentation

- add releases nav and aube lock by [@jdx](https://github.com/jdx) in
[#422](#422)
- include linux native packages in aube lock by
[@jdx](https://github.com/jdx) in
[#423](#423)

### 🔍 Other Changes

- Use published `clap-sort` crate instead of inlined module by
[@jdx](https://github.com/jdx) in
[#409](#409)
- add communique 1.0.1 by [@jdx](https://github.com/jdx) in
[#424](#424)

### 📦️ Dependency Updates

- lock file maintenance by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#381](#381)
- update taiki-e/upload-rust-binary-action digest to 10c1cf6 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#383](#383)
- update rust crate tokio to v1.51.1 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#384](#384)
- update rust crate indexmap to v2.14.0 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#385](#385)
- update rust crate rmcp to v1.4.0 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#389](#389)
- update rust crate strum to 0.28 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#395](#395)
- update rust crate toml_edit to 0.25 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#396](#396)
- update rust crate miniz_oxide to 0.9 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#390](#390)
- update rust crate ratatui to 0.30 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#392](#392)
- update actions/checkout action to v6 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#397](#397)
- update actions/deploy-pages action to v5 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#399](#399)
- update actions/configure-pages action to v6 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#398](#398)
- update actions/setup-node action to v6 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#400](#400)
- update actions/upload-pages-artifact action to v4 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#401](#401)
- update dependency node to v24 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#403](#403)
- update apple-actions/import-codesign-certs action to v6 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#402](#402)
- update nick-fields/retry action to v4 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#406](#406)
- update github artifact actions (major) by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#404](#404)
- update jdx/mise-action action to v4 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#405](#405)
- update rust crate which to v8 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#408](#408)
- update rust crate usage-lib to v3 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#407](#407)
- bump rustcrypto stack (aes-gcm, sha2, hkdf) together by
[@jdx](https://github.com/jdx) in
[#410](#410)
- update rust crate reqwest to 0.13 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#393](#393)
- update rust crate libloading to 0.9 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#388](#388)
- update rust crate keepass to 0.10 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#387](#387)
- update rust crate rand to 0.10 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#391](#391)
- lock file maintenance by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#411](#411)
- update rust crate google-cloud-secretmanager-v1 to v1.8.0 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#415](#415)
- update actions/upload-pages-artifact action to v5 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#418](#418)
- update rust crate rmcp to v1.5.0 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#416](#416)
- update rust crate clap to v4.6.1 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#413](#413)
- update rust crate tokio to v1.52.1 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#417](#417)
- update rust crate keepass to v0.10.6 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#414](#414)
- update taiki-e/upload-rust-binary-action digest to f0d45ae by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#419](#419)
- update rust crate aws-sdk-sts to v1.102.0 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#420](#420)

### New Contributors

- @john-trieu-nguyen made their first contribution in
[#427](#427)
- @nbfritch made their first contribution in
[#421](#421)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants