cheat sheet
WSL Interoperability
Running Linux tools from Windows and vice versa, file system access, and networking between WSL and Windows.
WSL Interoperability
What it is
WSL Interop is the bridging layer that lets Windows and Linux talk to each other inside a WSL installation — Windows binaries are callable from a Linux shell as if they were native (notepad.exe README.md), Linux binaries are callable from PowerShell or CMD via the wsl <cmd> prefix, paths translate via wslpath, env vars cross the boundary via WSLENV, and the two filesystems are visible through /mnt/<drive> and \\wsl.localhost\<Distro>\. This page focuses on the bridge; for installing, listing, exporting, and managing distros themselves (the wsl.exe distro CLI), see the wsl cheat sheet. Reach for these techniques when you want to use Linux tools to massage Windows data (or vice versa) without leaving your current shell.
Run Linux commands from PowerShell
The wsl <command> prefix routes any Linux command through the default WSL distribution without opening a shell session. Windows stdin and stdout are piped transparently, so you can compose wsl calls with PowerShell pipelines.
# Run a Linux command directly
wsl ls -la /home/alice
# Pipe Windows command into Linux
Get-Content .\data.csv | wsl awk -F, '{ print $2 }'
# Use Linux grep on Windows output
ipconfig | wsl grep "IPv4"
# Run a specific distro
wsl -d Ubuntu-22.04 -- cat /etc/os-release
Output (wsl ls -la /home/alice):
total 48
drwxr-x--- 6 alice alice 4096 Apr 26 09:15 .
drwxr-xr-x 3 root root 4096 Mar 10 12:00 ..
-rw-r--r-- 1 alice alice 220 Mar 10 12:00 .bash_logout
-rw-r--r-- 1 alice alice 3526 Mar 10 12:00 .bashrc
drwxr-xr-x 3 alice alice 4096 Apr 20 14:33 .config
drwxr-xr-x 2 alice alice 4096 Apr 25 10:02 projects
Output (Get-Content .\data.csv | wsl awk -F, '{ print $2 }'):
name
Alice
Bob
Carol
Output (ipconfig | wsl grep "IPv4"):
IPv4 Address. . . . . . . . . . . : 192.168.1.42
IPv4 Address. . . . . . . . . . . : 172.29.192.1
Output (wsl -d Ubuntu-22.04 -- cat /etc/os-release):
PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
Run Windows commands from WSL
From inside a WSL shell, Windows executables are accessible by their full name including the .exe extension — they live in the WSL PATH via the interop bridge. Use this to open Windows GUI apps, copy to the clipboard, or invoke PowerShell scripts without leaving your Linux terminal.
# Call Windows executables (the .exe is required)
notepad.exe README.md
explorer.exe .
clip.exe < ~/.ssh/id_ed25519.pub # copy SSH pubkey to clipboard
# Open Windows browser from WSL
cmd.exe /c start https://example.com
# Run PowerShell from WSL
powershell.exe -Command "Get-Date"
pwsh.exe -Command "Write-Host 'PowerShell 7'"
Output (powershell.exe -Command "Get-Date"):
Sunday, April 26, 2026 9:15:32 AM
Output (pwsh.exe -Command "Write-Host 'PowerShell 7'"):
PowerShell 7
File system paths
WSL mounts Windows drives under /mnt/<letter> (e.g. /mnt/c), and Windows can reach WSL files via the \\wsl.localhost\<Distro>\ UNC path. Use wslpath to convert between the two formats in scripts — hardcoding either style will break on the other side.
# Access Windows drives from WSL
ls /mnt/c/Users/Alice/Documents
# Windows path to WSL path
wslpath 'C:\Users\Alice\Documents' # → /mnt/c/Users/Alice/Documents
wslpath -w /home/alice/project # → \\wsl.localhost\Ubuntu\home\alice\project
# Access WSL files from Windows Explorer
# Type in address bar: \\wsl.localhost\Ubuntu\home\alice
Output (wslpath 'C:\Users\Alice\Documents'):
/mnt/c/Users/Alice/Documents
Output (wslpath -w /home/alice/project):
\\wsl.localhost\Ubuntu\home\alice\project
Networking
In WSL 2, the Linux kernel runs in a lightweight VM with its own virtual network adapter, so Linux and Windows are on separate IP addresses — though localhost is bridged automatically for most dev-server use cases. WSL 1 shares the Windows network stack directly and has no bridging issues, but lacks the full kernel. Use netsh portproxy when you need to expose a WSL 2 service to other machines on the LAN.
# Get Windows host IP from WSL (WSL2)
cat /etc/resolv.conf | grep nameserver | awk '{print $2}'
# or
ip route show default | awk '{print $3}'
# Expose WSL port to Windows (automatic in WSL2 for localhost)
# localhost:3000 in WSL → localhost:3000 in Windows browser
# Forward WSL port to LAN (run in PowerShell as Admin)
netsh interface portproxy add v4tov4 `
listenport=3000 listenaddress=0.0.0.0 `
connectport=3000 connectaddress=127.0.0.1
Output (cat /etc/resolv.conf | grep nameserver | awk '{print $2}'):
172.29.192.1
Output (ip route show default | awk '{print $3}'):
172.29.192.1
Useful WSL management commands (PowerShell)
# List installed distros
wsl --list --verbose
# Set default distro
wsl --set-default Ubuntu-22.04
# Shutdown all WSL instances
wsl --shutdown
# Export distro to backup
wsl --export Ubuntu-22.04 ubuntu-backup.tar
# Import from backup
wsl --import Ubuntu-Restored C:\WSL\Ubuntu ubuntu-backup.tar
# Set WSL version for a distro
wsl --set-version Ubuntu-22.04 2
Output (wsl --list --verbose):
NAME STATE VERSION
* Ubuntu-22.04 Running 2
Ubuntu-20.04 Stopped 2
Debian Stopped 1
For full coverage of distro lifecycle (wsl --install, wsl --import, wsl --export, wsl --set-version, etc.), see the wsl cheat sheet.
How the interop bridge works
The interop bridge lives in two cooperating pieces: a Linux-side daemon called init (PID 1) that owns the bridge socket on /run/WSL/<pid>_interop, and a Windows-side driver (lxss.sys) plus the Wsl.Host user-mode helper. When a Linux process calls a .exe filename, binfmt_misc matches the magic bytes, hands the call off to init, which serialises the execve over the bridge socket; on the Windows side, Wsl.Host launches the executable and pipes stdio back. The reverse path uses NT's \Device\WSLInterop and the Wsl.exe host process.
Inspect the bridge sockets at runtime:
ls -la /run/WSL/
# srwxrwxrwx 1 root root 0 May 24 09:00 12345_interop
# srwxrwxrwx 1 root root 0 May 24 09:01 12345_pty
# binfmt_misc entry that routes .exe to the interop layer
cat /proc/sys/fs/binfmt_misc/WSLInterop
Output:
enabled
interpreter /init
flags: PF
offset 0
magic 4d5a
/init is the bridge daemon — it is replaced by systemd on distros where systemd=true is set in /etc/wsl.conf, but the bridge logic still runs as a child of PID 1.
Disable interop at runtime (rarely useful but covered for completeness):
# Inside WSL
sudo sh -c 'echo 0 > /proc/sys/fs/binfmt_misc/WSLInterop'
# Re-enable
sudo sh -c 'echo 1 > /proc/sys/fs/binfmt_misc/WSLInterop'
# Disable persistently via /etc/wsl.conf
cat <<'EOF' | sudo tee /etc/wsl.conf
[interop]
enabled = false
appendWindowsPath = false
EOF
Output:
[interop]
enabled = false
appendWindowsPath = false
appendWindowsPath = false is the bigger lever — it removes every Windows PATH entry from the Linux $PATH, which is what most "I don't want cmd.exe in my Linux shell" requests really need.
/etc/wsl.conf — per-distro interop settings
/etc/wsl.conf is the distro-side configuration file consumed by /init at boot. Sections affect interop, mounting behaviour, network handling, and systemd. The interop and automount sections are the most relevant for bridging.
# /etc/wsl.conf
[automount]
enabled = true
options = "metadata,umask=22,fmask=11,uid=1000,gid=1000"
mountFsTab = true
root = /mnt/
[interop]
enabled = true
appendWindowsPath = true
[user]
default = alicedev
[boot]
systemd = true
[network]
hostname = mybox
generateHosts = true
generateResolvConf = true
[wsl2]
networkingMode = mirrored
firewall = true
dnsTunneling = true
autoProxy = true
options = metadata is the key flag for /mnt/c — without it, chmod and chown on a Windows drive are silently ignored. With metadata, WSL stores Unix permissions in NTFS alternate data streams.
Reload the file by terminating the distro and restarting it:
wsl --terminate Ubuntu-22.04
wsl -d Ubuntu-22.04
.wslconfig — global VM settings
.wslconfig is the Windows-side config file at %USERPROFILE%\.wslconfig controlling the whole WSL 2 VM (memory, CPU, swap, kernel, networking mode). Settings apply to every distro, take effect after wsl --shutdown, and have nothing to do with /etc/wsl.conf (which is per-distro). The interop-relevant settings are networkingMode, dnsTunneling, firewall, and autoProxy.
# %USERPROFILE%\.wslconfig
[wsl2]
memory = 8GB
processors = 4
swap = 2GB
localhostForwarding = true
networkingMode = mirrored
firewall = true
dnsTunneling = true
autoProxy = true
guiApplications = true
nestedVirtualization = true
[experimental]
sparseVhd = true
autoMemoryReclaim = gradual
networkingMode = mirrored (Windows 11 22H2+) is the big one — it eliminates the dual-IP problem by making the Linux VM share Windows' network namespace, so localhost is shared in both directions and ifconfig inside WSL shows the same IPs as ipconfig does outside it. The legacy default is nat, which uses the bridged adapter and the localhost forwarding shim.
# Apply changes
wsl --shutdown
wsl
WSLENV — passing environment variables across the bridge
WSLENV is the environment variable that controls which env vars cross from Windows to Linux (and vice versa) when one side launches the other. It's a colon-separated list of NAME[/flags] entries. Without WSLENV, only a tiny default set crosses — anything else stays in its native shell.
The flag suffixes:
| Flag | Direction | Meaning |
|---|---|---|
/p | both | Treat as a path; translate C:\foo ↔ /mnt/c/foo |
/l | Windows → Linux | Treat as a path list (PATH-style); split on ; and translate each |
/u | Windows → Linux only | Pass only when Windows launches Linux |
/w | Linux → Windows only | Pass only when Linux launches Windows |
Set it on the Windows side:
# Persist for the user
setx WSLENV "USERPROFILE/up:GOPATH/l:HOME/wu"
# Or just for the session
$env:WSLENV = "USERPROFILE/up:GOPATH/l:HOME/wu"
Then in a freshly-launched WSL shell:
echo $USERPROFILE # → /mnt/c/Users/alicedev
echo $GOPATH # → /mnt/c/Users/alicedev/go:/mnt/d/go (translated)
Output:
/mnt/c/Users/alicedev
/mnt/c/Users/alicedev/go:/mnt/d/go
A typical recipe — pass an API token from Windows into Linux without leaking it onto the command line:
$env:GH_TOKEN = 'ghp_*****'
$env:WSLENV = 'GH_TOKEN/u'
wsl gh repo list
Output:
alicedev/myproject Public Personal cheat-sheet site
...
Path translation with wslpath
wslpath is the canonical tool for converting between Windows paths (C:\Users\alice), WSL absolute paths (/mnt/c/Users/alice), and UNC paths (\\wsl.localhost\Ubuntu\home\alice). Use it in scripts whenever one side hands a path to the other — hardcoding either style breaks the moment the script runs on a different distro mount.
| Flag | Direction | Output |
|---|---|---|
| (no flag) | Win → POSIX | C:\Users\alice → /mnt/c/Users/alice |
-u | Win → POSIX | Same as default; explicit |
-w | POSIX → Win | /home/alice → \\wsl.localhost\Ubuntu\home\alice |
-m | POSIX → Win (mixed, forward slashes) | /mnt/c/Users → C:/Users — friendly to Cygwin-style tools |
-a | force absolute | resolves ./relative before converting |
wslpath 'C:\Users\Alice\Documents' # → /mnt/c/Users/Alice/Documents
wslpath -w /home/alicedev/project # → \\wsl.localhost\Ubuntu\home\alicedev\project
wslpath -m /mnt/c/Windows/System32 # → C:/Windows/System32
wslpath -a ../relative # → /home/alicedev/relative
# Wrap a Windows variable
echo "$(wslpath "$USERPROFILE")" # → /mnt/c/Users/alicedev
Output:
/mnt/c/Users/Alice/Documents
\\wsl.localhost\Ubuntu\home\alicedev\project
C:/Windows/System32
/home/alicedev/relative
/mnt/c/Users/alicedev
PowerShell does not have a built-in inverse, but you can call wslpath from the Windows side too:
wsl wslpath -w "/home/alicedev/project"
Output:
\\wsl.localhost\Ubuntu\home\alicedev\project
/mnt/c — DrvFs and the metadata flag
/mnt/<letter> is a DrvFs mount — a 9P-over-Hyper-V-Sockets filesystem driver that proxies all I/O back to NTFS on the Windows host. By default, DrvFs ignores Unix permissions (every file looks like 0777 owned by the current user) because NTFS has no native concept of Unix mode bits. The metadata mount option stores those bits in NTFS alternate data streams.
Mount with metadata so Unix permissions survive across reboots:
# /etc/wsl.conf
[automount]
options = "metadata,umask=22,fmask=11,case=off,uid=1000,gid=1000"
| Option | Meaning |
|---|---|
metadata | Persist Unix mode/uid/gid via NTFS streams |
umask=22 | Default mask for new directories (0755) |
fmask=11 | File mask offset (combined with umask gives 0644) |
uid=1000,gid=1000 | Owner of files without metadata |
| `case=off | dir |
After changing [automount], wsl --shutdown and restart. Confirm metadata is active:
mount | grep '^C:'
Output:
C: on /mnt/c type drvfs (rw,noatime,uid=1000,gid=1000,metadata,case=off,umask=22,fmask=11)
Performance note: DrvFs (/mnt/c) is much slower than the native ext4 filesystem inside the WSL VM. Keep source trees under /home/<user>/, not /mnt/c/Users/<user>/. The 9P trip for every stat-heavy operation (think npm install, git status on a 10000-file repo) typically runs 5–20× slower on /mnt/c.
\wsl.localhost\ — accessing Linux files from Windows
\\wsl.localhost\<Distro>\<path> is the reverse direction — Windows reads ext4 files inside a running WSL distro through a Plan 9 filesystem server hosted by init. Use it from Explorer, from any Win32 app that accepts UNC paths, or in PowerShell scripts.
# Open the WSL home in Explorer
explorer.exe \\wsl.localhost\Ubuntu\home\alicedev
# Read a file from PowerShell
Get-Content \\wsl.localhost\Ubuntu\home\alicedev\.bashrc
# Use a UNC path with native Windows tools
robocopy \\wsl.localhost\Ubuntu\home\alicedev\project C:\Backup\project /MIR
The legacy form \\wsl$\Ubuntu\... still works but is deprecated; new docs and IDEs prefer \\wsl.localhost\. The mapping is identical.
A common gotcha: Windows file watchers (FileSystemWatcher, used by every JS bundler in watch mode) do not receive change events on \\wsl.localhost\ paths. If hot-reload doesn't fire, the project lives on the wrong side of the bridge — move it under /home/ and edit it from VS Code's WSL remote.
Calling Windows from WSL — cmd.exe, PowerShell, browsers
Any Windows binary on %PATH% is invokable from WSL by its .exe name. The interop layer translates argv, stdio, and exit codes; WSLENV-tagged environment variables also cross. Three callable styles cover virtually every use case.
# Direct exe call — arguments pass through verbatim
notepad.exe README.md
code.exe .
explorer.exe . # open current dir in Explorer
# Open a URL in the Windows default browser
cmd.exe /c start https://example.com
# Pipe to the Windows clipboard
echo "hello" | clip.exe
cat ~/.ssh/id_ed25519.pub | clip.exe
# Run a PowerShell command and capture its output
TIMEZONE=$(powershell.exe -NoProfile -Command '[System.TimeZoneInfo]::Local.Id' | tr -d '\r')
echo "Windows timezone: $TIMEZONE"
# Trigger a Windows toast
powershell.exe -Command 'New-BurntToastNotification -Text "Build complete"'
Output:
Windows timezone: Pacific Standard Time
cmd.exe /c start is the universal "open this with Windows' default handler" pattern — it works for URLs, files, anchor links, anything Explorer's Open With recognises.
Strip CR characters when piping Windows output into Linux tools — PowerShell terminates lines with \r\n, which trips up grep, awk, and cut:
powershell.exe -Command 'Get-ChildItem C:\Windows -Filter *.dll | Select-Object -ExpandProperty Name' \
| tr -d '\r' \
| head -5
Output:
aadauthhelper.dll
aadcloudap.dll
aadjcsp.dll
AadWamExtension.dll
abovelockapp.dll
Calling WSL from Windows — wsl.exe -e / --exec
From PowerShell or CMD, wsl <command> runs a Linux command in the default distro; wsl -e (or --exec) skips the login shell and /etc/profile initialisation for a faster, more predictable invocation.
# Default invocation — uses the default distro and a login shell
wsl ls -la /home/alicedev
# Specific distro
wsl -d Ubuntu-22.04 -- cat /etc/os-release
# Specific user (sudo-free way to do work as root)
wsl -u root apt update
# Set working directory before running
wsl --cd /home/alicedev/project -- git status
# Skip the login shell — much faster for one-shots
wsl --exec /usr/bin/python3 -V
wsl -e /bin/bash -c "rg --type py 'def main' /home/alicedev/project"
# Pipe Windows command into Linux
Get-Content .\data.csv | wsl awk -F, '{ print $2 }'
# Pipe Linux back into PowerShell
$lines = wsl grep -c '^Error' /var/log/syslog
Write-Host "Error count: $lines"
The -- separator is important when the Linux command has flags that look like wsl.exe flags — without it, wsl ls -la / works, but wsl ls --color fails because --color is parsed by wsl.exe. Always wsl -- ls --color for safety.
A common pipeline pattern: use Linux tooling to summarise a Windows log:
Get-Content C:\Logs\app.log | wsl -e awk '/ERROR/ { count++ } END { print count }'
Output:
148
Named pipes and Unix sockets
WSL 2 supports Unix domain sockets (AF_UNIX) inside the VM, but Windows AF_UNIX sockets cannot be reached directly from Linux — and vice versa. The two interop paths for socket-style IPC are Hyper-V Sockets (AF_VSOCK on Linux, AF_HYPERV on Windows) and named pipes via \\.\pipe\<name> on Windows.
The most common practical case is the Docker Desktop socket: Docker Desktop exposes the Engine on a Windows named pipe at \\.\pipe\docker_engine and proxies it into WSL at /var/run/docker.sock. From Linux:
docker ps # uses the proxied socket
file /var/run/docker.sock # → socket
Output:
CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
8f3c9a1b4d7e nginx:alpine "/docker-entrypoint.…" Up 2 hours 0.0.0.0:8080->80/tcp web
/var/run/docker.sock: socket
Without Docker Desktop, you can roll your own bridge using socat and the npiperelay Windows helper from jstarks/npiperelay:
# Install socat in the distro
sudo apt install socat
# Place npiperelay.exe somewhere on $PATH (e.g. /usr/local/bin/)
# Then forward a Windows named pipe to a Unix socket
socat UNIX-LISTEN:/tmp/docker.sock,fork,group=docker,umask=007 \
EXEC:"npiperelay.exe -ep -s //./pipe/docker_engine",nofork
Output: (none — socat blocks while forwarding; logs go to stderr)
For the reverse direction (Linux socket → Windows app), socat can listen on a TCP port and Windows connects to localhost:<port> (works trivially with networkingMode = mirrored).
SSH agent forwarding into WSL
A frequent ask: use your Windows OpenSSH agent (and Yubikey-backed keys) from inside WSL without re-importing the keys. npiperelay again — pair it with socat and a Unix socket override.
# In ~/.bashrc
export SSH_AUTH_SOCK="$HOME/.ssh/agent.sock"
ss -a | grep -q "$SSH_AUTH_SOCK" || (
rm -f "$SSH_AUTH_SOCK"
setsid nohup socat UNIX-LISTEN:"$SSH_AUTH_SOCK",fork \
EXEC:"npiperelay.exe -ei -s //./pipe/openssh-ssh-agent",nofork \
>/dev/null 2>&1 &
)
Output: (none — exits 0; socat runs detached as a background process)
Now ssh-add -l inside WSL lists the keys loaded into the Windows ssh-agent service — see the windows ssh cheat sheet for managing that agent.
WSLg — running Linux GUIs on Windows
WSLg is Microsoft's compositor that lets a Linux X11 or Wayland client display windows directly on the Windows desktop. It ships as part of WSL on Windows 11 (and Windows 10 with the Microsoft.WSL MSIX package). No extra X server is needed — the user-mode RDP client mstsc.exe quietly handles the framebuffer, and wslg runs a Weston compositor inside a sidecar VM.
# Confirm WSLg is wired up
echo $DISPLAY $WAYLAND_DISPLAY $XDG_RUNTIME_DIR
# Expected: :0 wayland-0 /run/user/1000
# Sockets WSLg mounts in
ls /mnt/wslg
# Tmp/ doc/ runtime-dir/ versions.txt weston.log wsl.log
# Launch a GUI app
sudo apt install -y x11-apps
xeyes &
xclock &
Output:
:0 wayland-0 /run/user/1000
Tmp doc runtime-dir versions.txt weston.log wsl.log
A common dev-workflow use is opening Linux GUI tools from inside WSL alongside Windows IDE windows:
gnome-disks &
gvim README.md &
firefox https://localhost:5173 &
Output: (none — apps launch as detached background jobs; window IDs printed to stderr)
Disable WSLg for headless servers via .wslconfig:
[wsl2]
guiApplications = false
Or per-distro via /etc/wsl.conf:
[boot]
guiApplications = false
Networking between Windows and WSL
In WSL 2 with the legacy nat networking mode, the VM has its own IP and Windows runs a small port forwarder for localhost. With networkingMode = mirrored (Windows 11 22H2+), the VM shares Windows' network namespace and the two IPs collapse into one.
# Mirrored mode — same IP as Windows
hostname -I
ip addr show eth0
# Should match `ipconfig` output on Windows
# NAT mode — separate VM-side IP
# Discover the Windows host IP from Linux
ip route show default | awk '{print $3}'
cat /etc/resolv.conf | grep nameserver | awk '{print $2}'
Output (mirrored mode):
192.168.1.42
Output (NAT mode):
172.29.192.1
When you need a WSL-hosted service reachable from other machines on the LAN (NAT mode only — mirrored handles it automatically), use netsh portproxy:
# As Administrator
netsh interface portproxy add v4tov4 `
listenport=3000 listenaddress=0.0.0.0 `
connectport=3000 connectaddress=127.0.0.1
# Open the Windows Firewall for the port
New-NetFirewallRule -DisplayName "WSL Dev 3000" -Direction Inbound `
-LocalPort 3000 -Protocol TCP -Action Allow
# List active portproxy rules
netsh interface portproxy show all
# Remove the rule when done
netsh interface portproxy delete v4tov4 listenport=3000 listenaddress=0.0.0.0
DNS resolution is handled by dnsTunneling (in .wslconfig) — when enabled, WSL hands DNS queries to Windows via Hyper-V Sockets rather than over UDP, which fixes split-DNS and VPN scenarios that used to require manual resolv.conf editing.
Clipboard and toast notifications
Two small bridges that punch above their weight in daily workflows: the Windows clipboard via clip.exe/Get-Clipboard, and Windows toast notifications via BurntToast or PowerShell directly.
# Copy Linux output to the Windows clipboard
ls -la | clip.exe
cat ~/.ssh/id_ed25519.pub | clip.exe
date '+%Y-%m-%d %H:%M' | clip.exe
# Pull from the Windows clipboard into Linux
content=$(powershell.exe -Command 'Get-Clipboard' | tr -d '\r')
echo "Clipboard: $content"
# Toast notification on long-running task completion
make build && powershell.exe -Command \
"[reflection.assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null;
[System.Windows.Forms.MessageBox]::Show('Build complete')"
Output:
Clipboard: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... alice@example.com
For richer notifications (icon, actions, dismissable), pair with the BurntToast PowerShell module:
powershell.exe -Command "Import-Module BurntToast; New-BurntToastNotification -Text 'Build complete', 'All tests passed' -Sound 'Default'"
Output: (none — toast appears in the Windows Action Center)
Interop performance tips
Interop calls have non-trivial overhead — each .exe invocation crosses the Hyper-V VM boundary. A few rules keep WSL dev workflows snappy:
- Keep source on ext4.
/home/<user>/projectis 5–20× faster than/mnt/c/Users/<user>/projectfor stat-heavy ops. - Use
--execfor one-shot calls.wsl --execskips the login shell, halving startup latency. - Batch over the bridge. A single
wsl ... ; ... ; ...is faster than three separatewslcalls. - Prefer
localhostto specific IPs. Mirrored mode resolves it locally; NAT mode falls back to the forwarder. - Pin the kernel. WSL kernel updates can change
9pandHyper-V Socketbehaviour; lock to a tested version via[wsl2] kernel = C:\path\to\kernel. - Avoid Windows file watchers on WSL paths. Bundlers that watch
\\wsl.localhost\...will silently miss events; open the project from inside WSL via VS Code's Remote-WSL extension instead.
Common pitfalls
/mnt/cpermissions look like 0777 — addmetadatato[automount].optionsin/etc/wsl.confandwsl --shutdown.appendWindowsPath = trueclutters$PATH— turn it off in/etc/wsl.confif you don't wantcmd.exe,PowerShell.exe, and 30 other Windows tools inwhichresults.- CR/LF endings break shell scripts on
/mnt/c— files edited in Notepad get\r\n;bashreads the\ras part of the command. Usedos2unixor save via VS Code with LF endings. networkingMode = mirroredbreaks Docker Desktop — Docker uses bridged networking and needsnat. Use a separate distro for Docker workloads or keep mirrored off.wsl.exeis not on the WindowsPATHinside a Linux subshell — when called from another Linux program, the interop bridge needs an absolute path:/mnt/c/Windows/System32/wsl.exe.- Environment variables don't cross by default — only a tiny default set crosses; populate
WSLENVwith the names you need. - WSLg can't show windows for
root— WSLg uses the default user's socket;sudo xeyesfails unless you addxhost +SI:localuser:rootor run as the regular user.
Real-world recipes
One-line URL opener
Open the Windows browser to a URL from inside WSL — replaces xdg-open on a system without a GUI.
# Add to ~/.bashrc
open() { cmd.exe /c start "$1"; }
open https://example.com
open "$(wslpath -w ./report.html)"
Output: (none — Windows opens the URL/file in the default handler)
Edit a Windows config file with vim, save via metadata
sudo vim /mnt/c/Windows/System32/drivers/etc/hosts # needs elevated WSL or write-protected
Output: (none — opens an interactive vim session)
For files that require Administrator, open the file from PowerShell (elevated) instead.
Sync a Windows folder into the WSL home
Faster builds by relocating a project from /mnt/c/ into /home/:
rsync -a --delete \
/mnt/c/Users/alicedev/Code/myproject/ \
/home/alicedev/myproject/
Output: (none — exits 0 on success; stats only with -v)
Use Windows OpenSSH keys directly from WSL
# Symlink the Windows .ssh dir into WSL
ln -s /mnt/c/Users/alicedev/.ssh ~/.ssh-win
chmod 700 ~/.ssh-win
ssh -i ~/.ssh-win/id_ed25519 alicedev@myhost
Output:
Linux myhost 6.1.0-21-amd64 #1 SMP Debian 6.1.90-1 (2026-04-01) x86_64
Last login: Sat May 25 09:14:22 2026 from 10.0.0.5
alice@myhost:~$
Or use ssh-agent forwarding via npiperelay (see above).
Cross-shell scripted dev-loop
Run Vitest in Linux, surface failures as Windows toasts:
while true; do
if ! pnpm test; then
powershell.exe -Command "New-BurntToastNotification -Text 'Tests failed'"
fi
inotifywait -re modify ./src ./tests
done
Output: (loop runs indefinitely; each iteration prints pnpm test output)
Translate a path and open Explorer there
# In ~/.bashrc
explore() { explorer.exe "$(wslpath -w "$(realpath "${1:-.}")")"; }
explore .
explore ../sibling-folder
Output: (none — Explorer opens the translated path in a new window)
2026 update notes
WSL has shipped meaningfully since the days of the in-OS-bundled v1: it is now distributed as an MSIX (Microsoft.WSL) and gets release-train updates independent of Windows. A few changes worth knowing about in 2026:
- January 2026 servicing update fixed a
mirrorednetworking regression that producedNo route to hosterrors when accessing corporate resources over VPN — if you saw VPN-side traffic break in late 2025, the fix landed in the WSL 2.3.x release train. - dxgkrnl GPU patches (March 2026) added compute-only GPU support for running local LLMs, multiple virtual GPUs per VM, and
dma-fencebuffer sharing — relevant forwslgand CUDA workloads. - DrvFs metadata storage migration: recent WSL builds now persist Linux UID/GID/mode bits in NTFS Extended Attributes (EAs) rather than alternate data streams. Behaviour from the user's perspective is the same — the
metadatamount option is still the switch — but files created on newer WSL versions and copied to older ones lose their permissions. - Mirrored networking conflict with Docker Desktop remains current: Docker's
vpnkitand WSL mirrored mode both try to bind the same Windows host ports. KeepnetworkingMode = natif Docker Desktop is the primary container runtime, or run Docker inside a separate distro without mirrored.
Track changes at the microsoft/WSL GitHub releases page — the legacy learn.microsoft.com/.../wsl/release-notes page has not been updated past 2021.
Related cheat sheets
- wsl —
wsl.exedistro CLI (install, list, export, version) - windows-terminal — recommended terminal for mixed Windows + WSL panes
- powershell-basics — host shell on the Windows side of the bridge
- installation-wsl-ubuntu — Python on WSL workflow
- linux/ssh-tunnels — SSH over WSL → upstream Linux hosts
- ssh — Windows OpenSSH client; pairs with WSL via agent forwarding
Sources
- Accessing network applications with WSL — Microsoft Learn — official guide for NAT vs mirrored networking and
localhostforwarding. - Advanced settings configuration in WSL — Microsoft Learn — current
.wslconfigand/etc/wsl.confreference. - microsoft/WSL releases — GitHub — authoritative source for current WSL versions, the legacy learn.microsoft.com release notes page has not been updated past 2021.
- Microsoft Announces WSL Upgrades for Windows 11 — Winbuzzer (Mar 2026) — coverage of the dxgkrnl GPU and networking improvements that landed in 2026.
- Understanding the WSL2 Mirrored Networking Conflict — Codonomics (Apr 2026) — Docker Desktop / vpnkit port-bind conflict and workarounds.
- Drvfs & Plan9 — wsl.dev — current DrvFs internals; metadata now persists in NTFS Extended Attributes.
- WSL Networking in Windows 11: Mirrored Mode and DNS Tunneling Guide — Windows Forum — practical mirrored-mode configuration reference.