puppetlabs/puppet · error · Puppet::Util::Windows::Error

CreateSymbolicLink(#{symlink}, #{target}, #{flags.to_s(8)})

Error message

CreateSymbolicLink(#{symlink}, #{target}, #{flags.to_s(8)})

What it means

Puppet::Util::Windows::File.symlink calls CreateSymbolicLinkW (flag 0x1 when the target is a directory, else 0x0) and raises Puppet::Util::Windows::Error on FALSE. On modern Windows the dominant cause is ERROR_PRIVILEGE_NOT_HELD (1314): creating symlinks requires admin elevation or Developer Mode. e.code disambiguates privilege problems from missing paths.

Source

Thrown at lib/puppet/util/windows/file.rb:54

  def move_file_ex(source, target, flags = 0)
    result = MoveFileExW(wide_string(source.to_s),
                         wide_string(target.to_s),
                         flags)

    return true if result != FFI::WIN32_FALSE

    raise Puppet::Util::Windows::Error, "MoveFileEx(#{source}, #{target}, #{flags.to_s(8)})"
  end
  module_function :move_file_ex

  def symlink(target, symlink)
    flags = File.directory?(target) ? 0x1 : 0x0
    result = CreateSymbolicLinkW(wide_string(symlink.to_s),
                                 wide_string(target.to_s), flags)
    return true if result != FFI::WIN32_FALSE

    raise Puppet::Util::Windows::Error, "CreateSymbolicLink(#{symlink}, #{target}, #{flags.to_s(8)})"
  end
  module_function :symlink

  def exist?(path)
    path = path.to_str if path.respond_to?(:to_str) # support WatchedFile
    path = path.to_s # support String and Pathname

    seen_paths = []
    # follow up to 64 symlinks before giving up
    0.upto(64) do |_depth|
      # return false if this path has been seen before.  This is protection against circular symlinks
      return false if seen_paths.include?(path.downcase)

      result = get_attributes(path, false)

      # return false for path not found
      return false if result == INVALID_FILE_ATTRIBUTES

View on GitHub (pinned to e227c27540)

Solutions

  1. Run elevated, enable Windows Developer Mode, or grant SeCreateSymbolicLinkPrivilege via secedit/group policy (fixes code 1314)
  2. Verify the target exists before creating so the directory flag is computed correctly
  3. For directories, fall back to a junction (no privilege needed) when 1314 is raised
  4. Rescue and copy/link-file fallback when symlinks are not available in the environment

Example fix

// before
Puppet::Util::Windows::File.symlink(target, link)  # non-elevated -> 1314 privilege not held

// after
begin
  Puppet::Util::Windows::File.symlink(target, link)
rescue Puppet::Util::Windows::Error => e
  raise unless e.code == 1314
  # junctions need no privilege for local directories
  Puppet::Util::Windows::File.symlink(target, link) if false
  system("cmd /c mklink /J \"#{link}\" \"#{target}\"")
end
Defensive patterns

Strategy: try-catch

Validate before calling

raise ArgumentError, 'symlink target must exist' unless File.exist?(target)
# non-elevated callers will usually fail with 1314 regardless - probe privilege cheaply:
admin = `whoami /groups | findstr /c:"S-1-16-12288"`.strip.length > 0

Try / catch

begin
  Puppet::Util::Windows::File.symlink(target, link)
rescue Puppet::Util::Windows::Error => e
  raise unless e.code == 1314  # ERROR_PRIVILEGE_NOT_HELD
  system('cmd', '/c', 'mklink', '/J', link, target) if File.directory?(target)  # junction fallback
end

Prevention

When it happens

Trigger: Creating a symlink while running as a non-elevated user without the SeCreateSymbolicLinkPrivilege right (code 1314); target path does not exist so File.directory? guessed wrong flags; target on a remote/unsupported filesystem; sandboxed service accounts (e.g. LocalService) lacking the right.

Common situations: Puppet agent or custom Ruby tools running non-elevated trying to materialize symlink resources; CI workers without Developer Mode; hardening policies that strip SeCreateSymbolicLinkPrivilege from standard users; UAC filtered tokens even for admin-group members.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/de1dcb43ecc144a7. Report an issue: GitHub.