#requires -RunAsAdministrator
# Administrative privileges typically needed to create symbolic links
filter Test-SymLink {
if (fsutil.exe reparsepoint query $_ | Select-String '^Tag value: Symbolic Link$') {return $_}
}
function Get-ReparsePointTaregetDirectory {
# reference: http://techibee.com/powershell/read-target-folder-of-a-symlink-using-powershell/1916
[CmdletBinding()]
param(
[string]$Path
)
$Definition = @'
private const int FILE_SHARE_READ = 1;
private const int FILE_SHARE_WRITE = 2;
private const int CREATION_DISPOSITION_OPEN_EXISTING = 3;
private const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
[DllImport("kernel32.dll", EntryPoint = "GetFinalPathNameByHandleW", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetFinalPathNameByHandle(IntPtr handle, [In, Out] StringBuilder path, int bufLen, int flags);
[DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess,
int dwShareMode, IntPtr SecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
public static string GetSymbolicLinkTarget(System.IO.DirectoryInfo symlink)
{
SafeFileHandle directoryHandle = CreateFile(symlink.FullName, 0, 2, System.IntPtr.Zero, CREATION_DISPOSITION_OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, System.IntPtr.Zero);
if(directoryHandle.IsInvalid)
throw new Win32Exception(Marshal.GetLastWin32Error());
StringBuilder path = new StringBuilder(512);
int size = GetFinalPathNameByHandle(directoryHandle.DangerousGetHandle(), path, path.Capacity, 0);
if (size<0) throw new Win32Exception(Marshal.GetLastWin32Error()); // The remarks section of GetFinalPathNameByHandle mentions the return being prefixed with "\\?\" // More information about "\\?\" here -> http://msdn.microsoft.com/en-us/library/aa365247(v=VS.85).aspx
if (path[0] == '\\' && path[1] == '\\' && path[2] == '?' && path[3] == '\\') return path.ToString().Substring(4);
else
return path.ToString();
}
'@
Add-Type -MemberDefinition $Definition -Name Win32 -NameSpace System -UsingNamespace System.Text,Microsoft.Win32.SafeHandles,System.ComponentModel
[System.Win32]::GetSymbolicLinkTarget($Path)
}
function New-SymLink {
# https://gallery.technet.microsoft.com/scriptcenter/New-SymLink-60d2531e
<#
.SYNOPSIS
Creates a Symbolic link to a file or directory
.DESCRIPTION
Creates a Symbolic link to a file or directory as an alternative to mklink.exe
.PARAMETER Path
Name of the path that you will reference with a symbolic link.
.PARAMETER SymName
Name of the symbolic link to create. Can be a full path/unc or just the name.
If only a name is given, the symbolic link will be created on the current directory that the
function is being run on.
.PARAMETER File
Create a file symbolic link
.PARAMETER Directory
Create a directory symbolic link
.NOTES
Name: New-SymLink
Author: Boe Prox
Created: 15 Jul 2013
.EXAMPLE
New-SymLink -Path "C:\users\admin\downloads" -SymName "C:\users\admin\desktop\downloads" -Directory
SymLink Target Type
------- ------ ----
C:\Users\admin\Desktop\Downloads C:\Users\admin\Downloads Directory
Description
-----------
Creates a symbolic link to downloads folder that resides on C:\users\admin\desktop.
.EXAMPLE
New-SymLink -Path "C:\users\admin\downloads\document.txt" -SymName "SomeDocument" -File
SymLink Target Type
------- ------ ----
C:\users\admin\desktop\SomeDocument C:\users\admin\downloads\document.txt File
Description
-----------
Creates a symbolic link to document.txt file under the current directory called SomeDocument.
#>
[cmdletbinding(
DefaultParameterSetName = 'Directory',
SupportsShouldProcess=$True
)]
Param (
[parameter(Position=0,ParameterSetName='Directory',ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True,Mandatory=$True)]
[parameter(Position=0,ParameterSetName='File',ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True,Mandatory=$True)]
[ValidateScript({
If (Test-Path $_) {$True} Else {
Throw "`'$_`' doesn't exist!"
}
})]
[string]$Path,
[parameter(Position=1,ParameterSetName='Directory')]
[parameter(Position=1,ParameterSetName='File')]
[string]$SymName,
[parameter(Position=2,ParameterSetName='File')]
[switch]$File,
[parameter(Position=2,ParameterSetName='Directory')]
[switch]$Directory
)
Begin {
Try {
$null = [mklink.symlink]
} Catch {
Add-Type @"
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);
}
}
"@
}
}
Process {
#Assume target Symlink is on current directory if not giving full path or UNC
If ($SymName -notmatch "^(?:[a-z]:\\)|(?:\\\\\w+\\[a-z]\$)") {
$SymName = "{0}\{1}" -f $pwd,$SymName
}
$Flag = @{
File = 0
Directory = 1
}
If ($PScmdlet.ShouldProcess($Path,'Create Symbolic Link')) {
Try {
$return = [mklink.symlink]::CreateSymbolicLink($SymName,$Path,$Flag[$PScmdlet.ParameterSetName])
If ($return) {
$object = New-Object PSObject -Property @{
SymLink = $SymName
Target = $Path
Type = $PScmdlet.ParameterSetName
}
$object.pstypenames.insert(0,'System.File.SymbolicLink')
$object
} Else {
Throw "Unable to create symbolic link!"
}
} Catch {
Write-warning ("{0}: {1}" -f $path,$_.Exception.Message)
}
}
}
}
Get-ChildItem | Test-SymLink |
%{
$target = Get-ReparsePointTaregetDirectory $_.FullName
[System.IO.Directory]::Delete($_.FullName)
if ($?) {New-SymLink -Path ($target -replace '^.:') -SymName $_.FullName -Directory | select `
@{n='Symlink Location';e={$_.SymLink}}, @{n='New Target';e={$_.Target}}
}
}