tech.guitarrapc.cóm

Technical updates

PowerShell で シンボリックリンクを 使えるようにしよう

Windows は Vista以降に シンボリックリンクが利用可能になりました。

いやはやほんと遅い、やっとです。

ということで、PowerShell でシンボリックリンクを扱ってみたいですよね?扱いたいなら書けばいいんです。

目次

ジャンクション、ハードリンクと シンボリックリンクの違い

これまでも使えた、ジャンクションとハードリンクは シンボリックリンクとどのように違うのか把握しておきましょう。

PowerShell でシンボリックリンクを扱う

v4 までは標準コマンドレットでサポートされてなく、v5 では管理者権限が必要でした。 v6 (PowerShell Core) では、Windowsにおいても mklink 同様に開発者モードが有効ならユーザー権限で実行できるようになっています。

本当に PowerShell Core のほうが格段に使いやすいので検討されるといいと思います。(私は Windows PowerShell は非常に限定されたシーンでしか起動しなくなりました)

標準Cmdlet のシンボリックリンク処理

v5 で New-Item でシンボリックリンクを作成できるようになりました。これは改めて記事で紹介しました。

www.buildinsider.net

では、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処理

これは、FileInfoDirectoryInfo からふつーに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でシンボリックリンクがあった場合に、その情報を取得できます

  • Remove で、対象のシンボリックリンクを安全に削除できます
  • Set で、シンボリックリンクを作成できます

ちなみに シンボリックリンクは対象のパスが存在しなくてもリンクを作れます。

Set-SymbolicLink は、-ForceFile $true とすると、ファイルシンボリックリンクをリンク対象ファイルがなくても指定したパスに作れます。

フォルダの場合は、-ForceDirectory $true としてください。

もし両方がついていない場合は、対象パスが存在するときだけ、シンボリックリンクを作成できます。

まとめ

P/Invoke 可愛い、P/Invoke。