When the WSL2 Round-Trip Started Feeling Slow — v2.1.84
Claude Code v2.1.84, released in April 2026, introduced the PowerShell tool as an opt-in preview. This is a significant milestone for Windows developers: you can now use PowerShell as a native tool within Claude Code sessions, without needing to route everything through WSL (Windows Subsystem for Linux).
Until now, Windows users typically relied on WSL2 and Bash to interact with Claude Code. The PowerShell tool opens up native Windows capabilities — file system operations, registry access, Azure/Active Directory integration, and more — directly from your Claude Code workflow.
What Problem Does the PowerShell Tool Solve?
Working with Claude Code on Windows before this release came with a few pain points:
Limitations of the WSL2 approach:
- Mixed path formats between Windows and Linux (
\\wsl$\paths) caused frequent resolution errors - Accessing the Windows registry or COM objects required workarounds
- Running
.ps1scripts directly wasn't straightforward - Integrations with Active Directory or Azure AD needed extra bridging layers
The PowerShell tool eliminates these friction points, bringing native Windows development and IT operations directly into Claude Code.
Prerequisites
Before enabling the PowerShell tool, make sure you have the following:
- Claude Code v2.1.84 or later
- Windows 10 / 11 or Windows Server 2019 / 2022
- PowerShell 5.1 or PowerShell 7.x (recommended)
- Node.js 18 or later (Claude Code runtime)
To check your PowerShell version:
# Check PowerShell version
$PSVersionTable.PSVersion
# Example output
Major Minor Build Revision
----- ----- ----- --------
7 4 7 0PowerShell 7.x (PowerShell Core) is strongly recommended for its improved security features and cross-platform design.
How to Enable the PowerShell Tool
The PowerShell tool is currently an opt-in preview. Here are two ways to turn it on:
Option 1: Via the /config Menu
Launch Claude Code and run the /config command:
/config
Navigate to "Tools & Execution" in the settings menu. Find the "PowerShell Tool (Preview)" entry and press Enter to enable it. Press Esc to cancel, or Enter to save your changes.
Option 2: Edit settings.json Directly
Open ~/.claude/settings.json (on Windows: %USERPROFILE%\.claude\settings.json) and add the following:
{
"tools": {
"powershell": {
"enabled": true,
"version": "pwsh"
}
}
}Set "version" to "pwsh" for PowerShell 7 or "powershell" for Windows PowerShell 5.1. Restart Claude Code after saving to apply the changes.
Verifying the Setup
Once Claude Code restarts, you can confirm the tool is active by asking:
Do your available tools include PowerShell?
If everything is configured correctly, Claude will confirm it can execute PowerShell commands.
Basic Usage
With the PowerShell tool enabled, Claude Code can execute PowerShell alongside Bash. On Windows, it will automatically select the appropriate tool based on context.
File and Directory Operations
# List all TypeScript files recursively
Get-ChildItem -Path . -Recurse -Filter "*.ts" | Select-Object FullName
# Read file contents
Get-Content .\src\components\Button.tsx
# Create a new directory
New-Item -ItemType Directory -Path ".\src\features\auth"
# Create a backup of a config file
Copy-Item .\config.json .\config.json.bakGathering System Information
# Check installed Node.js version
node --version
# Output: v22.14.0
# Available memory
Get-CimInstance Win32_OperatingSystem | Select-Object FreePhysicalMemory, TotalVisibleMemorySize
# Read a user environment variable
[System.Environment]::GetEnvironmentVariable("PATH", "User")Package Management
# List top-level npm packages
npm list --depth=0
# Update global packages
npm update -g
# Install with pnpm (frozen lockfile)
pnpm install --frozen-lockfileSecurity Features Explained
The v2.1.84 release introduced several hardened security behaviors specifically for the PowerShell tool. These integrate with Claude Code's existing dangerous-command detection system.
Background Job Bypass Prevention
The PowerShell & operator at the end of a command can be used to sidestep security checks by running processes in the background. Claude Code detects trailing & usage and prompts for confirmation before proceeding.
# ⚠️ This will trigger a confirmation prompt
Start-Process notepad.exe &
# ✅ Normal execution — no prompt
Start-Process notepad.exe-ErrorAction Break Control
The -ErrorAction Break parameter can cause the PowerShell debugger to hang in certain conditions. Claude Code monitors for this and handles it safely so your session stays responsive.
Archive Extraction TOCTOU Prevention
Commands like Expand-Archive are checked for path safety before executing. This guards against Time-of-Check-Time-of-Use (TOCTOU) attacks where the target path could change between validation and execution.
PS 5.1 Argument Splitting Hardening
When external command arguments contain both double-quotes and whitespace, PowerShell 5.1 has known argument-splitting quirks. Claude Code detects this pattern and prompts for confirmation rather than auto-approving.
# ⚠️ Claude Code will prompt for this on PS 5.1
# (arguments contain double-quotes AND whitespace)
& "C:\Program Files\MyApp\app.exe" "input file.txt"Persistent Setup Patterns and Windows-Specific Pitfalls
So far we've covered /config and settings.json for enabling the tool, but in practice you'll hit Windows-specific issues: settings disappearing after reboot, VS Code not picking them up, PowerShell execution policies blocking scripts. Here are three persistence patterns I've settled on after running this on multiple Windows development machines.
Pattern 1: PowerShell Profile ($PROFILE) — Personal Dev Machines
Writing the environment variable into $PROFILE (loaded on every PowerShell start) is the most flexible and recommended approach.
# Find the profile path (will be created if missing)
echo $PROFILE
# Example: C:\Users\YourName\Documents\PowerShell\Microsoft.PowerShell_profile.ps1
# Edit it
notepad $PROFILEAdd this line and save:
# Enable Claude Code PowerShell tool by default
$env:CLAUDE_CODE_USE_POWERSHELL_TOOL = "1"The setting takes effect in new terminals. To apply immediately in the current shell, run . $PROFILE.
Pattern 2: Windows System Environment Variable — Team Distribution
If you launch from multiple terminals or GUI tools (VS Code, Cursor, etc.) and want the variable always set, register it as a Windows system environment variable.
- Windows key → search "environment variables" → "Edit the system environment variables"
- Click "Environment Variables..."
- Under "User variables," click "New..."
- Variable name:
CLAUDE_CODE_USE_POWERSHELL_TOOL, Value:1 - OK to save (existing terminals need to be restarted)
This pattern fits well into team setup scripts when you want all developers configured identically.
Pattern 3: Using It from the VS Code Integrated Terminal
When launching claude from VS Code, the most reliable approach is to make PowerShell the default terminal in settings.json:
{
"terminal.integrated.defaultProfile.windows": "PowerShell"
}Combined with Pattern 1 or 2 above, the variable is inherited automatically in the integrated terminal.
Common Pitfall: "Running Scripts Is Disabled on This System"
You may hit Set-ExecutionPolicy restrictions occasionally. In some organizations the policy can't be changed, so check the current state before deciding.
# Check the current policy
Get-ExecutionPolicy
# Allow scripts for the current user (the most conservative useful change)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserRemoteSigned allows local scripts and requires signature verification only for downloaded ones — a reasonable middle ground for individual developers and small teams. On managed corporate PCs, check with IT before changing this.
Real-World Workflow Examples
1. Azure Resource Management
# Show current Azure account details
az account show --output json | ConvertFrom-Json | Select-Object name, id
# List all resource groups
az group list --output table
# Check App Service running state
az webapp show --name my-app --resource-group my-rg --query "state" --output tsvYou can ask Claude directly:
List all resources in our dev resource group and identify any App Services
that haven't been accessed in the past 30 days.
2. Active Directory / Entra ID Operations
# Import the AD module (requires domain-joined machine)
Import-Module ActiveDirectory
# Check which groups a user belongs to
Get-ADUser -Identity "john.doe" -Properties MemberOf | Select-Object MemberOf
# Find disabled accounts
Search-ADAccount -AccountDisabled -UsersOnly | Select-Object Name, SamAccountName3. Windows Event Log Analysis
# Get error events from the last 24 hours
$since = (Get-Date).AddHours(-24)
Get-WinEvent -LogName "Application" -FilterHashtable @{
Level = 2
StartTime = $since
} | Select-Object TimeCreated, Id, Message | Format-Table -AutoSizeAsk Claude to "analyze the application event log for anomalies," and it will run this query and interpret the results for you.
4. CI/CD Pipeline Support
# Generate build version string
$buildNumber = $env:BUILD_BUILDNUMBER ?? "local"
$version = "1.0.$buildNumber"
Write-Host "##[set-variable name=Version;value=$version]"
# Run tests and parse results
$testResult = dotnet test --logger trx --results-directory .\TestResults 2>&1
$failed = $testResult | Select-String "Failed: (\d+)" | ForEach-Object { $_.Matches[0].Groups[1].Value }
if ([int]$failed -gt 0) { exit 1 }Using /env with the PowerShell Tool
The PowerShell tool is fully integrated with Claude Code's /env command. Environment variables you set persist across both Bash and PowerShell tool invocations within the same session.
/env NODE_ENV=production
/env AZURE_SUBSCRIPTION_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
This makes it easy to simulate CI/CD environments or manage secrets locally during development.
Troubleshooting
Error: "Running scripts is disabled on this system"
File C:\...\script.ps1 cannot be loaded because running scripts is disabled on this system.
Fix: Check and update your PowerShell execution policy.
# See current policies
Get-ExecutionPolicy -List
# Allow local scripts for the current user (recommended)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserError: "pwsh is not found"
This happens when settings.json specifies "version": "pwsh" but PowerShell 7 isn't installed.
# Install PowerShell 7 via winget
winget install --id Microsoft.PowerShell -e
# Or download the MSI directly from GitHub Releases
# https://github.com/PowerShell/PowerShell/releasesRestart Claude Code after installation.
Too Many Confirmation Prompts
If harmless commands are triggering confirmation dialogs, you can add them to an allow-list:
// In settings.json
{
"tools": {
"powershell": {
"allowedCommands": [
"Get-ChildItem",
"Get-Content",
"Get-CimInstance"
]
}
}
}Two Switch Decisions That Trip People Up
PowerShell 5.1 vs. PowerShell 7 (pwsh)
Default to PowerShell 7. It's faster, cross-platform, has better security primitives, and supports newer syntax. The exception is when a Windows-only module like ActiveDirectory ships only for 5.1 — in that case, switch back per task. There's no global "right answer" here, but 7 is the right starting point.
Does the PowerShell tool make WSL2 obsolete?
Not quite. WSL2 still wins when you need Linux-specific tooling, Docker, or native Python/C builds. The PowerShell tool wins for Windows-native administration and .NET automation. The rule of thumb I use: "Linux-leaning task → WSL2, Windows-leaning task → PowerShell tool."
Recipe 1: Bootstrapping a Project with winget
Every README has a "install these tools" section, and most teams still install them by hand. Asking Claude to run a small winget block solves it once and gracefully skips already-installed tools.
# What runs when you ask Claude: "install everything this repo needs via winget"
$tools = @("Microsoft.PowerShell", "GitHub.cli", "Microsoft.DotNet.SDK.9", "Microsoft.VisualStudioCode")
foreach ($id in $tools) {
if (-not (winget list --id $id -e --accept-source-agreements 2>$null | Select-String $id)) {
winget install --id $id -e --silent --accept-source-agreements --accept-package-agreements
} else {
Write-Host "Skip: $id (already installed)"
}
}The detail that matters is the winget list check before winget install. Skipping it leads Claude into double-installs that error out the whole script.
Recipe 2: Pairing PR Workflows with the gh CLI
GitHub CLI is fine in WSL, but installing gh on the Windows side lets you push and open PRs directly from PowerShell Tool calls. My favorite shortcut is to ask Claude to apply edits, commit, push, and open a PR in one go.
# branch -> commit -> push -> PR creation in one line
$branch = "feat/$(Get-Date -Format yyyyMMdd-HHmm)-claude-edit"
git checkout -b $branch
git add -A
git commit -m "Apply Claude edits"
git push -u origin $branch
gh pr create --fill --webgh pr create --fill --web autofills the body and opens the browser, so you keep a final review checkpoint without slowing down. If you'd rather have Claude write the body, swap --fill for --body "$(cat .git/CLAUDE_NOTE.md)".
Recipe 3: One Command for dotnet Test + Coverage
dotnet test alone doesn't give you coverage; you usually need coverlet. Saving the right invocation as a recipe means Claude reproduces the same quality gate every time.
$proj = ".\src\MyApp.Tests\MyApp.Tests.csproj"
dotnet test $proj `
--collect:"XPlat Code Coverage" `
--results-directory ./TestResults `
--logger "console;verbosity=normal"
$cov = Get-ChildItem .\TestResults -Recurse -Filter coverage.cobertura.xml | Sort-Object LastWriteTime -Descending | Select-Object -First 1
Write-Host "Coverage report: $($cov.FullName)"PowerShell's backtick line continuation feels odd if you came from Bash. Tell Claude once to break long commands with backticks and it sticks to that style.
Recipe 4: Crossing the WSL/PowerShell Boundary Safely
Even after PowerShell Tool gets traction, you still want WSL for tools that shine on Linux (latest ripgrep, jq, and friends). Calling WSL from PowerShell is the bridge — but path translation is where it bites.
# Correct way to grep a Windows path from WSL
$winPath = "C:\Users\me\proj"
$wslPath = wsl wslpath -a "$winPath"
wsl rg --hidden --no-ignore "TODO" $wslPathwslpath -a converts cleanly to POSIX form. Hand C:\Users\me\proj directly to WSL and you'll get "No such file" — Claude will keep doing this unless you record the recipe.
Recipe 5: Wiring Stripe and Firebase CLIs Into the Loop
I keep Stripe CLI and Firebase CLI on the Windows side. Once Claude can reach them through PowerShell Tool, debugging payments and AdMob feels notably less painful.
# Pull the last 100 events and surface only the failed payment intents
stripe events list --limit 100 --format json | `
ConvertFrom-Json | `
Where-Object { $_.type -like "*payment_intent.payment_failed*" } | `
Select-Object id, created, type, @{n="amount";e={$_.data.object.amount}} |
Format-Table -AutoSizeAsk Claude "show me recent payment failures" and it will write and run something like this. Save the pattern in your CLAUDE.md ("for Stripe event analysis, use ConvertFrom-Json filters") so the next conversation skips the explanation.
Recipe 6: Restarting Windows Services Without Permission Surprises
Restart-Service often requires admin. Without guardrails, Claude tries it, hits a permission error, and silently moves on. I have Claude write a wrapper that explicitly nudges toward elevation when needed.
function Restart-ServiceIfPossible {
param([string]$ServiceName)
$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if (-not $svc) { Write-Warning "Service not found: $ServiceName"; return }
try {
Restart-Service -Name $ServiceName -Force -ErrorAction Stop
Write-Host "Restarted: $ServiceName"
} catch {
Write-Warning "Restart failed (likely needs admin). Try: Start-Process pwsh -Verb RunAs"
}
}Through PowerShell Tool, -Verb RunAs opens a separate elevated process so the elevation prompt never blocks your existing session.
Before You Let These Run Unattended — Gate the Risky Operations
These recipes are comfortable when you watch each result, but Auto Mode changes the calculus. Commands that rewrite your environment — winget install, Restart-Service — can suddenly run without a confirmation step.
My rule of thumb is: let reads and lookups through, but pause on anything that changes state. The sturdiest PowerShell-side move is to add -WhatIf to state-changing cmdlets so Claude shows the plan first.
# Preview destructive operations with -WhatIf before applying
function Invoke-ServiceRestartSafely {
param([string]$ServiceName, [switch]$Apply)
if (-not $Apply) {
Restart-Service -Name $ServiceName -WhatIf
Write-Host "Plan only. Re-run with -Apply to execute."
return
}
Restart-Service -Name $ServiceName -Force
}Note in your CLAUDE.md that destructive operations should be previewed with -WhatIf first, so cmdlets like Restart-Service or Remove-Item don't run for real on their own. Pair that with Claude Code's /permissions — allow lookups such as winget list, but require confirmation for installers like winget install — and even an unattended session keeps a hard stop right before anything that could break. It dovetails nicely with the elevation-prompt wrapper from Recipe 6.
Sharing Recipes Across a Team
You can keep these in your personal Microsoft.PowerShell_profile.ps1, but I prefer dropping them in .tools/recipes.ps1 at the repo root so the team picks them up. Note the path in CLAUDE.md ("dot-source .tools/recipes.ps1 before running PowerShell"), and Claude will load the same recipes every session.
# .tools/recipes.ps1 entry point
. "$PSScriptRoot\install-deps.ps1"
. "$PSScriptRoot\test-coverage.ps1"
. "$PSScriptRoot\service-helpers.ps1"Looking back
The PowerShell tool in Claude Code v2.1.84 is a meaningful step forward for Windows developers. Here's what to take away:
- Enable it via
/configorsettings.jsonas an opt-in preview - It runs alongside the Bash tool, giving Claude Code native access to Windows resources
- Built-in security hardening covers background job bypasses, TOCTOU risks, and PS 5.1 argument quirks
- Ideal for Azure, Active Directory, event log analysis, and enterprise CI/CD workflows
- The
/envcommand works seamlessly across both PowerShell and Bash tools
If you want finer control over what Claude Code is allowed to run, check out Claude Code Tool Permissions: Custom Allow/Deny Policies, which covers mixing allow, ask, and deny rules to balance safety and development speed.
A Concrete Next Step
Tomorrow morning, add one line to your repo's README: which PowerShell script Claude should run first via the PowerShell Tool. Even something as small as "run ./scripts/setup.ps1 before anything else" reshapes how Claude opens its first session. Recipes are an asset that compound — the work pays back the second time you reach for them.