Windows は Vista以降に シンボリックリンクが利用可能になりました。
いやはやほんと遅い、やっとです。
ということで、PowerShell でシンボリックリンクを扱ってみたいですよね?扱いたいなら書けばいいんです。
ジャンクション、ハードリンクと シンボリックリンクの違い
これまでも使えた、ジャンクションとハードリンクは シンボリックリンクとどのように違うのか把握しておきましょう。
PowerShell でシンボリックリンクは扱いにくい
ほんとです。少なくとも v4 では。
標準Cmdlet でシンボリックリンクを扱えない
ありません。諦めてください。なんでも、v5 では New-Item でシンボリックリンクを作成できるとかなんとか。
Windows でシンボリックリンクは ln ではない。
で、ln
でいけるのか?残念、Windows では mklink
コマンドです。別にいいでしょう。
せめて、PowerShell から mklink
でよべるのかというと、まさかのNoです。
PowerShell から mklink を呼ぶには、cmd /c "mklink"
とする必要があります。
つまり、 mklink というコマンドが良く知られていますが、 cmd.exe に実装されているため、PowerShell から直接呼べません。
Remove-Item が使えない
もはやありえません。cmdでシンボリックリンクを消したいと思った時、対象がフォルダへの Reparse Point なら rmdir
、ファイルなら del
で大丈夫でした。
ところが、PowerShell で フォルダシンボリックに対して Remove-Item -Recurse
するとシンボリックリンク先のアイテムを消します。絶望です。
つまり、標準Cmdlet が シンボリックリンクに対応していません。
.NETで処理する
cmd で呼び出すとかやめましょう。 (ない)
PSCX とか使うのやめましょう。 (あれ嫌い)
.NETで簡単に書けるんだから、自分で書けばいいんですよ。
Get処理
これは、FileInfo
や DirectoryInfo
からふつーにAttributes を取得すれば問題ありません。
シンボリックリンクは、 System.IO.FileAttributes
から ReparsePoint
として取得できます。
function IsFileReparsePoint ([System.IO.FileInfo]$Path) { Write-Verbose ('File attribute detected as ReparsePoint') $fileAttributes = [System.IO.FileAttributes]::Archive, [System.IO.FileAttributes]::ReparsePoint -join ', ' $attribute = [System.IO.File]::GetAttributes($Path) $result = $attribute -eq $fileAttributes if ($result) { Write-Verbose ('Attribute detected as ReparsePoint. : {0}' -f $attribute) return $result } else { Write-Verbose ('Attribute detected as NOT ReparsePoint. : {0}' -f $attribute) return $result } }
Remove処理
幸いにして、.NET Framework では、シンボリックリンクの削除は System.IO.File の Delete メソッド や System.IO.Directory の Deleteメソッドでふつーに処理できます。
The behavior of this method differs slightly when deleting a directory that contains a reparse point, such as a symbolic link or a mount point. If the reparse point is a directory, such as a mount point, it is unmounted and the mount point is deleted. This method does not recurse through the reparse point. If the reparse point is a symbolic link to a file, the reparse point is deleted and not the target of the symbolic link.
ということで、Removeは問題ありませんね。
function RemoveFileReparsePoint ([System.IO.FileInfo]$Path) { [System.IO.File]::Delete($Path.FullName) } function RemoveDirectoryReparsePoint ([System.IO.DirectoryInfo]$Path) { [System.IO.Directory]::Delete($Path.FullName) }
Set処理
シンボリックリックを作る処理だけは、 P/Invoke が必要なのでしれっとやります。
internal static class Win32 { [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, SymLinkFlag dwFlags) internal enum SymLinkFlag { File = 0, Directory = 1 } } public static void CreateSymLink(string name, string target, bool isDirectory = false) { if (!Win32.CreateSymbolicLink(name, target, isDirectory ? Win32.SymLinkFlag.Directory : Win32.SymLinkFlag.File)) { throw new System.ComponentModel.Win32Exception() } }
これで必要な処理は集まりました。あとは書くだけです。
コード
GitHub で公開しておきます。valentia にも組み込まれているのでぜひどうぞ。
それぞれのコードは Gist でも置いておきましょう。
Get-SymbolicLink
Set-SymbolicLink
Remove-SymbolicLink
使い方
は、いらないですよね?
シンボリックリンクは、管理者で実行してください (ユーザー権限では実行できません)
Getでシンボリックリンクがあった場合に、その情報を取得できます。
- Remove で、対象のシンボリックリンクを安全に削除できます。
- Set で、シンボリックリンクを作成できます。
ちなみに シンボリックリンクは対象のパスが存在しなくてもリンクを作れます。
Set-SymbolicLink
は、-ForceFile $true
とすると、ファイルシンボリックリンクをリンク対象ファイルがなくても指定したパスに作れます。
フォルダの場合は、-ForceDirectory $true
としてください。
もし両方がついていない場合は、対象パスが存在するときだけ、シンボリックリンクを作成できます。
まとめ
P/Invoke 可愛い、P/Invoke。