030
030

Reputation: 11719

How to create a symlink on Windows via Ruby?

Creating symlinks on Ubuntu via FileUtils.symlink 'X', 'Y' works while on Windows the following error occurs:

C:/ruby/lib/ruby/2.0.0/fileutils.rb:349:in `symlink': symlink() function is unimplemented on this machine (NotImplementedError)
from C:/ruby/lib/ruby/2.0.0/fileutils.rb:349:in `block in ln_s'
from C:/ruby/lib/ruby/2.0.0/fileutils.rb:1567:in `fu_each_src_dest0'
from C:/ruby/lib/ruby/2.0.0/fileutils.rb:347:in `ln_s'
from C:/test/test.rb:7:in `<class:Test>'
from C:/test/test.rb:1:in `<main>'

As a workaround it is possible to create symlinks on Windows via mklink, but I would like to create symlinks on Windows via Ruby as well.

Upvotes: 1

Views: 2752

Answers (3)

maltesar
maltesar

Reputation: 336

Just tested this with JRuby 9.1.13.0 (2.3.3) and FileUtils seems to handle Windows symlinks now. The following is working if run as administrator. Softlinks should also work without administrator privileges.

require 'fileutils'

# Hardlinks work only for files
FileUtils.ln('./dir/file', './link')
# Softlinks to directories are created as symlinkd
FileUtils.ln_s('./dir', './link_dir')

Upvotes: 1

Rivenfall
Rivenfall

Reputation: 1263

First you need to run the script in elevated mode: Run ruby script in elevated mode

def windows?
  RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
end

if windows?
  def FileUtils.symlink source, dest, out = nil
    dest = File.expand_path(dest).gsub(?/, ?\\)
    source = File.expand_path(source).gsub(?/, ?\\)
    opt = File.directory?(source) ? '/d ' : ''
    output = `cmd.exe /c mklink #{opt}"#{dest}" "#{source}"` # & pause`
    out.puts output if out
  end
end

Upvotes: 1

Gabe
Gabe

Reputation: 495

Slightly embarrassed to post this ugly hack, but if you just GOTTA make it work, you can redefine methods and shell out to mklink. mklink is a bit complicated because it isn't a stand-alone program. Mklink is a cmd.exe shell command.

Below is for File class. For FileUtils, you can replace the class name and redefine methods you need as listed in the docs

require 'open3'

class << File
  alias_method :old_symlink, :symlink
  alias_method :old_symlink?, :symlink?

  def symlink(old_name, new_name)
    #if on windows, call mklink, else self.symlink
    if RUBY_PLATFORM =~ /mswin32|cygwin|mingw|bccwin/
      #windows mklink syntax is reverse of unix ln -s
      #windows mklink is built into cmd.exe
      #vulnerable to command injection, but okay because this is a hack to make a cli tool work.
      stdin, stdout, stderr, wait_thr = Open3.popen3('cmd.exe', "/c mklink #{new_name} #{old_name}")
      wait_thr.value.exitstatus
    else
      self.old_symlink(old_name, new_name)
    end
  end

  def symlink?(file_name)
    #if on windows, call mklink, else self.symlink
    if RUBY_PLATFORM =~ /mswin32|cygwin|mingw|bccwin/
      #vulnerable to command injection because calling with cmd.exe with /c?
      stdin, stdout, stderr, wait_thr = Open3.popen3("cmd.exe /c dir #{file_name} | find \"SYMLINK\"")
      wait_thr.value.exitstatus
    else
      self.old_symlink?(file_name)
    end
  end
end

Upvotes: 5

Related Questions