Skip to main content

Windows symbolic links

This page explains why, on Windows, in the xpack/.bin folder contains .cmd and .ps1 scripts.

Brief

The short version is because Windows does not support symbolic links to files.

Problem

By design, npm required a method to differentiate between multiple applications installed in distinct folders. The traditional method is to add multiple paths to PATH. However, npm preferred a different method: creating symbolic links to individual applications in a .bin folder and adding this folder to the top of the PATH.

This works very well on Unix systems, but is not applicable on Windows, as there are no symbolic links to files.

NTFS supports only junctions for directories (whatever this can be).

According to tests on a Windows system (2004, build 19041):

  • mklink /J new old - creates a directory junction, no admin rights; works on different partitions or volumes, but only locally on the same computer
  • mklink /D new old - creates a symbolic link, but requires admin rights or developer mode; can point to any file or folder either on the local computer or using a SMB path to targets over a network

Reference:

Rumours say that since Windows 10 Insiders build 14972, symlinks can be created without admin rights (https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10/), but tests on build 19041 did not confirm this; therefore this method was considered unreliable.

Solution

The solution is to use wrappers/shims, to call executables from a different folder.

  • shell wrappers for mingw cases
  • .cmd wrappers for old DOS
  • .ps1 wrappers for new Power Shell

For now the binaries are invoked directly from the actual install location, not from the junction created in the local project.

Examples

clang.cmd
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
"C:\Users\ilg\AppData\Roaming\xPacks\@xpack-dev-tools\clang\17.0.6-2.1\.content\bin\clang.exe" %*
clang.ps1
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent

$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "C:/Users/ilg/AppData/Roaming/xPacks/@xpack-dev-tools/clang/17.0.6-2.1/.content/bin/clang.exe" $args
} else {
& "C:/Users/ilg/AppData/Roaming/xPacks/@xpack-dev-tools/clang/17.0.6-2.1/.content/bin/clang.exe" $args
}
exit $LASTEXITCODE