.Net中的Junction Points操作

Junction Points是NTFS v5+的新特性,功能和我们所熟知的UNIX中的文件夹软链接类似(与Windows中的文件夹快捷方式不同的是,它进行了路径重定向)。如在Vista中的C:\Documents and Settings 就是C:\Users的一个链接。然而,在Windows中并没有提供相关命令,只能编程通过API来实现。在CodeProject中有篇文章Manipulating NTFS Junction Points in .NET将相关API进行了封装,使得我们可以很容易的在.Net中进行相关操作。

由于我对WinAPI编程并不熟,这里就不更多介绍了,更多信息请访问原作者相关文章。这里仅转一下相关函数用法:

创建Junction Point

// Creates a Junction Point at
// C:\Foo\JunctionPoint that points to the directory C:\Bar.
// Fails if there is already a file,
// directory or Junction Point with the specified path.
JunctionPoint.Create(@"C:\Foo\JunctionPoint", @"C:\Bar",
false/*don't overwrite*/)

// Creates a Junction Point at C:\Foo\JunctionPoint that points to
// the directory C:\Bar.
// Replaces an existing Junction Point if found at the specified path.
JunctionPoint.Create(@"C:\Foo\JunctionPoint", @"C:\Bar", true/*overwrite*/)

注:不能对文件创建Junction Points.

删除Junction Point

// Delete a Junction Point at C:\Foo\JunctionPoint if it exists.
// Does nothing if there is no such Junction Point.
// Fails if the specified path refers to an existing file or
// directory rather than a Junction Point.
JunctionPoint.Delete(@"C:\Foo\JunctionPoint")

判断Junction Point是否存在

// Returns true if there is a Junction Point at C:\Foo\JunctionPoint.
// Returns false if the specified path refers to an existing file
// or directory rather than a Junction Point
// or if it refers to the vacuum of space.
bool exists = JunctionPoint.Exists(@"C:\Foo\JunctionPoint")

获取Junction Point所指向的实际地址

// Create a Junction Point for demonstration purposes whose target is C:\Bar.
JunctionPoint.Create(@"C:\Foo\JunctionPoint", @"C:\Bar", false)

// Returns the full path of the target of the Junction Point at
// C:\Foo\JunctionPoint.
// Fails if the specified path does not refer to a Junction Point.
string target = JunctionPoint.GetTarget(@"C:\Foo\JunctionPoint")

// target will be C:\Bar

注:这个函数有问题,对系统的权限要求过高,如果对系统盘的一些文件夹链接访问往往会报异常,我把它改了一下,新的代码如下:

Code
    /**//// <summary>
    
/// Provides access to NTFS junction points in .Net.
    
/// </summary>

    public static class JunctionPoint
    
{
        
/**//// <summary>
        
/// The file or directory is not a reparse point.
        
/// </summary>

        private const int ERROR_NOT_A_REPARSE_POINT = 4390;

        
/**//// <summary>
        
/// The reparse point attribute cannot be set because it conflicts with an existing attribute.
        
/// </summary>

        private const int ERROR_REPARSE_ATTRIBUTE_CONFLICT = 4391;

        
/**//// <summary>
        
/// The data present in the reparse point buffer is invalid.
        
/// </summary>

        private const int ERROR_INVALID_REPARSE_DATA = 4392;

        
/**//// <summary>
        
/// The tag present in the reparse point buffer is invalid.
        
/// </summary>

        private const int ERROR_REPARSE_TAG_INVALID = 4393;

        
/**//// <summary>
        
/// There is a mismatch between the tag specified in the request and the tag present in the reparse point.
        
/// </summary>

        private const int ERROR_REPARSE_TAG_MISMATCH = 4394;

        
/**//// <summary>
        
/// Command to set the reparse point data block.
        
/// </summary>

        private const int FSCTL_SET_REPARSE_POINT = 0x000900A4;

        
/**//// <summary>
        
/// Command to get the reparse point data block.
        
/// </summary>

        private const int FSCTL_GET_REPARSE_POINT = 0x000900A8;

        
/**//// <summary>
        
/// Command to delete the reparse point data base.
        
/// </summary>

        private const int FSCTL_DELETE_REPARSE_POINT = 0x000900AC;

        
/**//// <summary>
        
/// Reparse point tag used to identify mount points and junction points.
        
/// </summary>

        private const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003;

        
/**//// <summary>
        
/// This prefix indicates to NTFS that the path is to be treated as a non-interpreted
        
/// path in the virtual file system.
        
/// </summary>

        private const string NonInterpretedPathPrefix = @"\??\";

        [Flags]
        
private enum EFileAccess : uint
        
{
            GenericRead 
= 0x80000000,
            GenericWrite 
= 0x40000000,
            GenericExecute 
= 0x20000000,
            GenericAll 
= 0x10000000,
            GenericNone 
= 0x0,
        }


        [Flags]
        
private enum EFileShare : uint
        
{
            None 
= 0x00000000,
            Read 
= 0x00000001,
            Write 
= 0x00000002,
            Delete 
= 0x00000004,
        }


        
private enum ECreationDisposition : uint
        
{
            New 
= 1,
            CreateAlways 
= 2,
            OpenExisting 
= 3,
            OpenAlways 
= 4,
            TruncateExisting 
= 5,
        }


        [Flags]
        
private enum EFileAttributes : uint
        
{
            Readonly 
= 0x00000001,
            Hidden 
= 0x00000002,
            System 
= 0x00000004,
            Directory 
= 0x00000010,
            Archive 
= 0x00000020,
            Device 
= 0x00000040,
            Normal 
= 0x00000080,
            Temporary 
= 0x00000100,
            SparseFile 
= 0x00000200,
            ReparsePoint 
= 0x00000400,
            Compressed 
= 0x00000800,
            Offline 
= 0x00001000,
            NotContentIndexed 
= 0x00002000,
            Encrypted 
= 0x00004000,
            Write_Through 
= 0x80000000,
            Overlapped 
= 0x40000000,
            NoBuffering 
= 0x20000000,
            RandomAccess 
= 0x10000000,
            SequentialScan 
= 0x08000000,
            DeleteOnClose 
= 0x04000000,
            BackupSemantics 
= 0x02000000,
            PosixSemantics 
= 0x01000000,
            OpenReparsePoint 
= 0x00200000,
            OpenNoRecall 
= 0x00100000,
            FirstPipeInstance 
= 0x00080000
        }


        [StructLayout(LayoutKind.Sequential)]
        
private struct REPARSE_DATA_BUFFER
        
{
            
/**//// <summary>
            
/// Reparse point tag. Must be a Microsoft reparse point tag.
            
/// </summary>

            public uint ReparseTag;

            
/**//// <summary>
            
/// Size, in bytes, of the data after the Reserved member. This can be calculated by:
            
/// (4 * sizeof(ushort)) + SubstituteNameLength + PrintNameLength + 
            
/// (namesAreNullTerminated ? 2 * sizeof(char) : 0);
            
/// </summary>

            public ushort ReparseDataLength;

            
/**//// <summary>
            
/// Reserved; do not use. 
            
/// </summary>

            public ushort Reserved;

            
/**//// <summary>
            
/// Offset, in bytes, of the substitute name string in the PathBuffer array.
            
/// </summary>

            public ushort SubstituteNameOffset;

            
/**//// <summary>
            
/// Length, in bytes, of the substitute name string. If this string is null-terminated,
            
/// SubstituteNameLength does not include space for the null character.
            
/// </summary>

            public ushort SubstituteNameLength;

            
/**//// <summary>
            
/// Offset, in bytes, of the print name string in the PathBuffer array.
            
/// </summary>

            public ushort PrintNameOffset;

            
/**//// <summary>
            
/// Length, in bytes, of the print name string. If this string is null-terminated,
            
/// PrintNameLength does not include space for the null character. 
            
/// </summary>

            public ushort PrintNameLength;

            
/**//// <summary>
            
/// A buffer containing the unicode-encoded path string. The path string contains
            
/// the substitute name string and print name string.
            
/// </summary>

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)]
            
public byte[] PathBuffer;
        }


        [DllImport(
"kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        
private static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode,
            IntPtr InBuffer, 
int nInBufferSize,
            IntPtr OutBuffer, 
int nOutBufferSize,
            
out int pBytesReturned, IntPtr lpOverlapped);

        [DllImport(
"kernel32.dll", SetLastError = true)]
        
private static extern IntPtr CreateFile(
            
string lpFileName,
            EFileAccess dwDesiredAccess,
            EFileShare dwShareMode,
            IntPtr lpSecurityAttributes,
            ECreationDisposition dwCreationDisposition,
            EFileAttributes dwFlagsAndAttributes,
            IntPtr hTemplateFile);

        
/**//// <summary>
        
/// Creates a junction point from the specified directory to the specified target directory.
        
/// </summary>
        
/// <remarks>
        
/// Only works on NTFS.
        
/// </remarks>
        
/// <param name="junctionPoint">The junction point path</param>
        
/// <param name="targetDir">The target directory</param>
        
/// <param name="overwrite">If true overwrites an existing reparse point or empty directory</param>
        
/// <exception cref="IOException">Thrown when the junction point could not be created or when
        
/// an existing directory was found and <paramref name="overwrite" /> if false</exception>

        public static void Create(string junctionPoint, string targetDir, bool overwrite)
        
{
            targetDir 
= Path.GetFullPath(targetDir);

            
if (!Directory.Exists(targetDir))
                
throw new IOException("Target path does not exist or is not a directory.");

            
if (Directory.Exists(junctionPoint))
            
{
                
if (!overwrite)
                    
throw new IOException("Directory already exists and overwrite parameter is false.");
            }

            
else
            
{
                Directory.CreateDirectory(junctionPoint);
            }


            
using (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite))
            
{
                
byte[] targetDirBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(targetDir));

                REPARSE_DATA_BUFFER reparseDataBuffer 
= new REPARSE_DATA_BUFFER();

                reparseDataBuffer.ReparseTag 
= IO_REPARSE_TAG_MOUNT_POINT;
                reparseDataBuffer.ReparseDataLength 
= (ushort)(targetDirBytes.Length + 12);
                reparseDataBuffer.SubstituteNameOffset 
= 0;
                reparseDataBuffer.SubstituteNameLength 
= (ushort)targetDirBytes.Length;
                reparseDataBuffer.PrintNameOffset 
= (ushort)(targetDirBytes.Length + 2);
                reparseDataBuffer.PrintNameLength 
= 0;
                reparseDataBuffer.PathBuffer 
= new byte[0x3ff0];
                Array.Copy(targetDirBytes, reparseDataBuffer.PathBuffer, targetDirBytes.Length);

                
int inBufferSize = Marshal.SizeOf(reparseDataBuffer);
                IntPtr inBuffer 
= Marshal.AllocHGlobal(inBufferSize);

                
try
                
{
                    Marshal.StructureToPtr(reparseDataBuffer, inBuffer, 
false);

                    
int bytesReturned;
                    
bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT,
                        inBuffer, targetDirBytes.Length 
+ 20, IntPtr.Zero, 0out bytesReturned, IntPtr.Zero);

                    
if (!result)
                        ThrowLastWin32Error(
"Unable to create junction point.");
                }

                
finally
                
{
                    Marshal.FreeHGlobal(inBuffer);
                }

            }

        }


        
/**//// <summary>
        
/// Deletes a junction point at the specified source directory along with the directory itself.
        
/// Does nothing if the junction point does not exist.
        
/// </summary>
        
/// <remarks>
        
/// Only works on NTFS.
        
/// </remarks>
        
/// <param name="junctionPoint">The junction point path</param>

        public static void Delete(string junctionPoint)
        
{
            
if (!Directory.Exists(junctionPoint))
            
{
                
if (File.Exists(junctionPoint))
                    
throw new IOException("Path is not a junction point.");

                
return;
            }


            
using (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite))
            
{
                REPARSE_DATA_BUFFER reparseDataBuffer 
= new REPARSE_DATA_BUFFER();

                reparseDataBuffer.ReparseTag 
= IO_REPARSE_TAG_MOUNT_POINT;
                reparseDataBuffer.ReparseDataLength 
= 0;
                reparseDataBuffer.PathBuffer 
= new byte[0x3ff0];

                
int inBufferSize = Marshal.SizeOf(reparseDataBuffer);
                IntPtr inBuffer 
= Marshal.AllocHGlobal(inBufferSize);
                
try
                
{
                    Marshal.StructureToPtr(reparseDataBuffer, inBuffer, 
false);

                    
int bytesReturned;
                    
bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_DELETE_REPARSE_POINT,
                        inBuffer, 
8, IntPtr.Zero, 0out bytesReturned, IntPtr.Zero);

                    
if (!result)
                        ThrowLastWin32Error(
"Unable to delete junction point.");
                }

                
finally
                
{
                    Marshal.FreeHGlobal(inBuffer);
                }


                
try
                
{
                    Directory.Delete(junctionPoint);
                }

                
catch (IOException ex)
                
{
                    
throw new IOException("Unable to delete junction point.", ex);
                }

            }

        }


        
/**//// <summary>
        
/// Determines whether the specified path exists and refers to a junction point.
        
/// </summary>
        
/// <param name="path">The junction point path</param>
        
/// <returns>True if the specified path represents a junction point</returns>
        
/// <exception cref="IOException">Thrown if the specified path is invalid
        
/// or some other error occurs</exception>

        public static bool Exists(string path)
        
{
            
if (!Directory.Exists(path))
                
return false;

            
using (SafeFileHandle handle = OpenReparsePoint(path, EFileAccess.GenericRead))
            
{
                
string target = InternalGetTarget(handle);
                
return target != null;
            }

        }


        
/**//// <summary>
        
/// Gets the target of the specified junction point.
        
/// </summary>
        
/// <remarks>
        
/// Only works on NTFS.
        
/// </remarks>
        
/// <param name="junctionPoint">The junction point path</param>
        
/// <returns>The target of the junction point</returns>
        
/// <exception cref="IOException">Thrown when the specified path does not
        
/// exist, is invalid, is not a junction point, or some other error occurs</exception>

        public static string GetTarget(string junctionPoint)
        
{
            
using (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericNone))
            
{
                
string target = InternalGetTarget(handle);
                
if (target == null)
                    
throw new IOException("Path is not a junction point.");

                
return target;
            }

        }


        
private static string InternalGetTarget(SafeFileHandle handle)
        
{
            
int outBufferSize = Marshal.SizeOf(typeof(REPARSE_DATA_BUFFER));
            IntPtr outBuffer 
= Marshal.AllocHGlobal(outBufferSize);

            
try
            
{
                
int bytesReturned;
                
bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_GET_REPARSE_POINT,
                    IntPtr.Zero, 
0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero);

                
if (!result)
                
{
                    
int error = Marshal.GetLastWin32Error();
                    
if (error == ERROR_NOT_A_REPARSE_POINT)
                        
return null;

                    ThrowLastWin32Error(
"Unable to get information about junction point.");
                }


                REPARSE_DATA_BUFFER reparseDataBuffer 
= (REPARSE_DATA_BUFFER)
                    Marshal.PtrToStructure(outBuffer, 
typeof(REPARSE_DATA_BUFFER));

                
if (reparseDataBuffer.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
                    
return null;

                
string targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer,
                    reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength);

                
if (targetDir.StartsWith(NonInterpretedPathPrefix))
                    targetDir 
= targetDir.Substring(NonInterpretedPathPrefix.Length);

                
return targetDir;
            }

            
finally
            
{
                Marshal.FreeHGlobal(outBuffer);
            }

        }


        
private static SafeFileHandle OpenReparsePoint(string reparsePoint, EFileAccess accessMode)
        
{
            SafeFileHandle reparsePointHandle 
= new SafeFileHandle(CreateFile(reparsePoint, accessMode,
                EFileShare.Read 
| EFileShare.Write | EFileShare.Delete,
                IntPtr.Zero, ECreationDisposition.OpenExisting,
                EFileAttributes.BackupSemantics 
| EFileAttributes.OpenReparsePoint, IntPtr.Zero), true);

            
if (Marshal.GetLastWin32Error() != 0)
                ThrowLastWin32Error(
"Unable to open reparse point.");

            
return reparsePointHandle;
        }


        
private static void ThrowLastWin32Error(string message)
        
{
            
throw new IOException(message, Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()));
        }

    }

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值