ReparseTools.psm1 a few functions to manage reparse points

Powershell doesn’t have native functionality to manage reparse points. You can add classes for relevant NET types that will allow you to easily create symlinks and hardlinks and to delete them. One thing to note about removing reparse points is that most admins will be used to just deleting them “del c:\mount\reparse”, for example. If you try that in powershell (with Remove-Item or its alias del) it will complain until you use the -Force and -Recurse parameters. Then it will remove all items under the reparse point and finally remove the reparse point. That behavior is somewhere between annoying and disastrous. To get around it, you can call NET classes directly – if you use “[System.IO.Directory]::Delete($Path)”, it will remove the reparse point without affecting anything else.

Remove-ReparsePoint will check to verify that the target is a reparse point, then will check to see if it’s a file or directory and use the appropriate NET class to delete it.

You might also note that there are a couple of Test-Path cmdlets that specify LiteralPath and ErrorAction. That fits a specific case where you might be trying to mount a volume shadow copy. Test-Path will only recognize those paths if they’re passed as literal, and it will improperly throw an error message telling you not to use them even though it returns an accurate result (it should throw a warning, if anything).

New-ReparsePoint will create a new reparse point. You can even use GLOBALROOT children such as volume shadow copies (protip, start with “Get-WmiObject win32_shadowcopy”, but another post will come later).  It will try to determine if the target is a file or directory and create the appropriate reparse point (symlink or hardlink, respectively).  If it can’t figure it out correctly, you can use -IsFile or -IsDirectory to force the desired behavior.

So here is ReparseTools.psm1:

#reparsetools.ps1

# First add a type so it stays available for this instance and we don't have to keep adding it.

Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;

namespace mklink
{
    public class symlink
    {
        [DllImport("kernel32.dll")]
        public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int dwFlags);
        [DllImport("kernel32.dll")]
        public static extern uint GetLastError();
    }
}
'@

Function New-ReparsePoint() {
     <#   
    .SYNOPSIS   
        Creates a reparse point to the specified target.

    .DESCRIPTION   
        Creates a reparse point to the specified target.

    .PARAMETER Path
        Path to the reparse point to remove.  

    .NOTES   
        Author: Jordan Mills
        Version: 1.0

    .EXAMPLE 
        Remove-SymbolicLink -Path "E:\directory\mount"
    #>    
    [cmdletbinding(DefaultParameterSetName="default")]
    Param (
        [parameter(
            ParameterSetName="default",
            Position=0,
    		Mandatory=$true,
            ValueFromPipeLine=$True,
    		ValueFromPipelineByPropertyName=$True
        )]
        [parameter(
            ParameterSetName="file",
            Position=0,
    		Mandatory=$true,
            ValueFromPipeLine=$True,
    		ValueFromPipelineByPropertyName=$True
        )]
        [parameter(
            ParameterSetName="directory",
            Position=0,
    		Mandatory=$true,
            ValueFromPipeLine=$True,
    		ValueFromPipelineByPropertyName=$True
        )]
        [Alias("Path","FileName","Directory")]
        [string[]]$Name,
        [parameter(
            ParameterSetName="default",
            Position=1,
    		Mandatory=$true,
    		ValueFromPipelineByPropertyName=$True
        )]
        [parameter(
            ParameterSetName="file",
            Position=1,
    		Mandatory=$true,
    		ValueFromPipelineByPropertyName=$True
        )]
        [parameter(
            ParameterSetName="directory",
            Position=1,
    		Mandatory=$true,
    		ValueFromPipelineByPropertyName=$True
        )]
        [string]$TargetPath,
        [parameter(
            ParameterSetName="file",
            Position=2,
    		Mandatory=$false,
    		ValueFromPipelineByPropertyName=$True
        )] 
        [switch]$IsFile,
        [parameter(
            ParameterSetName="directory",
            Position=2,
    		Mandatory=$false,
    		ValueFromPipelineByPropertyName=$True
        )]
        [switch]$IsDirectory
    )

    If ($IsFile -or $IsDirectory) {
        If($IsFile) {
            $result = [mklink.symlink]::CreateSymbolicLink($Name,$TargetPath,0)
        } 
        Else {
            If($IsDirectory) {
                $result = [mklink.symlink]::CreateSymbolicLink($Name,$TargetPath,1)
            } 
            Else {
                Write-Error -Message "Conflicting path type parameters.  This should not happen."
                Break;
            }
        }
    } 
    Else {
        If (Test-Path -LiteralPath $TargetPath -PathType Leaf -ErrorAction SilentlyContinue) {
            $result = [mklink.symlink]::CreateSymbolicLink($Name,$TargetPath,0)
        } 
        Else {
            If (Test-Path -LiteralPath $TargetPath -PathType Container -ErrorAction SilentlyContinue) {
                $result = [mklink.symlink]::CreateSymbolicLink($Name,$TargetPath,1)
            } 
            Else {
                Write-Error -Message "Unable to determine path type of TargetPath.  Use -IsFile or -IsDirectory."
                Break;
            }
        }
    }

    If ($result) { 
        Get-Item $Name
    } Else {
        Write-Error -Message "Error creating symbolic link" -Category WriteError #-ErrorId $([mklink.symlink]::GetLastError())
    }
}

Function Remove-ReparsePoint {
     <#   
    .SYNOPSIS   
        Removes a file or directory that is a reparse point (symlink or hardlink) without removing all child objects.

    .DESCRIPTION   
        Removes a file or directory that is a reparse point (symlink or hardlink) without removing all child objects.

    .PARAMETER Path
        Path to the reparse point to remove.  

    .NOTES   
        Author: Jordan Mills
        Version: 1.0

    .EXAMPLE 
        Remove-SymbolicLink -Path "E:\directory\mount"
    #>    
    [cmdletbinding()]
    Param (
        [parameter(
            Position=0,
    		Mandatory=$true,
            ValueFromPipeLine=$True,
    		ValueFromPipelineByPropertyName=$True
        )]
        [Alias("FullName","Name","FileName","Directory")]
        [ValidateScript({Test-Path $_})] 
        [string[]]$Path
    )

    $Path |
    Get-Item |
    ForEach-Object {
        $Item = $_;
        Switch ($Item.Attributes -band ([IO.FileAttributes]::ReparsePoint -bor [IO.FileAttributes]::Directory)) {
            ([IO.FileAttributes]::ReparsePoint -bor [IO.FileAttributes]::Directory) {
                # Is reparse directory / symlink
                If ($whatif) {
                    Write-Host "What if: Performing the operation `"Delete Directory`" on target `"$($_.FullName)`""
                } Else {
                    [System.IO.Directory]::Delete($Item.FullName);
                    Break;
                }
            }
            ([IO.FileAttributes]::ReparsePoint -bor 0) {
                # Is reparse file / hardlink
                If ($whatif) {
                    Write-Host "What if: Performing the operation `"Delete File`" on target `"$($_.FullName)`""
                } Else {
                    [System.IO.File]::Delete($Item.FullName);
                    Break;
                }
            }
            default {
                Write-Error "$Item is not a reparse point."
            }
        }
    }    
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s