📱 A Windows command-line utility for copying files from phones and other media devices connected as MTP devices.
Media Device Copier is a Windows command-line utility for copying files to and from phones and other devices connected via MTP (Media Transfer Protocol).
Use it to:
list-devices, alias l).list-files, alias lf), optionally filtered by regex and/or printed as full device paths.upload-files, alias u).download-files, alias d).--move to delete the source after a successful transfer.--copy-recursive).net10.0-windows).MediaDeviceCopier.exe -h
MediaDeviceCopier.exe list-devices
# alias
MediaDeviceCopier.exe l
MediaDeviceCopier.exe list-files -n "Apple iPhone" -s "Internal Storage\DCIM\100APPLE"
# alias + filter to only jpeg files + print full device paths
MediaDeviceCopier.exe lf -n "Apple iPhone" -s "Internal Storage\DCIM\100APPLE" -f "\.(jpg|jpeg)$" --full-path
MediaDeviceCopier.exe download-files -n "Apple iPhone" -s "Internal Storage\DCIM\100APPLE" -t "D:\Photos" -r
MediaDeviceCopier.exe upload-files -n "Android Device" -s "C:\Documents" -t "Internal Storage\Documents" -r -f "\.pdf$"
Lists all available MTP devices.
list-deviceslExample:
MediaDeviceCopier.exe list-devices
Lists files in a device folder.
list-fileslfOptions:
-n, --device-name (required): the MTP device name.-s, --source-folder (required): the device folder to list.-f, --filter-files (optional): .NET regex to filter by file name.--full-path (optional): print full device paths instead of just file names.Usage:
MediaDeviceCopier.exe list-files -n "<Device>" -s "<DeviceFolder>" [-f "<regex>"] [--full-path]
Examples:
# List all files in a device folder
MediaDeviceCopier.exe list-files -n "Apple iPhone" -s "Internal Storage\DCIM\100APPLE"
# List only JPEG files and print full device paths
MediaDeviceCopier.exe lf -n "Apple iPhone" -s "Internal Storage\DCIM\100APPLE" -f "\.(jpg|jpeg)$" --full-path
Downloads files from an MTP device folder to a Windows folder.
download-filesdRequired options:
-n, --device-name: the MTP device name.-s, --source-folder: the device folder to copy from.-t, --target-folder: the Windows folder to copy into.Common optional options:
-se, --skip-existing [true|false]: skip files already at the destination (default: true). Pass false to overwrite existing files.-r, --copy-recursive [true|false]: recurse into subfolders (default: false). The flag alone (-r) implies true.-mv, --move: delete source after successful transfer.-sf, --filter-subfolders: .NET regex filter for subfolder names (used during recursion).-f, --filter-files: .NET regex filter for file names.Examples:
# Copy pictures recursively and skip already-copied images
MediaDeviceCopier.exe download-files -n "Apple iPhone" -s "Internal Storage" -t "D:\MyPictureFolder" -r
# Move (download then delete from device) all videos after archiving
MediaDeviceCopier.exe download-files -n "Apple iPhone" -s "Internal Storage\DCIM\100APPLE" -t "D:\Archive" -r --move
# Copy only MP4 files from a flat folder (non-recursive)
MediaDeviceCopier.exe download-files -n "Apple iPhone" -s "Internal Storage\DCIM\100APPLE" -t "D:\Videos" -f "\.mp4$"
# Recursive copy: only subfolders starting with 2025, only JPG/PNG files
MediaDeviceCopier.exe download-files -n "Apple iPhone" -s "Internal Storage" -t "D:\MyPictureFolder" -r -sf "^2025.*" -f "\.(jpg|png)$"
# Force overwrite existing files (disable skip-existing)
MediaDeviceCopier.exe download-files -n "Apple iPhone" -s "Internal Storage\DCIM" -t "D:\Photos" --skip-existing false
Uploads files from a Windows folder to an MTP device folder.
upload-filesuRequired options:
-n, --device-name: the MTP device name.-s, --source-folder: the Windows folder to copy from.-t, --target-folder: the device folder to copy into.Common optional options:
-se, --skip-existing [true|false]: skip files already at the destination (default: true). Pass false to overwrite existing files.-r, --copy-recursive [true|false]: recurse into subfolders (default: false). The flag alone (-r) implies true.-mv, --move: delete source after successful transfer.-sf, --filter-subfolders: .NET regex filter for subfolder names (used during recursion).-f, --filter-files: .NET regex filter for file names.Examples:
# Upload only PDFs
MediaDeviceCopier.exe upload-files -n "Android Device" -s "C:\Documents" -t "Internal Storage\Documents" -r -f "\.pdf$"
The filtering system uses two independent regex patterns:
-sf, --filter-subfolders) are applied during folder recursion before descending into subfolders.-f, --filter-files) are applied to the file list within each processed folder.Notes:
list-files filters by file name, not the full path.list-devices first to get the exact name to pass to -n.--skip-existing behaves as true when omitted.--copy-recursive behaves as false when omitted.--move behaves as false when omitted.--skip-existing, --copy-recursive): can be passed as a bare flag (-r = true) or with an explicit value (--skip-existing false).--copy-recursive is enabled, the folder is created.--copy-recursive is enabled, the folder is created.MediaDeviceCopier.exe --version
If you see “Device not found”, run:
MediaDeviceCopier.exe list-devices
Then copy/paste the device name exactly into -n.
--copy-recursive enabled, download may create missing target foldersIf a filter regex is invalid, the CLI will reject it. Start simple and escape backslashes correctly in your shell.
Some device files may not be transferable via MTP; those are reported as skipped.
If a file cannot be downloaded (for example, the device reports an object with no name, or every download strategy fails), it is reported as FAILED (<reason>), the run continues with the remaining files, and a summary of failed files and folders is printed at the end. The exit code is 0 when everything succeeded and 1 if any file or folder failed, so scripts can detect an incomplete transfer.
0x8007000D (“The data is invalid”). These are retried a few times with a short delay. If a folder still cannot be read during a recursive copy, it is reported, the copy continues with the next folder, and the run exits with code 1.MTP transfers can fail for certain files due to device firmware quirks, timing issues, or protocol limitations. MediaDeviceCopier uses a multi-strategy download pipeline and detailed diagnostics to make downloads more resilient.
Implementation details are documented in ARCHITECTURE_MTP_STRATEGIES.md.
Requires the .NET 10 SDK on Windows (the app targets net10.0-windows).
# Build
dotnet build -c Release
# Run the mocked test suite
dotnet test MediaDeviceCopier.Tests.Mocked/MediaDeviceCopier.Tests.Mocked.csproj
# Produce the single-file executable (framework-dependent, win-x64)
dotnet publish MediaDeviceCopier/MediaDeviceCopier.csproj -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true -o publish
# -> publish/MediaDeviceCopier.exe
Note:
MediaDeviceCopier.Tests.RealDeviceis a separate suite that requires a physical MTP device connected to the machine, so it is not run bydotnet testabove or in CI. Run it manually when validating against real hardware.
The application version comes from a single source: the <Version> element in
MediaDeviceCopier/MediaDeviceCopier.csproj.
AssemblyVersion, FileVersion, and the version shown by --version / --help are all
derived from it, so a release only requires changing that one value.
Two GitHub Actions workflows run automatically on every pull request and on pushes to main:
windows-latest.git add --renormalize . and commit.Dependabot opens weekly PRs for NuGet and GitHub Actions updates.
Releases are produced by the Release workflow
(.github/workflows/release.yml), which triggers on any pushed
tag matching v*. To cut a release:
<Version> in
MediaDeviceCopier/MediaDeviceCopier.csproj
(this is the only place to change it), commit, and merge to main.Tag and push. The tag version must match <Version> exactly (with a v prefix) —
the workflow verifies this and fails the build on a mismatch:
git checkout main
git pull
git tag v0.8.0
git push origin v0.8.0
MediaDeviceCopier.exe, generates release notes
(categorized via .github/release.yml), and creates a draft
GitHub Release with the exe attached.Tip: When a PR closes multiple issues, give each one its own closing keyword —
Closes #20, closes #21, closes #22. WritingCloses #20, #21, #22only closes the first.
MIT License. See LICENSE for details.
Contributions, issues, and feature requests are welcome!