15

I understand that Windows later-than-or-equal-to Vista provides the mklink shell command. I'd like to make use of this from the Msys terminal. Any idea how?

When I enter mklink on the msys terminal, it outputs sh: mklink: command not found. Msys only provides a fake ln utility which appears to be effectively the same as cp.

I tried writing a shell script to open a Windows shell and run mklink within it, but when my shell script tries to execute cmd /C <instructions>, msys brings the Windows shell to the foreground of the current terminal and leaves it there, without running the instructions.

15

Using cmd //c mklink directly on the MSYS bash command line should work.

$ cmd //c mklink
Creates a symbolic link.

MKLINK [[/D] | [/H] | [/J]] Link Target

        /D      Creates a directory symbolic link.  Default is a file
                symbolic link.
        /H      Creates a hard link instead of a symbolic link.
        /J      Creates a Directory Junction.
        Link    specifies the new symbolic link name.
        Target  specifies the path (relative or absolute) that the new link
                refers to.

Note: The mklink command and arguments need to be provided as a single argument to cmd. Quote the entire command like so

cmd //c 'mklink link target'

Note that the command would normally be

cmd  /c 'mklink link target'

which would work in Cygwin and other shell environments, and even in an existing CMD session.  However, msys seems to mangle command-line arguments (to Windows commands), and it interprets /c as a pathname to the root of the C disk, and converts it to c:\.  Typing //c has been found to cause msys to pass the /c option to cmd.  See How to run internal cmd command from the msys shell?

4
  • I get the same result as you when I enter the command you entered, but when I supply the necessary arguments to actually make a link cmd /c mklink <link> <target>, then it opens the Windows shell in my current msys window. – JellicleCat Feb 14 '13 at 18:11
  • Ah, the mklink command and arguments need to be quoted. Answer amended accordingly. – ak2 Feb 15 '13 at 9:19
  • 2
    ...AND you need to escape the /c parameter, using //c instead. See superuser.com/a/526777/50251. – rsenna May 24 '14 at 3:36
  • Not much consolation, but I am trying to get mklink working without msys in a makefile and it is causing no end of pain. The cmd /c thing almost worked, but then incapacitated mingw32-make (no further output). Wonderful. Think I'm going to find a way to avoid symlinks if this is the case! – Engineer Jul 5 '15 at 15:59
7

You can use Windows native symlinks. To enable it uncomment line with:

MSYS=winsymlinks:nativestrict

in MSYS2 start bat file. And run MSYS2 with admin privileges.

1
  • 1
    In the current version you can set this by uncommenting the respective line in the respective .ini-files (msys2,ini, etc.) in the Msys2 installation folder (C:\msys64 by default). – TNT Mar 11 '18 at 9:52
5

Windows 10 now supports symbolic links without needing to run as administrator, so long as you turn on developer mode in the Windows settings.

Once that is done, you can get ln working correctly with a single line in a .bashrc file in your home directory!

export MSYS=winsymlinks:nativestrict

(I was not able to figure out where the equivalent .ini file is in my Git Bash installation. But setting the environment variable there would probably work, too.)

More details in this very helpful article by Josh Kelley: https://www.joshkel.com/2018/01/18/symlinks-in-windows/

4

MSYS=winsymlinks:nativestrict requires you to run MSYS2 in elevated mode, I'm really not comfortable with that.

This script only prompts for UAC elevation when invoked, of course it won't be useful for scripting then, but at least it suits my needs:

  • ~/scripts/sh/msys2-ln.sh:

    #!/bin/sh
    if [ "$#" -eq 2 -a "$1" == "-s" ]; then
        TARGET="$2"
        LINK=$(basename "$TARGET")
    elif [ "$#" -eq 3 -a "$1" == "-s" ]; then
        TARGET="$2"
        LINK="$3"
    else
        echo "this weak implementation only supports \`ln -s\`"
        exit
    fi
    
    if [ -d "$TARGET" ]; then
        MKLINK_OPTS="//d"
    fi
    
    TARGET=$(cygpath -w -a "$TARGET")
    LINK=$(cygpath -w -a "$LINK")
    
    echo "$TARGET"
    echo "$LINK"
    cscript //nologo ~/scripts/wsh/run-elevated.js \
        cmd //c mklink $MKLINK_OPTS "$LINK" "$TARGET"
    
  • ~/scripts/wsh/run-elevated.js

    var args = WScript.Arguments;
    if(args.length == 0){
        WScript.Echo("nothing to run");
        WScript.Quit(0);
    }
    
    var quoted_args = [];
    for(var i = 1; i < args.length; ++i){
        var arg = args(i); // it's a callable, not array like
        if(arg.indexOf(" ") != -1){
            arg = "\"" + arg + "\"";
        }
        quoted_args.push(arg);
    }
    
    var SHAPP = WScript.CreateObject("shell.application");
    SHAPP.ShellExecute(args(0), quoted_args.join(" "), "", "runas", 1);
    
  • ~/.bashrc

    # workaround for MSYS2's awkward ln
    if [ $- == *i* -a ! -z $MSYSTEM ]; then
        alias ln='~/scripts/sh/msys2-ln.sh'
        alias ecmd='powershell "start-process cmd.exe \"/k cd /d $(pwd -W)\" -verb runas"'
    fi
    

The additional ecmd alias launches an elevated cmd in current directory, might be useful sometimes, and it also serves as an example of acquiring UAC elevation through powershell, if somebody knows how to escape this thing properly, we can ditch that WSH helper.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.