tech.guitarrapc.cóm

PowerShell Technical Update and Features

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

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処理

これは、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-SymbolicLink

Set-SymbolicLink

Remove-SymbolicLink

使い方

は、いらないですよね?

  • シンボリックリンクは、管理者で実行してください (ユーザー権限では実行できません)

  • Getでシンボリックリンクがあった場合に、その情報を取得できます。

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

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

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

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

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

まとめ

P/Invoke 可愛い、P/Invoke。