WindowsはVista以降にシンボリックリンクが利用可能になりました。
いやはやほんと遅い、やっとです。
ということで、PowerShellでシンボリックリンクを扱ってみたいですよね? 扱いたいなら書けばいいんです。
ジャンクション、ハードリンクと シンボリックリンクの違い
これまでも使えた、ジャンクションとハードリンクはシンボリックリンクとどのように違うのか把握しておきましょう。
PowerShell でシンボリックリンクを扱う
v4までは標準コマンドレットでサポートされてなく、v5 では管理者権限が必要でした。 v6 (PowerShell Core) では、Windowsにおいてもmklink 同様に開発者モードが有効ならユーザー権限で実行できるようになっています。
本当にPowerShell Coreのほうが格段に使いやすいので検討されるといいでしょう。私は非常に限定されたシーンでしかWindows PowerShellを起動しなくなりました。
標準Cmdlet のシンボリックリンク処理
v5からNew-Itemでシンボリックリンクを作成できるようになりました。これは改めて記事で紹介しました。
では、PowerShell 6.2 でユーザー権限でシンボリックリンクを作ってみましょう。~/,gitconfig へシンボリックリンクした ~/.gitconfig.localファイルを作るならこうです。
New-Item -Type SymbolicLink ~\.gitconfig.local -Value .gitconfig
シンボリックリンクの削除がRemove-Itemなので、実ファイルとリンクファイルを間違えないように気を付けてください。lnのようにunlinkがあると安全ですがそうではない。
Windows でシンボリックリンクは ln ではない。
で、lnでいけるのか? 残念、Windowsではmklinkコマンドです。別にいいでしょう。
せめて、PowerShellからmklinkでよべるのかというと、まさかのNoです。
PowerShellからmklinkを呼ぶには、cmd /c "mklink"とします。
つまり、 mklinkというコマンドが良く知られていますが、 cmd.exeに実装されているため、PowerShellから直接呼べません。
Remove-Item が使えない
v4において、シンボリックリンク処理がないのはともかく削除のハンドルもできないのは苦しいです。cmdでシンボリックリンクを消したいと思った時、対象がフォルダへのReparse Pointならrmdir、ファイルならdelで大丈夫でした。
ところが、PowerShellでフォルダシンボリックに対してRemove-Item -Recurseするとシンボリックリンク先のアイテムを消します。操作ミスを容易に誘発するので、これは結構いやな制約です。
つまり、v4では標準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。