我简化/更新了Jeff Brown在CodeProject上发布的CreateJunction代码,例如使用自动封送传递结构体给DeviceIoControl,而不是手动管理内存。我只对创建连接点进行了此操作,因为您可以使用Directory.Delete()删除它,并且.Net的GetAttributes返回它是否具有重分析点。
我还删除了目标目录存在性检查,因为我发现能够创建到不存在或以后会存在的文件夹的联接很有用。(不同的驱动器等)
有一件事我无法理解, 就是字符串长度和nInBufferSize DeviceIoControl参数所添加的大小,它们似乎不完全等于Marshal.SizeOf返回值。
我在VB.Net中完成了这个操作,所以我使用了IC#Code的CodeConverter扩展将其转换为C#:
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
public class Junction
{
[Flags]
private enum Win32FileAccess : uint
{
GenericRead = 0x80000000U,
GenericWrite = 0x40000000U,
GenericExecute = 0x20000000U,
GenericAll = 0x10000000U
}
[Flags]
private enum Win32FileAttribute : uint
{
AttributeReadOnly = 0x1U,
AttributeHidden = 0x2U,
AttributeSystem = 0x4U,
AttributeDirectory = 0x10U,
AttributeArchive = 0x20U,
AttributeDevice = 0x40U,
AttributeNormal = 0x80U,
AttributeTemporary = 0x100U,
AttributeSparseFile = 0x200U,
AttributeReparsePoint = 0x400U,
AttributeCompressed = 0x800U,
AttributeOffline = 0x1000U,
AttributeNotContentIndexed = 0x2000U,
AttributeEncrypted = 0x4000U,
AttributeIntegrityStream = 0x8000U,
AttributeVirtual = 0x10000U,
AttributeNoScrubData = 0x20000U,
AttributeEA = 0x40000U,
AttributeRecallOnOpen = 0x40000U,
AttributePinned = 0x80000U,
AttributeUnpinned = 0x100000U,
AttributeRecallOnDataAccess = 0x400000U,
FlagOpenNoRecall = 0x100000U,
FlagOpenReparsePoint = 0x200000U,
FlagSessionAware = 0x800000U,
FlagPosixSemantics = 0x1000000U,
FlagBackupSemantics = 0x2000000U,
FlagDeleteOnClose = 0x4000000U,
FlagSequentialScan = 0x8000000U,
FlagRandomAccess = 0x10000000U,
FlagNoBuffering = 0x20000000U,
FlagOverlapped = 0x40000000U,
FlagWriteThrough = 0x80000000U
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern SafeFileHandle CreateFile(string lpFileName, Win32FileAccess dwDesiredAccess,
FileShare dwShareMode, IntPtr lpSecurityAttributes, FileMode dwCreationDisposition,
Win32FileAttribute dwFlagsAndAttributes, IntPtr hTemplateFile);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct ReparseDataBuffer
{
public uint ReparseTag;
public ushort ReparseDataLength;
private ushort Reserved;
public ushort SubstituteNameOffset;
public ushort SubstituteNameLength;
public ushort PrintNameOffset;
public ushort PrintNameLength;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8184)]
internal string PathBuffer;
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool DeviceIoControl(SafeFileHandle hDevice, uint dwIoControlCode,
[In] ReparseDataBuffer lpInBuffer, uint nInBufferSize,
IntPtr lpOutBuffer, uint nOutBufferSize,
[Out] uint lpBytesReturned, IntPtr lpOverlapped);
public static void Create(string junctionPath, string targetDir, bool overwrite = false)
{
const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003U;
const uint FSCTL_SET_REPARSE_POINT = 0x900A4U;
const string NonInterpretedPathPrefix = @"\??\";
if (Directory.Exists(junctionPath))
{
if (!overwrite)
throw new IOException("Directory already exists and overwrite parameter is false.");
}
else
{
Directory.CreateDirectory(junctionPath);
}
targetDir = NonInterpretedPathPrefix + Path.GetFullPath(targetDir);
using (var reparsePointHandle = CreateFile(junctionPath, Win32FileAccess.GenericWrite,
FileShare.Read | FileShare.Write | FileShare.Delete, IntPtr.Zero, FileMode.Open,
Win32FileAttribute.FlagBackupSemantics | Win32FileAttribute.FlagOpenReparsePoint, IntPtr.Zero))
{
if (reparsePointHandle.IsInvalid || Marshal.GetLastWin32Error() != 0)
{
throw new IOException("Unable to open reparse point.", new Win32Exception());
}
ushort byteLength = (ushort)(targetDir.Length * 2);
var reparseDataBuffer = new ReparseDataBuffer()
{
ReparseTag = IO_REPARSE_TAG_MOUNT_POINT,
ReparseDataLength = (ushort)(byteLength + 12u),
SubstituteNameOffset = 0,
SubstituteNameLength = byteLength,
PrintNameOffset = (ushort)(byteLength + 2u),
PrintNameLength = 0,
PathBuffer = targetDir
};
bool result = DeviceIoControl(reparsePointHandle, FSCTL_SET_REPARSE_POINT, reparseDataBuffer, (uint)(byteLength + 20), IntPtr.Zero, 0u, 0u, IntPtr.Zero);
if (!result)
throw new IOException("Unable to create junction point.", new Win32Exception());
}
}
}
同时也提供 VB.Net 的源代码:
Imports System
Imports System.ComponentModel
Imports System.IO
Imports System.Runtime.InteropServices
Imports Microsoft.Win32.SafeHandles
Public Class Junction
<Flags>
Private Enum Win32FileAccess As UInteger
GenericRead = &H80000000UI
GenericWrite = &H40000000
GenericExecute = &H20000000
GenericAll = &H10000000
End Enum
<Flags>
Private Enum Win32FileAttribute As UInteger
AttributeReadOnly = &H1
AttributeHidden = &H2
AttributeSystem = &H4
AttributeDirectory = &H10
AttributeArchive = &H20
AttributeDevice = &H40
AttributeNormal = &H80
AttributeTemporary = &H100
AttributeSparseFile = &H200
AttributeReparsePoint = &H400
AttributeCompressed = &H800
AttributeOffline = &H1000
AttributeNotContentIndexed = &H2000
AttributeEncrypted = &H4000
AttributeIntegrityStream = &H8000
AttributeVirtual = &H10000
AttributeNoScrubData = &H20000
AttributeEA = &H40000
AttributeRecallOnOpen = &H40000
AttributePinned = &H80000
AttributeUnpinned = &H100000
AttributeRecallOnDataAccess = &H400000
FlagOpenNoRecall = &H100000
FlagOpenReparsePoint = &H200000
FlagSessionAware = &H800000
FlagPosixSemantics = &H1000000
FlagBackupSemantics = &H2000000
FlagDeleteOnClose = &H4000000
FlagSequentialScan = &H8000000
FlagRandomAccess = &H10000000
FlagNoBuffering = &H20000000
FlagOverlapped = &H40000000
FlagWriteThrough = &H80000000UI
End Enum
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Private Shared Function CreateFile(lpFileName As String, dwDesiredAccess As Win32FileAccess,
dwShareMode As FileShare, lpSecurityAttributes As IntPtr,
dwCreationDisposition As FileMode, dwFlagsAndAttributes As Win32FileAttribute,
hTemplateFile As IntPtr) As SafeFileHandle
End Function
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
Private Structure ReparseDataBuffer
Public ReparseTag As UInteger
Public ReparseDataLength As UShort
Private Reserved As UShort
Public SubstituteNameOffset As UShort
Public SubstituteNameLength As UShort
Public PrintNameOffset As UShort
Public PrintNameLength As UShort
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=8184)>
Friend PathBuffer As String
End Structure
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Private Shared Function DeviceIoControl(hDevice As SafeFileHandle, dwIoControlCode As UInteger,
<[In]> ByRef lpInBuffer As ReparseDataBuffer, nInBufferSize As UInteger,
lpOutBuffer As IntPtr, nOutBufferSize As UInteger,
<Out> ByRef lpBytesReturned As UInteger, lpOverlapped As IntPtr) As Boolean
End Function
Public Shared Sub Create(junctionPath As String, targetDir As String, Optional overwrite As Boolean = False)
Const IO_REPARSE_TAG_MOUNT_POINT As UInteger = &HA0000003UI
Const FSCTL_SET_REPARSE_POINT As UInteger = &H900A4
Const NonInterpretedPathPrefix As String = "\??\"
If Directory.Exists(junctionPath) Then
If Not overwrite Then Throw New IOException("Directory already exists and overwrite parameter is false.")
Else
Directory.CreateDirectory(junctionPath)
End If
targetDir = NonInterpretedPathPrefix & Path.GetFullPath(targetDir)
Using reparsePointHandle As SafeFileHandle = CreateFile(junctionPath, Win32FileAccess.GenericWrite,
FileShare.Read Or FileShare.Write Or FileShare.Delete, IntPtr.Zero, FileMode.Open,
Win32FileAttribute.FlagBackupSemantics Or Win32FileAttribute.FlagOpenReparsePoint, IntPtr.Zero)
If reparsePointHandle.IsInvalid OrElse Marshal.GetLastWin32Error() <> 0 Then
Throw New IOException("Unable to open reparse point.", New Win32Exception())
End If
Dim byteLength As UShort = CType(targetDir.Length * 2, UShort)
Dim reparseDataBuffer As New ReparseDataBuffer With {
.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT,
.ReparseDataLength = byteLength + 12US,
.SubstituteNameOffset = 0,
.SubstituteNameLength = byteLength,
.PrintNameOffset = byteLength + 2US,
.PrintNameLength = 0,
.PathBuffer = targetDir
}
Dim result As Boolean = DeviceIoControl(reparsePointHandle, FSCTL_SET_REPARSE_POINT, reparseDataBuffer, byteLength + 20US, IntPtr.Zero, 0, 0, IntPtr.Zero)
If Not result Then Throw New IOException("Unable to create junction point.", New Win32Exception())
End Using
End Sub
End Class