Created
February 11, 2026 21:44
-
-
Save vmfunc/e5bcb30361f79218e94f88f67c099528 to your computer and use it in GitHub Desktop.
virtiofs reparse point workaround using bindflt CreateBindLink
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * cowork-drives - bind windows drive roots into a folder via bindflt | |
| * | |
| * virtiofs cant follow NTFS reparse points (junctions/symlinks), | |
| * so we use CreateBindLink to create kernel-level bind mounts | |
| * that show up as real directories. must run as SYSTEM. | |
| * | |
| * usage: cowork-drives.exe <folder> <letter> [letter ...] | |
| * example: cowork-drives.exe C:\Users\me\AllDrives C D F | |
| */ | |
| #include <windows.h> | |
| #include <stdio.h> | |
| typedef HRESULT (WINAPI *CreateBindLinkFn)( | |
| LPCWSTR virtualPath, | |
| LPCWSTR backingPath, | |
| ULONG flags, | |
| ULONG extraInfoCount, | |
| void *extraInfo | |
| ); | |
| typedef HRESULT (WINAPI *RemoveBindLinkFn)(LPCWSTR virtualPath); | |
| int wmain(int argc, wchar_t *argv[]) { | |
| if (argc < 3) { | |
| wprintf(L"usage: %s <folder> <drive> [drive ...]\n", argv[0]); | |
| wprintf(L"example: %s C:\\Users\\me\\AllDrives C D F\n", argv[0]); | |
| return 1; | |
| } | |
| HMODULE lib = LoadLibraryW(L"bindfltapi.dll"); | |
| if (!lib) { | |
| wprintf(L"err: cant load bindfltapi.dll (%lu)\n", GetLastError()); | |
| return 1; | |
| } | |
| CreateBindLinkFn bind = | |
| (CreateBindLinkFn)GetProcAddress(lib, "CreateBindLink"); | |
| RemoveBindLinkFn unbind = | |
| (RemoveBindLinkFn)GetProcAddress(lib, "RemoveBindLink"); | |
| if (!bind) { | |
| wprintf(L"err: CreateBindLink not in bindfltapi.dll\n"); | |
| FreeLibrary(lib); | |
| return 1; | |
| } | |
| LPCWSTR root = argv[1]; | |
| CreateDirectoryW(root, NULL); | |
| int ok = 0, fail = 0, skip = 0; | |
| for (int i = 2; i < argc; i++) { | |
| wchar_t letter = argv[i][0]; | |
| if (letter >= L'a' && letter <= L'z') | |
| letter -= 32; | |
| /* build paths */ | |
| wchar_t virt[MAX_PATH], target[8]; | |
| swprintf(virt, MAX_PATH, L"%s\\%c", root, letter); | |
| swprintf(target, 8, L"%c:\\", letter); | |
| /* check drive exists */ | |
| if (GetDriveTypeW(target) == DRIVE_NO_ROOT_DIR) { | |
| wprintf(L" skip %c (drive not found)\n", letter); | |
| skip++; | |
| continue; | |
| } | |
| /* remove old junction/symlink if present */ | |
| DWORD attr = GetFileAttributesW(virt); | |
| if (attr != INVALID_FILE_ATTRIBUTES && | |
| (attr & FILE_ATTRIBUTE_REPARSE_POINT)) { | |
| RemoveDirectoryW(virt); | |
| } | |
| CreateDirectoryW(virt, NULL); | |
| if (unbind) unbind(virt); | |
| HRESULT hr = bind(virt, target, 0, 0, NULL); | |
| if (SUCCEEDED(hr)) { | |
| wprintf(L" ok %c %s -> %s\n", letter, virt, target); | |
| ok++; | |
| } else { | |
| wprintf(L" FAIL %c 0x%08lX\n", letter, hr); | |
| fail++; | |
| } | |
| } | |
| wprintf(L"\n%d ok, %d failed, %d skipped\n", ok, fail, skip); | |
| if (ok > 0) | |
| wprintf(L"bind links active until reboot\n"); | |
| FreeLibrary(lib); | |
| return fail > 0 ? 1 : 0; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #requires -RunAsAdministrator | |
| <# | |
| .SYNOPSIS | |
| mounts windows drives into a folder so cowork's virtiofs can see them. | |
| .DESCRIPTION | |
| virtiofs cant follow NTFS reparse points (junctions/symlinks), so this | |
| script uses CreateBindLink (via bindflt minifilter) to create kernel-level | |
| bind mounts that show up as real directories. | |
| CreateBindLink requires SYSTEM privileges, so the script creates a | |
| one-shot scheduled task to run the bind tool as SYSTEM. | |
| bind links persist until reboot. run this after each boot before | |
| opening cowork, or set it up as a startup task. | |
| .PARAMETER Folder | |
| target folder. defaults to $env:USERPROFILE\AllDrives | |
| .PARAMETER Drives | |
| drive letters to bind. defaults to all fixed drives on the system. | |
| #> | |
| param( | |
| [string]$Folder = "$env:USERPROFILE\AllDrives", | |
| [string[]]$Drives | |
| ) | |
| # self-elevate if needed | |
| if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { | |
| $args_str = "-Folder `"$Folder`"" | |
| if ($Drives) { $args_str += " -Drives $($Drives -join ',')" } | |
| Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" $args_str" -Verb RunAs | |
| exit | |
| } | |
| $scriptDir = Split-Path -Parent $PSCommandPath | |
| $exe = Join-Path $scriptDir "cowork-drives.exe" | |
| if (-not (Test-Path $exe)) { | |
| Write-Host "error: cowork-drives.exe not found at $exe" -ForegroundColor Red | |
| Write-Host "put it next to this script" | |
| exit 1 | |
| } | |
| # auto-detect fixed drives if none specified | |
| if (-not $Drives) { | |
| $Drives = (Get-WmiObject Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 }).DeviceID -replace ':', '' | |
| Write-Host "detected drives: $($Drives -join ', ')" | |
| } | |
| # clean up old junctions in the folder | |
| if (Test-Path $Folder) { | |
| Get-ChildItem $Folder -Directory | ForEach-Object { | |
| if ($_.Attributes -band [IO.FileAttributes]::ReparsePoint) { | |
| Write-Host "removing old junction: $($_.Name)" | |
| [IO.Directory]::Delete($_.FullName) | |
| } | |
| } | |
| } | |
| # build args | |
| $driveArgs = ($Drives | ForEach-Object { $_.Substring(0,1).ToUpper() }) -join ' ' | |
| $argString = "`"$Folder`" $driveArgs" | |
| # write a bat to capture output | |
| $logFile = Join-Path $scriptDir "cowork-drives.log" | |
| $batFile = Join-Path $env:TEMP "cowork-drives-run.bat" | |
| "@echo off`r`n`"$exe`" $argString > `"$logFile`" 2>&1" | Out-File $batFile -Encoding ascii | |
| # run as SYSTEM via scheduled task | |
| $taskName = "CoworkDrivesBind" | |
| try { | |
| Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue | |
| } catch {} | |
| $action = New-ScheduledTaskAction -Execute $batFile | |
| $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest | |
| $task = New-ScheduledTask -Action $action -Principal $principal | |
| Register-ScheduledTask -TaskName $taskName -InputObject $task -Force | Out-Null | |
| Start-ScheduledTask -TaskName $taskName | |
| # wait for it | |
| $timeout = 10 | |
| for ($i = 0; $i -lt $timeout; $i++) { | |
| Start-Sleep 1 | |
| $info = Get-ScheduledTaskInfo -TaskName $taskName -ErrorAction SilentlyContinue | |
| if ($info -and $info.LastTaskResult -ne 267009) { break } # 267009 = still running | |
| } | |
| Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue | |
| # show results | |
| if (Test-Path $logFile) { | |
| Write-Host "" | |
| Get-Content $logFile | Write-Host | |
| } | |
| Write-Host "" | |
| Write-Host "drives mounted at: $Folder" -ForegroundColor Green | |
| Write-Host "these persist until reboot. run this script again after rebooting." |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @echo off | |
| powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0cowork-mount-drives.ps1" | |
| pause |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment