Search code examples
windowscommand-linealiasjunctioncmder

How to use command line aliases to create a junction using paths with spaces in Cmder


I am using Cmder on Windows 10 (ConEmu 161206 [64] stable). I want to create an alias called cloud that creates a junction. Here's what I have typed in Cmder:

alias cloud=mklink /J "C:\Users\chjensen\OneDrive - chjensen\$1" "$2"

Let's say I want to create a link folder called Sublime, and the target is my Sublime packages at C:\Program Files\Sublime Text 3\Data\Packages. I try this:

cloud sublime C:\Program Files\Sublime Text 3\Data\Packages

But that command results in the following output:

Junction created for C:\Users\chjensen\OneDrive - chjensen\sublime <<===>> C:\Program

The 1st path is correct, but the 2nd path - C:\Program - is being cut off at the first space. When I try to access the link directory I can't, obviously, because it's trying to link to C:\Program, which doesn't exist.

If I write the entire command manually it works:

mklink /J "C:\Users\chjensen\OneDrive - chjensen\sublime" "C:\Program Files\Sublime Text 3\Data\Packages"

Junction created for C:\Users\chjensen\OneDrive - chjensen\sublime <<===>> C:\Program Files\Sublime Text 3\Data\Packages

I have also tried using the quotes in the actual command:

alias cloud=mklink /J "C:\Users\chjensen\OneDrive - chjensen\$1" $2
cloud sublime "C:\Program Files\Sublime Text 3\Data\Packages"

but I get the same result.

What am I doing wrong?


Solution

  • To fix this issue you can use the following code (this is not a very elegant solution, but it will work reasonably well). Note that I'm using c:\temp to test rather than your home directory, but its the same concept:

    alias cloud=mklink /J "C:\temp\OneDrive - chjensen\$1" $2 $3 $4 $5 $6 $7 $8 $9
    

    This would be the MS cmd command:

    cloud sublime "C:\Program Files\Sublime Text 3\Data\Packages"
    

    Here's an easy way to debug the alias, but using an echo command to put it to the terminal:

    alias cloudp=echo mklink /J "C:\temp\OneDrive - chjensen\$1" $2 $3 $4 $5 $6 $7 $8 $9
    

    As you've already mentioned, the issue that you're running into is that $2 is translating into "C:\Program, $3=Files\Sublime, etc. With this inelegant solution, it will simply put them all back together again, with the spaces and perhaps some trailing spaces that we don't care about after the last double quote.

    You could experiment with escaping each space via the carrot symbol, e.g., C:\Program^ Files\Sublime^ Text^ 3\Data\Packages, but I don't think that it will work and you don't want to remember to put a carrot symbol every time.

    If you can remove the spaces from your OneDrive path (spaces are evil, especially in the Microsoft cmd prompt), you could solve this much more elegantly via:

    alias cloud2=mklink /J C:\temp\OneDrive_chjensen\$*
    

    The $* means everything after the cloud command.

    Given what I've said above, what I would actually suggest... if at all possible, transition to using the PowerShell console in Cmder. It will be so much simpler than using the doskey based alias'. I wrote about a method that I use to pull in .ps1 files to the PowerShell consoles in this stackoverflow answer.

    I've created an example of some PowerShell that you could use (depending on your PowerShell version) to do the same thing, but with a lot more error checking and flexibility. See this link for more on PowerShell symbolic links.

    Additionally, consider using $env:USERPROFILE instead of C:\Users\chjensen to make the script more reusable on other machines (also, $env:ProgramFiles instead of c:\Program Files).

    function Get-DestPath
    {
        [CmdletBinding()]
        param ( [AllowNull()][String] $DestPath )
    
        if (!$DestPath)
        {
            # set to default
            $DestPath = 'C:\temp\OneDrive - chjensen\'
        }
    
        return $DestPath
    }
    
    function New-SymbolicLinkItem
    {
        [CmdletBinding(SupportsShouldProcess=$true)]
        param
        (
             [Parameter(Mandatory=$true)][String] $NewLinkName 
            ,[Parameter(Mandatory=$true)][String] $ItemToLink
            ,[AllowNull()][String] $DestPath
        )
    
        $DestPath = Get-DestPath -DestPath $DestPath
    
        Write-Verbose -Message ('New Link: NewLinkName={0}, ItemToLink={1}, DestPath={2}' -f $NewLinkName, $ItemToLink, $DestPath)
    
        if ($PSCmdlet.ShouldProcess($ItemToLink, 'New-SymbolicLinkItem'))
        {
            try
            {
                New-Item -ItemType SymbolicLink -Path $DestPath -Name $NewLinkName -Value $ItemToLink -ErrorAction Stop
            }
            catch
            {
                ('Error creating link: {0}, line number: {1}' -f $_, $_.InvocationInfo.ScriptLineNumber)
            }
        }
    }
    Set-Alias -Name cloud -Value New-SymbolicLinkItem
    

    You would use this command the same way in a PowerShell console as with the alias via:

    cloud sublime "C:\Program Files\Sublime Text 3\Data\Packages"
    

    I hope that this helps. Let me know if you have any questions.