TL;DR
.NET 6 でDirectory
クラスにCreateSymbolikLink
メソッドが追加されたが、5以前では使えないため Win32API を使って操作する。
注意する点は実行に管理者権限が必要なため、Unity などから利用するのは難しいかもしれない。
.NET 6 以降は以下を参照。
目次
検証環境
- LINQPad
- .NET 3.1
- .NET Framework 4.7.2
シンボリックリンクの操作
作成する
CreateSymbolicLink
を使用する。
まずは定義部分。
[DllImport("kernel32.dll", SetLastError = true)] static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, SymbolicLink dwFlags); enum SymbolicLink { File = 0, Directory = 1 }
続いて使い方。
作成に成功すればtrue
、失敗すればfalse
が返ってくる。
void Main() { var src = @"C:\Workspace\SymLinkWork\folder1"; var dest = @"C:\Workspace\SymLinkWork\folder2"; var result = CreateSymbolicLink(dest, src, SymbolicLink.Directory); result.Dump(); }
注意が必要なのはコードの実行には管理者権限が必要なことである。LINQPadで実行する場合もLINQPad自体を管理者権限で実行しないと失敗する。 現在管理者権限で実行されてるかどうかは、以下のようにして取れる。
public static bool IsRunningAsAdmin() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { WindowsIdentity identity = WindowsIdentity.GetCurrent(); WindowsPrincipal principal = new WindowsPrincipal(identity); return principal.IsInRole(WindowsBuiltInRole.Administrator); } //for mac and linux return true; }
リンク先を取得する
GetFinalPathNameByHandle
を使用する。
まずは定義部分。
[DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode, SetLastError = true)] private static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr securityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile); [DllImport("kernel32.dll", EntryPoint = "GetFinalPathNameByHandleW", CharSet = CharSet.Unicode, SetLastError = true)] private static extern int GetFinalPathNameByHandle([In] SafeFileHandle hFile, [Out] StringBuilder lpszFilePath, [In] int cchFilePath, [In] int dwFlags); private const int CREATION_DISPOSITION_OPEN_EXISTING = 3; private const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; public static string ResolveLinkTarget(string path) { if (!Directory.Exists(path) && !File.Exists(path)) { throw new IOException("Path not found"); } SafeFileHandle directoryHandle = CreateFile(path, 0, 2, IntPtr.Zero, CREATION_DISPOSITION_OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero); //Handle file / folder if (directoryHandle.IsInvalid) { throw new Win32Exception(Marshal.GetLastWin32Error()); } StringBuilder result = new StringBuilder(512); int mResult = GetFinalPathNameByHandle(directoryHandle, result, result.Capacity, 0); if (mResult < 0) { throw new Win32Exception(Marshal.GetLastWin32Error()); } if (result.Length >= 4 && result[0] == '\\' && result[1] == '\\' && result[2] == '?' && result[3] == '\\') { return result.ToString().Substring(4); // "\\?\" remove } return result.ToString(); }
続いて使い方。
var src = @"C:\Workspace\SymLinkWork\folder1"; var dest2 = @"C:\Workspace\SymLinkWork\folder2"; var dest3 = @"C:\Workspace\SymLinkWork\folder3"; CreateSymbolicLink(dest2, src, SymbolicLink.Directory); CreateSymbolicLink(dest3, dest2, SymbolicLink.Directory); ResolveLinkTarget(dest2).Dump(); ResolveLinkTarget(dest3).Dump();
実行結果。
FinalPath
の名前の通り、リンク先がシンボリックリンクだった場合は最後のリンク先が返ってくる。つまり今回はどちらもfolder1になる。
C:\Workspace\SymLinkWork\folder1 C:\Workspace\SymLinkWork\folder1
削除する
ディレクトリを消すだけ。
Directory.Delete(dest);
指定したディレクトリ配下のシンボリックリンクを確認にする
var dirs = Directory.EnumerateDirectories(root, "*", SearchOption.AllDirectories); dirs.Select(path => { var link = ResolveLinkTarget(path); return new { Path = path, SymLink = path != link, Link = link }; }) .Dump("LinkList");
検証に使用したコード全文(LINQPad)
void Main() { var root = @"C:\Workspace\SymLinkWork"; var src = @"C:\Workspace\SymLinkWork\folder1"; var dest2 = @"C:\Workspace\SymLinkWork\folder2"; var dest3 = @"C:\Workspace\SymLinkWork\folder3"; IsRunningAsAdmin().Dump("Admin"); CreateSymbolicLink(dest2, src, SymbolicLink.Directory).Dump("Create"); CreateSymbolicLink(dest3, dest2, SymbolicLink.Directory).Dump("Create"); ResolveLinkTarget(dest2); ResolveLinkTarget(dest3); GetSymLinkList(root); DeleteSymlink(dest2); DeleteSymlink(dest3); GetSymLinkList(root); } void DeleteSymlink(string path) { Directory.Delete(path); } void GetSymLinkList(string root) { var dirs = Directory.EnumerateDirectories(root, "*", SearchOption.AllDirectories); dirs.Select(path => { var link = ResolveLinkTarget(path); return new { Path = path, SymLink = path != link, Link = link }; }) .Dump("LinkList"); } // Create [DllImport("kernel32.dll", SetLastError = true)] static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, SymbolicLink dwFlags); enum SymbolicLink { File = 0, Directory = 1 } // ResolveLinkTarget [DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode, SetLastError = true)] private static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr securityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile); [DllImport("kernel32.dll", EntryPoint = "GetFinalPathNameByHandleW", CharSet = CharSet.Unicode, SetLastError = true)] private static extern int GetFinalPathNameByHandle([In] SafeFileHandle hFile, [Out] StringBuilder lpszFilePath, [In] int cchFilePath, [In] int dwFlags); private const int CREATION_DISPOSITION_OPEN_EXISTING = 3; private const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; public static string ResolveLinkTarget(string path) { if (!Directory.Exists(path) && !File.Exists(path)) { throw new IOException("Path not found"); } SafeFileHandle directoryHandle = CreateFile(path, 0, 2, IntPtr.Zero, CREATION_DISPOSITION_OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero); //Handle file / folder if (directoryHandle.IsInvalid) { throw new Win32Exception(Marshal.GetLastWin32Error()); } StringBuilder result = new StringBuilder(512); int mResult = GetFinalPathNameByHandle(directoryHandle, result, result.Capacity, 0); if (mResult < 0) { throw new Win32Exception(Marshal.GetLastWin32Error()); } if (result.Length >= 4 && result[0] == '\\' && result[1] == '\\' && result[2] == '?' && result[3] == '\\') { return result.ToString().Substring(4); // "\\?\" remove } return result.ToString(); } // Check Admin public static bool IsRunningAsAdmin() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { WindowsIdentity identity = WindowsIdentity.GetCurrent(); WindowsPrincipal principal = new WindowsPrincipal(identity); return principal.IsInRole(WindowsBuiltInRole.Administrator); } //for mac and linux return true; }