diff --git a/src/internal/syscall/windows/mksyscall.go b/src/internal/syscall/windows/mksyscall.go
index 21a2b4e777328a5b35d3ea4a3693c76ebf253447..0b01938b871c09cb2be65aba4fef119bb8fdf550 100644
--- a/src/internal/syscall/windows/mksyscall.go
+++ b/src/internal/syscall/windows/mksyscall.go
@@ -4,4 +4,4 @@
 
 package windows
 
-//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go
+//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go security_windows.go
diff --git a/src/internal/syscall/windows/reparse_windows.go b/src/internal/syscall/windows/reparse_windows.go
new file mode 100644
index 0000000000000000000000000000000000000000..7c6ad8fb7e064b4fcf99fd55fd753bb639251b31
--- /dev/null
+++ b/src/internal/syscall/windows/reparse_windows.go
@@ -0,0 +1,64 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package windows
+
+const (
+	FSCTL_SET_REPARSE_POINT    = 0x000900A4
+	IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
+
+	SYMLINK_FLAG_RELATIVE = 1
+)
+
+// These structures are described
+// in https://msdn.microsoft.com/en-us/library/cc232007.aspx
+// and https://msdn.microsoft.com/en-us/library/cc232006.aspx.
+
+// REPARSE_DATA_BUFFER_HEADER is a common part of REPARSE_DATA_BUFFER structure.
+type REPARSE_DATA_BUFFER_HEADER struct {
+	ReparseTag uint32
+	// The size, in bytes, of the reparse data that follows
+	// the common portion of the REPARSE_DATA_BUFFER element.
+	// This value is the length of the data starting at the
+	// SubstituteNameOffset field.
+	ReparseDataLength uint16
+	Reserved          uint16
+}
+
+type SymbolicLinkReparseBuffer struct {
+	// The integer that contains the offset, in bytes,
+	// of the substitute name string in the PathBuffer array,
+	// computed as an offset from byte 0 of PathBuffer. Note that
+	// this offset must be divided by 2 to get the array index.
+	SubstituteNameOffset uint16
+	// The integer that contains the length, in bytes, of the
+	// substitute name string. If this string is null-terminated,
+	// SubstituteNameLength does not include the Unicode null character.
+	SubstituteNameLength uint16
+	// PrintNameOffset is similar to SubstituteNameOffset.
+	PrintNameOffset uint16
+	// PrintNameLength is similar to SubstituteNameLength.
+	PrintNameLength uint16
+	// Flags specifies whether the substitute name is a full path name or
+	// a path name relative to the directory containing the symbolic link.
+	Flags      uint32
+	PathBuffer [1]uint16
+}
+
+type MountPointReparseBuffer struct {
+	// The integer that contains the offset, in bytes,
+	// of the substitute name string in the PathBuffer array,
+	// computed as an offset from byte 0 of PathBuffer. Note that
+	// this offset must be divided by 2 to get the array index.
+	SubstituteNameOffset uint16
+	// The integer that contains the length, in bytes, of the
+	// substitute name string. If this string is null-terminated,
+	// SubstituteNameLength does not include the Unicode null character.
+	SubstituteNameLength uint16
+	// PrintNameOffset is similar to SubstituteNameOffset.
+	PrintNameOffset uint16
+	// PrintNameLength is similar to SubstituteNameLength.
+	PrintNameLength uint16
+	PathBuffer      [1]uint16
+}
diff --git a/src/internal/syscall/windows/security_windows.go b/src/internal/syscall/windows/security_windows.go
new file mode 100644
index 0000000000000000000000000000000000000000..2c145e160f70b44214c86a337d7cdd407636e886
--- /dev/null
+++ b/src/internal/syscall/windows/security_windows.go
@@ -0,0 +1,57 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package windows
+
+import (
+	"syscall"
+)
+
+const (
+	SecurityAnonymous      = 0
+	SecurityIdentification = 1
+	SecurityImpersonation  = 2
+	SecurityDelegation     = 3
+)
+
+//sys	ImpersonateSelf(impersonationlevel uint32) (err error) = advapi32.ImpersonateSelf
+//sys	RevertToSelf() (err error) = advapi32.RevertToSelf
+
+const (
+	TOKEN_ADJUST_PRIVILEGES = 0x0020
+	SE_PRIVILEGE_ENABLED    = 0x00000002
+)
+
+type LUID struct {
+	LowPart  uint32
+	HighPart int32
+}
+
+type LUID_AND_ATTRIBUTES struct {
+	Luid       LUID
+	Attributes uint32
+}
+
+type TOKEN_PRIVILEGES struct {
+	PrivilegeCount uint32
+	Privileges     [1]LUID_AND_ATTRIBUTES
+}
+
+//sys	OpenThreadToken(h syscall.Handle, access uint32, openasself bool, token *syscall.Token) (err error) = advapi32.OpenThreadToken
+//sys	LookupPrivilegeValue(systemname *uint16, name *uint16, luid *LUID) (err error) = advapi32.LookupPrivilegeValueW
+//sys	adjustTokenPrivileges(token syscall.Token, disableAllPrivileges bool, newstate *TOKEN_PRIVILEGES, buflen uint32, prevstate *TOKEN_PRIVILEGES, returnlen *uint32) (ret uint32, err error) [true] = advapi32.AdjustTokenPrivileges
+
+func AdjustTokenPrivileges(token syscall.Token, disableAllPrivileges bool, newstate *TOKEN_PRIVILEGES, buflen uint32, prevstate *TOKEN_PRIVILEGES, returnlen *uint32) error {
+	ret, err := adjustTokenPrivileges(token, disableAllPrivileges, newstate, buflen, prevstate, returnlen)
+	if ret == 0 {
+		// AdjustTokenPrivileges call failed
+		return err
+	}
+	// AdjustTokenPrivileges call succeeded
+	if err == syscall.EINVAL {
+		// GetLastError returned ERROR_SUCCESS
+		return nil
+	}
+	return err
+}
diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go
index 015862d71336f480c8dd3e9e9ee202dc1765e08e..77d6033a353d62ca97cc00cab115389484de8e5a 100644
--- a/src/internal/syscall/windows/syscall_windows.go
+++ b/src/internal/syscall/windows/syscall_windows.go
@@ -140,3 +140,4 @@ func Rename(oldpath, newpath string) error {
 //sys	GetACP() (acp uint32) = kernel32.GetACP
 //sys	GetConsoleCP() (ccp uint32) = kernel32.GetConsoleCP
 //sys	MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32, wchar *uint16, nwchar int32) (nwrite int32, err error) = kernel32.MultiByteToWideChar
+//sys	GetCurrentThread() (pseudoHandle syscall.Handle, err error) = kernel32.GetCurrentThread
diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go
index 0b814e9b4a460986be41cd1887defef1fb44be6a..f6a89540725a2625f1fad103d1eff8b6f514613b 100644
--- a/src/internal/syscall/windows/zsyscall_windows.go
+++ b/src/internal/syscall/windows/zsyscall_windows.go
@@ -38,13 +38,20 @@ func errnoErr(e syscall.Errno) error {
 var (
 	modiphlpapi = syscall.NewLazyDLL(sysdll.Add("iphlpapi.dll"))
 	modkernel32 = syscall.NewLazyDLL(sysdll.Add("kernel32.dll"))
+	modadvapi32 = syscall.NewLazyDLL(sysdll.Add("advapi32.dll"))
 
-	procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
-	procGetComputerNameExW   = modkernel32.NewProc("GetComputerNameExW")
-	procMoveFileExW          = modkernel32.NewProc("MoveFileExW")
-	procGetACP               = modkernel32.NewProc("GetACP")
-	procGetConsoleCP         = modkernel32.NewProc("GetConsoleCP")
-	procMultiByteToWideChar  = modkernel32.NewProc("MultiByteToWideChar")
+	procGetAdaptersAddresses  = modiphlpapi.NewProc("GetAdaptersAddresses")
+	procGetComputerNameExW    = modkernel32.NewProc("GetComputerNameExW")
+	procMoveFileExW           = modkernel32.NewProc("MoveFileExW")
+	procGetACP                = modkernel32.NewProc("GetACP")
+	procGetConsoleCP          = modkernel32.NewProc("GetConsoleCP")
+	procMultiByteToWideChar   = modkernel32.NewProc("MultiByteToWideChar")
+	procGetCurrentThread      = modkernel32.NewProc("GetCurrentThread")
+	procImpersonateSelf       = modadvapi32.NewProc("ImpersonateSelf")
+	procRevertToSelf          = modadvapi32.NewProc("RevertToSelf")
+	procOpenThreadToken       = modadvapi32.NewProc("OpenThreadToken")
+	procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
+	procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
 )
 
 func GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizePointer *uint32) (errcode error) {
@@ -103,3 +110,89 @@ func MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32,
 	}
 	return
 }
+
+func GetCurrentThread() (pseudoHandle syscall.Handle, err error) {
+	r0, _, e1 := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
+	pseudoHandle = syscall.Handle(r0)
+	if pseudoHandle == 0 {
+		if e1 != 0 {
+			err = errnoErr(e1)
+		} else {
+			err = syscall.EINVAL
+		}
+	}
+	return
+}
+
+func ImpersonateSelf(impersonationlevel uint32) (err error) {
+	r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(impersonationlevel), 0, 0)
+	if r1 == 0 {
+		if e1 != 0 {
+			err = errnoErr(e1)
+		} else {
+			err = syscall.EINVAL
+		}
+	}
+	return
+}
+
+func RevertToSelf() (err error) {
+	r1, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0)
+	if r1 == 0 {
+		if e1 != 0 {
+			err = errnoErr(e1)
+		} else {
+			err = syscall.EINVAL
+		}
+	}
+	return
+}
+
+func OpenThreadToken(h syscall.Handle, access uint32, openasself bool, token *syscall.Token) (err error) {
+	var _p0 uint32
+	if openasself {
+		_p0 = 1
+	} else {
+		_p0 = 0
+	}
+	r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(h), uintptr(access), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0)
+	if r1 == 0 {
+		if e1 != 0 {
+			err = errnoErr(e1)
+		} else {
+			err = syscall.EINVAL
+		}
+	}
+	return
+}
+
+func LookupPrivilegeValue(systemname *uint16, name *uint16, luid *LUID) (err error) {
+	r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemname)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
+	if r1 == 0 {
+		if e1 != 0 {
+			err = errnoErr(e1)
+		} else {
+			err = syscall.EINVAL
+		}
+	}
+	return
+}
+
+func adjustTokenPrivileges(token syscall.Token, disableAllPrivileges bool, newstate *TOKEN_PRIVILEGES, buflen uint32, prevstate *TOKEN_PRIVILEGES, returnlen *uint32) (ret uint32, err error) {
+	var _p0 uint32
+	if disableAllPrivileges {
+		_p0 = 1
+	} else {
+		_p0 = 0
+	}
+	r0, _, e1 := syscall.Syscall6(procAdjustTokenPrivileges.Addr(), 6, uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(newstate)), uintptr(buflen), uintptr(unsafe.Pointer(prevstate)), uintptr(unsafe.Pointer(returnlen)))
+	ret = uint32(r0)
+	if true {
+		if e1 != 0 {
+			err = errnoErr(e1)
+		} else {
+			err = syscall.EINVAL
+		}
+	}
+	return
+}
diff --git a/src/os/os_windows_test.go b/src/os/os_windows_test.go
index a6085f13680a908669c17c43608db68704326555..acdf4f17a658fb9ad5d282fc36f9d329cf388e41 100644
--- a/src/os/os_windows_test.go
+++ b/src/os/os_windows_test.go
@@ -5,26 +5,21 @@
 package os_test
 
 import (
+	"fmt"
+	"internal/syscall/windows"
 	"internal/testenv"
 	"io/ioutil"
 	"os"
 	osexec "os/exec"
 	"path/filepath"
+	"runtime"
 	"sort"
 	"strings"
 	"syscall"
 	"testing"
+	"unsafe"
 )
 
-var supportJunctionLinks = true
-
-func init() {
-	b, _ := osexec.Command("cmd", "/c", "mklink", "/?").Output()
-	if !strings.Contains(string(b), " /J ") {
-		supportJunctionLinks = false
-	}
-}
-
 func TestSameWindowsFile(t *testing.T) {
 	temp, err := ioutil.TempDir("", "TestSameWindowsFile")
 	if err != nil {
@@ -78,34 +73,331 @@ func TestSameWindowsFile(t *testing.T) {
 	}
 }
 
-func TestStatJunctionLink(t *testing.T) {
-	if !supportJunctionLinks {
-		t.Skip("skipping because junction links are not supported")
+type dirLinkTest struct {
+	name    string
+	mklink  func(link, target string) error
+	issueNo int // correspondent issue number (for broken tests)
+}
+
+func testDirLinks(t *testing.T, tests []dirLinkTest) {
+	tmpdir, err := ioutil.TempDir("", "testDirLinks")
+	if err != nil {
+		t.Fatal(err)
 	}
+	defer os.RemoveAll(tmpdir)
 
-	dir, err := ioutil.TempDir("", "go-build")
+	oldwd, err := os.Getwd()
 	if err != nil {
-		t.Fatalf("failed to create temp directory: %v", err)
+		t.Fatal(err)
 	}
-	defer os.RemoveAll(dir)
+	err = os.Chdir(tmpdir)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Chdir(oldwd)
 
-	link := filepath.Join(filepath.Dir(dir), filepath.Base(dir)+"-link")
+	dir := filepath.Join(tmpdir, "dir")
+	err = os.Mkdir(dir, 0777)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = ioutil.WriteFile(filepath.Join(dir, "abc"), []byte("abc"), 0644)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, test := range tests {
+		link := filepath.Join(tmpdir, test.name+"_link")
+		err := test.mklink(link, dir)
+		if err != nil {
+			t.Errorf("creating link for %s test failed: %v", test.name, err)
+			continue
+		}
+
+		data, err := ioutil.ReadFile(filepath.Join(link, "abc"))
+		if err != nil {
+			t.Errorf("failed to read abc file: %v", err)
+			continue
+		}
+		if string(data) != "abc" {
+			t.Errorf(`abc file is expected to have "abc" in it, but has %v`, data)
+			continue
+		}
+
+		if test.issueNo > 0 {
+			t.Logf("skipping broken %q test: see issue %d", test.name, test.issueNo)
+			continue
+		}
+
+		fi, err := os.Stat(link)
+		if err != nil {
+			t.Errorf("failed to stat link %v: %v", link, err)
+			continue
+		}
+		expected := filepath.Base(dir)
+		got := fi.Name()
+		if !fi.IsDir() || expected != got {
+			t.Errorf("link should point to %v but points to %v instead", expected, got)
+			continue
+		}
+	}
+}
+
+// reparseData is used to build reparse buffer data required for tests.
+type reparseData struct {
+	substituteName namePosition
+	printName      namePosition
+	pathBuf        []uint16
+}
 
-	output, err := osexec.Command("cmd", "/c", "mklink", "/J", link, dir).CombinedOutput()
+type namePosition struct {
+	offset uint16
+	length uint16
+}
+
+func (rd *reparseData) addUTF16s(s []uint16) (offset uint16) {
+	off := len(rd.pathBuf) * 2
+	rd.pathBuf = append(rd.pathBuf, s...)
+	return uint16(off)
+}
+
+func (rd *reparseData) addString(s string) (offset, length uint16) {
+	p := syscall.StringToUTF16(s)
+	return rd.addUTF16s(p), uint16(len(p)-1) * 2 // do not include terminating NUL in the legth (as per PrintNameLength and SubstituteNameLength documentation)
+}
+
+func (rd *reparseData) addSubstituteName(name string) {
+	rd.substituteName.offset, rd.substituteName.length = rd.addString(name)
+}
+
+func (rd *reparseData) addPrintName(name string) {
+	rd.printName.offset, rd.printName.length = rd.addString(name)
+}
+
+func (rd *reparseData) addStringNoNUL(s string) (offset, length uint16) {
+	p := syscall.StringToUTF16(s)
+	p = p[:len(p)-1]
+	return rd.addUTF16s(p), uint16(len(p)) * 2
+}
+
+func (rd *reparseData) addSubstituteNameNoNUL(name string) {
+	rd.substituteName.offset, rd.substituteName.length = rd.addStringNoNUL(name)
+}
+
+func (rd *reparseData) addPrintNameNoNUL(name string) {
+	rd.printName.offset, rd.printName.length = rd.addStringNoNUL(name)
+}
+
+// pathBuffeLen returns length of rd pathBuf in bytes.
+func (rd *reparseData) pathBuffeLen() uint16 {
+	return uint16(len(rd.pathBuf)) * 2
+}
+
+// Windows REPARSE_DATA_BUFFER contains union member, and cannot be
+// translated into Go directly. _REPARSE_DATA_BUFFER type is to help
+// construct alternative versions of Windows REPARSE_DATA_BUFFER with
+// union part of SymbolicLinkReparseBuffer or MountPointReparseBuffer type.
+type _REPARSE_DATA_BUFFER struct {
+	header windows.REPARSE_DATA_BUFFER_HEADER
+	detail [syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
+}
+
+func createDirLink(link string, rdb *_REPARSE_DATA_BUFFER) error {
+	err := os.Mkdir(link, 0777)
+	if err != nil {
+		return err
+	}
+
+	linkp := syscall.StringToUTF16(link)
+	fd, err := syscall.CreateFile(&linkp[0], syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING,
+		syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
+	if err != nil {
+		return err
+	}
+	defer syscall.CloseHandle(fd)
+
+	buflen := uint32(rdb.header.ReparseDataLength) + uint32(unsafe.Sizeof(rdb.header))
+	var bytesReturned uint32
+	return syscall.DeviceIoControl(fd, windows.FSCTL_SET_REPARSE_POINT,
+		(*byte)(unsafe.Pointer(&rdb.header)), buflen, nil, 0, &bytesReturned, nil)
+}
+
+func createMountPoint(link string, target *reparseData) error {
+	var buf *windows.MountPointReparseBuffer
+	buflen := uint16(unsafe.Offsetof(buf.PathBuffer)) + target.pathBuffeLen() // see ReparseDataLength documentation
+	byteblob := make([]byte, buflen)
+	buf = (*windows.MountPointReparseBuffer)(unsafe.Pointer(&byteblob[0]))
+	buf.SubstituteNameOffset = target.substituteName.offset
+	buf.SubstituteNameLength = target.substituteName.length
+	buf.PrintNameOffset = target.printName.offset
+	buf.PrintNameLength = target.printName.length
+	copy((*[2048]uint16)(unsafe.Pointer(&buf.PathBuffer[0]))[:], target.pathBuf)
+
+	var rdb _REPARSE_DATA_BUFFER
+	rdb.header.ReparseTag = windows.IO_REPARSE_TAG_MOUNT_POINT
+	rdb.header.ReparseDataLength = buflen
+	copy(rdb.detail[:], byteblob)
+
+	return createDirLink(link, &rdb)
+}
+
+func TestDirectoryJunction(t *testing.T) {
+	var tests = []dirLinkTest{
+		{
+			// Create link similar to what mklink does, by inserting \??\ at the front of absolute target.
+			name: "standard",
+			mklink: func(link, target string) error {
+				var t reparseData
+				t.addSubstituteName(`\??\` + target)
+				t.addPrintName(target)
+				return createMountPoint(link, &t)
+			},
+		},
+		{
+			// Do as junction utility https://technet.microsoft.com/en-au/sysinternals/bb896768.aspx does - set PrintNameLength to 0.
+			name: "have_blank_print_name",
+			mklink: func(link, target string) error {
+				var t reparseData
+				t.addSubstituteName(`\??\` + target)
+				t.addPrintName("")
+				return createMountPoint(link, &t)
+			},
+			issueNo: 16145,
+		},
+	}
+	output, _ := osexec.Command("cmd", "/c", "mklink", "/?").Output()
+	mklinkSupportsJunctionLinks := strings.Contains(string(output), " /J ")
+	if mklinkSupportsJunctionLinks {
+		tests = append(tests,
+			dirLinkTest{
+				name: "use_mklink_cmd",
+				mklink: func(link, target string) error {
+					output, err := osexec.Command("cmd", "/c", "mklink", "/J", link, target).CombinedOutput()
+					if err != nil {
+						fmt.Errorf("failed to run mklink %v %v: %v %q", link, target, err, output)
+					}
+					return nil
+				},
+			},
+		)
+	} else {
+		t.Log(`skipping "use_mklink_cmd" test, mklink does not supports directory junctions`)
+	}
+	testDirLinks(t, tests)
+}
+
+func enableCurrentThreadPrivilege(privilegeName string) error {
+	ct, err := windows.GetCurrentThread()
 	if err != nil {
-		t.Fatalf("failed to run mklink %v %v: %v %q", link, dir, err, output)
+		return err
 	}
-	defer os.Remove(link)
+	var t syscall.Token
+	err = windows.OpenThreadToken(ct, syscall.TOKEN_QUERY|windows.TOKEN_ADJUST_PRIVILEGES, false, &t)
+	if err != nil {
+		return err
+	}
+	defer syscall.CloseHandle(syscall.Handle(t))
+
+	var tp windows.TOKEN_PRIVILEGES
 
-	fi, err := os.Stat(link)
+	privStr, err := syscall.UTF16PtrFromString(privilegeName)
 	if err != nil {
-		t.Fatalf("failed to stat link %v: %v", link, err)
+		return err
 	}
-	expected := filepath.Base(dir)
-	got := fi.Name()
-	if !fi.IsDir() || expected != got {
-		t.Fatalf("link should point to %v but points to %v instead", expected, got)
+	err = windows.LookupPrivilegeValue(nil, privStr, &tp.Privileges[0].Luid)
+	if err != nil {
+		return err
+	}
+	tp.PrivilegeCount = 1
+	tp.Privileges[0].Attributes = windows.SE_PRIVILEGE_ENABLED
+	return windows.AdjustTokenPrivileges(t, false, &tp, 0, nil, nil)
+}
+
+func createSymbolicLink(link string, target *reparseData, isrelative bool) error {
+	var buf *windows.SymbolicLinkReparseBuffer
+	buflen := uint16(unsafe.Offsetof(buf.PathBuffer)) + target.pathBuffeLen() // see ReparseDataLength documentation
+	byteblob := make([]byte, buflen)
+	buf = (*windows.SymbolicLinkReparseBuffer)(unsafe.Pointer(&byteblob[0]))
+	buf.SubstituteNameOffset = target.substituteName.offset
+	buf.SubstituteNameLength = target.substituteName.length
+	buf.PrintNameOffset = target.printName.offset
+	buf.PrintNameLength = target.printName.length
+	if isrelative {
+		buf.Flags = windows.SYMLINK_FLAG_RELATIVE
+	}
+	copy((*[2048]uint16)(unsafe.Pointer(&buf.PathBuffer[0]))[:], target.pathBuf)
+
+	var rdb _REPARSE_DATA_BUFFER
+	rdb.header.ReparseTag = syscall.IO_REPARSE_TAG_SYMLINK
+	rdb.header.ReparseDataLength = buflen
+	copy(rdb.detail[:], byteblob)
+
+	return createDirLink(link, &rdb)
+}
+
+func TestDirectorySymbolicLink(t *testing.T) {
+	var tests []dirLinkTest
+	output, _ := osexec.Command("cmd", "/c", "mklink", "/?").Output()
+	mklinkSupportsDirectorySymbolicLinks := strings.Contains(string(output), " /D ")
+	if mklinkSupportsDirectorySymbolicLinks {
+		tests = append(tests,
+			dirLinkTest{
+				name: "use_mklink_cmd",
+				mklink: func(link, target string) error {
+					output, err := osexec.Command("cmd", "/c", "mklink", "/D", link, target).CombinedOutput()
+					if err != nil {
+						fmt.Errorf("failed to run mklink %v %v: %v %q", link, target, err, output)
+					}
+					return nil
+				},
+			},
+		)
+	} else {
+		t.Log(`skipping "use_mklink_cmd" test, mklink does not supports directory symbolic links`)
+	}
+
+	// The rest of these test requires SeCreateSymbolicLinkPrivilege to be held.
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	err := windows.ImpersonateSelf(windows.SecurityImpersonation)
+	if err != nil {
+		t.Fatal(err)
 	}
+	defer windows.RevertToSelf()
+
+	err = enableCurrentThreadPrivilege("SeCreateSymbolicLinkPrivilege")
+	if err != nil {
+		t.Skipf(`skipping some tests, could not enable "SeCreateSymbolicLinkPrivilege": %v`, err)
+	}
+	tests = append(tests,
+		dirLinkTest{
+			name: "use_os_pkg",
+			mklink: func(link, target string) error {
+				return os.Symlink(target, link)
+			},
+		},
+		dirLinkTest{
+			// Create link similar to what mklink does, by inserting \??\ at the front of absolute target.
+			name: "standard",
+			mklink: func(link, target string) error {
+				var t reparseData
+				t.addPrintName(target)
+				t.addSubstituteName(`\??\` + target)
+				return createSymbolicLink(link, &t, false)
+			},
+		},
+		dirLinkTest{
+			name: "relative",
+			mklink: func(link, target string) error {
+				var t reparseData
+				t.addSubstituteNameNoNUL(filepath.Base(target))
+				t.addPrintNameNoNUL(filepath.Base(target))
+				return createSymbolicLink(link, &t, true)
+			},
+			issueNo: 15978,
+		},
+	)
+	testDirLinks(t, tests)
 }
 
 func TestStartProcessAttr(t *testing.T) {