Skip to content
Binary file modified CreateSymLink.reg
Binary file not shown.
42 changes: 35 additions & 7 deletions CreateSymLinkFile.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,29 @@ function Test-Admin {
return $currentUser.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Confirm-Overwrite {
param ($linkPath)
Add-Type -AssemblyName PresentationFramework
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?


$message = "A file with the name '$(Split-Path $linkPath -Leaf)' already exists. Overwrite?"
$result = [System.Windows.MessageBox]::Show(
$message,
"Confirm Overwrite",
[System.Windows.MessageBoxButton]::YesNoCancel,
[System.Windows.MessageBoxImage]::Warning
)
return $result
}

# Function to escape wildcard characters in a path
function Escape-WildcardCharacters {
param ([string]$path)
$escapedPath = $path -replace '([*?\[\]])', '``$1'
return $escapedPath
}

# Save the initial working directory
# $initialWorkingDirectory = [System.IO.Directory]::GetCurrentDirectory()
$initialWorkingDirectory = (Get-Location).Path

if (-not (Test-Admin)) {
Expand All @@ -14,6 +36,7 @@ if (-not (Test-Admin)) {
Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" `"$initialWorkingDirectory`"" -Verb RunAs
Exit # Exit the non-elevated instance
}
$initialWorkingDirectory = Escape-WildcardCharacters -path $initialWorkingDirectory

# Retrieve the working directory from arguments
$originalWorkingDirectory = $args[0]
Expand All @@ -40,21 +63,26 @@ if ($selectedPath) {
$linkName = Split-Path -Leaf $selectedPath
$linkPath = Join-Path -Path $currentDir -ChildPath $linkName

# Escape wildcard characters in the paths
$escapedLinkPath = Escape-WildcardCharacters -path $linkPath
$selectedPath = Escape-WildcardCharacters -path $selectedPath

Write-Output "Initial Working Directory: $initialWorkingDirectory"
Write-Output "Original Working Directory: $originalWorkingDirectory"
Write-Output "Current Directory: $currentDir"
Write-Output "Selected Path: $selectedPath"
Write-Output "Link Path: $linkPath"
Write-Output "Escaped Link Path: $escapedLinkPath"

# Check if selected item is a file, and then check if we already have a file with that name and offer to overwrite if we do
if (Test-Path -Path $selectedPath -PathType Leaf) {
if (Test-Path -Path $linkPath) {
if (Test-Path -Path $selectedPath -PathType Leaf) {
if (Test-Path -Path $escapedLinkPath) {
$choice = Confirm-Overwrite -linkPath $linkPath
if ($choice -eq 'Yes') {
# Remove the existing symlink
Remove-Item -Path $linkPath -Force
Remove-Item -Path $escapedLinkPath -Force
# Create a new file symbolic link
New-Item -ItemType SymbolicLink -Path $linkPath -Target $selectedPath -ErrorAction Stop
Write-Output "Overwritten file symbolic link: $linkPath -> $selectedPath"
Write-Output "Overwritten file symbolic link: $escapedLinkPath -> $selectedPath"
} elseif ($choice -eq 'No') {
Write-Output "Symbolic link (or file/folder with that name) already existed! Operation canceled by user by not overwriting."
} else {
Expand All @@ -63,7 +91,7 @@ if ($selectedPath) {
} else {
# Create a new file symbolic link
New-Item -ItemType SymbolicLink -Path $linkPath -Target $selectedPath -ErrorAction Stop
Write-Output "Created file symbolic link: $linkPath -> $selectedPath"
Write-Output "Created file symbolic link: $escapedLinkPath -> $selectedPath"
}
} else {
Write-Output "The selected path is not a file."
Expand All @@ -75,4 +103,4 @@ if ($selectedPath) {
# Keep the window open
#Read-Host -Prompt "Press Enter to exit"
Write-Output "`n`nThis window will autoclose soon"
Start-Sleep -Seconds 1
Start-Sleep -Seconds 1
132 changes: 114 additions & 18 deletions CreateSymLinkFolder.ps1
Original file line number Diff line number Diff line change
@@ -1,17 +1,102 @@
$source = @'
using System;
using System.Reflection;
using System.Windows.Forms;
public class MyFolderSelector
{
public string DefaultPath, Title, Message;
public MyFolderSelector(string defaultPath = "MyComputer", string title = "Select a folder", string message = "")
{
DefaultPath = defaultPath;
Title = title;
Message = message;
}
public string GetPath()
{
// filter for which methods we want to get
BindingFlags c_flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
// get the assembly of the file dialog so we can access its internal types
var fileDialogAssembly = typeof(FileDialog).Assembly;
// get the iFile so we can use its functions to put our settings and show our dialog
var iFileDialog = fileDialogAssembly.GetType("System.Windows.Forms.FileDialogNative+IFileDialog");
var openFileDialog = new OpenFileDialog
{ // our dialog options
AddExtension = false,
CheckFileExists = false,
DereferenceLinks = true,
Filter = "Folders|\n",
InitialDirectory = DefaultPath,
Multiselect = false,
Title = Title
};
// create vista dialog
var ourDialog = (typeof(OpenFileDialog).GetMethod("CreateVistaDialog", c_flags)).Invoke(openFileDialog, new object[] { });
// attatch our options
typeof(OpenFileDialog).GetMethod("OnBeforeVistaDialog", c_flags).Invoke(openFileDialog, new[] { ourDialog });
iFileDialog.GetMethod("SetOptions", c_flags).Invoke(ourDialog, new object[] { (uint)(0x20000840 | 0x00000020 /*defaultOptions | folderPickerOption*/) });
// generate update event constructor so the file name updates after selection
var vistaDialogEventsConstructorInfo = fileDialogAssembly
.GetType("System.Windows.Forms.FileDialog+VistaDialogEvents")
.GetConstructor(c_flags, null, new[] { typeof(FileDialog) }, null);
var adviseParametersWithOutputConnectionToken = new[] { vistaDialogEventsConstructorInfo.Invoke(new object[] { openFileDialog }), 0U };
// add the event to the dialog
iFileDialog.GetMethod("Advise").Invoke(ourDialog, adviseParametersWithOutputConnectionToken);
// show the dialog to user and get response
int retVal = (int)iFileDialog.GetMethod("Show").Invoke(ourDialog, new object[] { IntPtr.Zero });
// returns the file name if the user made a selection
return retVal == 0 ? openFileDialog.FileName : "";
}
}
'@
# Function to check for elevated privileges
function Test-Admin {
$currentUser = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
return $currentUser.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Confirm-Overwrite {
param ($linkPath)
Add-Type -AssemblyName PresentationFramework

# Presume that it's not a folder
$isDirectory = $false

if (-not [string]::IsNullOrWhiteSpace($linkPath)) {
# If the input box isn't empty, check if it's a valid path
if (Test-Path $linkPath -PathType Leaf) {
# if the path points to a file, it is not a folder
$isDirectory = $false
} elseif (Test-Path $linkPath -PathType Container) {
# if the path points to a folder, it is a folder
$isDirectory = $true
}
}

$message = "A $(if ($isDirectory) { 'folder' } else { 'file' }) with the name '$(Split-Path $linkPath -Leaf)' already exists. Overwrite?"
$result = [System.Windows.MessageBox]::Show(
$message,
"Confirm Overwrite",
[System.Windows.MessageBoxButton]::YesNoCancel,
[System.Windows.MessageBoxImage]::Warning
)
return $result
}

function Escape-WildcardCharacters {
param ([string]$path)
$escapedPath = $path -replace '([*?\[\]])', '``$1'
return $escapedPath
}

# Save the initial working directory
$initialWorkingDirectory = (Get-Location).Path
$initialWorkingDirectory = [System.IO.Directory]::GetCurrentDirectory()
$escapedInitialWorkingDirectory = Escape-WildcardCharacters -path $initialWorkingDirectory

if (-not (Test-Admin)) {
Write-Output "Please run this script as an administrator. Let's try rerunning automatically..."

# Re-run the script with elevated privileges, passing the initial working directory as an argument
Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" `"$initialWorkingDirectory`"" -Verb RunAs
Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" `"$escapedInitialWorkingDirectory`"" -Verb RunAs
Exit # Exit the non-elevated instance
}

Expand All @@ -21,14 +106,19 @@ Set-Location -Path $originalWorkingDirectory

# Function to show folder picker dialog
function Show-FolderPickerDialog {
Add-Type -AssemblyName System.Windows.Forms
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
$dialog.Description = "Select a folder to create a symbolic link"
$dialog.SelectedPath = [System.IO.Directory]::GetCurrentDirectory()
if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
return $dialog.SelectedPath
} else {

Add-Type -Language CSharp -TypeDefinition $source -ReferencedAssemblies ("System.Windows.Forms")

$path = $originalWorkingDirectory
$title = "Select Folder"
$description = "Select a folder to create a symbolic link"
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Select the folder to create a symbolic link to"


$out = [MyFolderSelector]::new($path, $title, $description).GetPath()

if ($out -eq "") {
return $null
} else {
return $out
}
}

Expand All @@ -40,29 +130,35 @@ if ($selectedPath) {
$linkName = Split-Path -Leaf $selectedPath
$linkPath = Join-Path -Path $currentDir -ChildPath $linkName

# Escape wildcard characters in the paths
$escapedLinkPath = Escape-WildcardCharacters -path $linkPath
$escapedSelectedPath = Escape-WildcardCharacters -path $selectedPath

Write-Output "Original Working Directory: $originalWorkingDirectory"
Write-Output "Current Directory: $currentDir"
Write-Output "Selected Path: $selectedPath"
Write-Output "Escaped Selected Path: $escapedSelectedPath"
Write-Output "Link Path: $linkPath"
Write-Output "Escaped Link Path: $escapedLinkPath"

if (Test-Path -Path $selectedPath -PathType Container) {
if (Test-Path -Path $linkPath) {
$choice = Confirm-Overwrite -linkPath $linkPath
if (Test-Path -Path $escapedSelectedPath -PathType Container) {
if (Test-Path -Path $escapedLinkPath) {
Comment on lines +144 to +145
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so, since the escape***Path variables effectively supersede the original ***Path variables, why not just update the originals to reduce the amount of possible confusion on future maintainers?

a variable is useless if it's going to be made useless on purpose 4 lines after it's declared.

$choice = Confirm-Overwrite -linkPath $escapedLinkPath
if ($choice -eq 'Yes') {
# Remove the existing symlink
Remove-Item -Path $linkPath -Force
Remove-Item -Path $escapedLinkPath -Force
# Create a new directory symbolic link
New-Item -ItemType SymbolicLink -Path $linkPath -Target $selectedPath -ErrorAction Stop
Write-Output "Overwritten directory symbolic link: $linkPath -> $selectedPath"
New-Item -ItemType SymbolicLink -Path $linkPath -Target $escapedSelectedPath -ErrorAction Stop
Write-Output "Overwritten directory symbolic link: $escapedLinkPath -> $escapedSelectedPath"
} elseif ($choice -eq 'No') {
Write-Output "Symbolic link (or file/folder with that name) already existed! Operation canceled by user by not overwriting."
} else {
Write-Output "Operation canceled by user."
}
} else {
# Create a new directory symbolic link
New-Item -ItemType SymbolicLink -Path $linkPath -Target $selectedPath -ErrorAction Stop
Write-Output "Created directory symbolic link: $linkPath -> $selectedPath"
New-Item -ItemType SymbolicLink -Path $linkPath -Target $escapedSelectedPath -ErrorAction Stop
Write-Output "Created directory symbolic link: $escapedLinkPath -> $escapedSelectedPath"
}
} else {
Write-Output "The selected path is not a folder."
Expand All @@ -74,4 +170,4 @@ if ($selectedPath) {
# Keep the window open
#Read-Host -Prompt "Press Enter to exit"
Write-Output "`n`nThis window will autoclose soon"
Start-Sleep -Seconds 1
Start-Sleep -Seconds 1