WSL2 runs Linux inside a lightweight virtual machine, and unlike WSL1, it does not share the Windows USB stack directly. Installing adb inside a WSL2 Ubuntu shell and running adb devices against a phone plugged into the PC returns an empty list every time, not because of a driver problem, but because the VM simply has no USB devices attached to it at all until one is explicitly forwarded.
WSL2's virtual machine is isolated from the host's physical hardware for the same reason any VM is: it only sees devices Windows has been told to hand over. Microsoft does not build native USB passthrough into WSL2 itself; the supported path is a separate open-source tool, usbipd-win, which uses the USB/IP protocol to share a specific device from Windows into the Linux VM on demand.
winget install --interactive --exact dorssel.usbipd-win
This installs the Windows-side service. Inside the WSL2 distro, the matching client tools are needed as well:
sudo apt install linux-tools-generic hwdata
sudo update-alternatives --install /usr/local/bin/usbip usbip /usr/lib/linux-tools/*-generic/usbip 20
From an elevated Windows PowerShell prompt (not inside WSL), list connected USB devices:
usbipd list
Find the phone in the list — it typically shows up with a vendor string matching the manufacturer once USB debugging is enabled and the device is in File Transfer mode, not Charging only. Note its BUSID, then bind and attach it:
usbipd bind --busid 2-4
usbipd attach --wsl --busid 2-4
bind only needs to run once per device per Windows boot; attach needs to run again each time the device is reconnected or WSL is restarted.
lsusb
The phone should now appear in the WSL2 Linux distro's own USB device list. Start the ADB server inside WSL and check:
adb kill-server
adb devices
If it still shows unauthorized, the phone's authorization dialog needs to be accepted just as it would for a native Linux or Windows connection — passthrough only solves the visibility problem, not the standard ADB RSA key handshake.
Because the device is now attached to a real Linux kernel (the WSL2 VM's), the same udev permission issues that affect a native Linux install can show up here too. If adb devices reports "no permissions," the fix is identical to a bare-metal Linux machine: add a udev rule for the vendor ID, or run adb as root temporarily to confirm that permissions are indeed the blocker before spending time on anything else.
Unplugging and replugging the phone, or the phone rebooting on its own (common after a fastboot flash), drops the USB/IP attachment and requires re-running usbipd attach from Windows. This is easy to forget mid-session and shows up as ADB suddenly losing the device for no apparent reason; before assuming a driver or cable problem, check whether the device simply needs to be reattached after a reboot.
Developers whose build tooling, shell scripts, or CI-mirroring local setup already lives in a Linux environment gain by keeping ADB calls inside that same environment rather than switching to a native Windows terminal just for device commands. It also gives Linux-only tooling — certain fastboot flashing scripts, or udev-rule-driven automation copied from a CI pipeline — a way to run unmodified on a Windows machine instead of needing a port to PowerShell.
usbipd detach --busid 2-4
Running this from the Windows side returns the phone to Windows's own USB stack, which is worth doing before using any Windows-native Android tool (a phone manufacturer's own desktop suite, for instance) in the same session, since a device attached to WSL2 is not simultaneously visible to Windows applications at the same time.
For a workflow that relies on this daily, a small PowerShell script run at login (or a scheduled task) that calls usbipd attach --wsl --busid <id> --auto-attach avoids needing to remember the manual command every morning. The --auto-attach flag keeps watching for the device and reattaches it automatically if it is unplugged and reconnected later in the same session, which is the closest this setup gets to the seamless behavior a native Linux install or macOS gets by default.