diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..9d36e22
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,13 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(git tag v1.1.1.15 fd3dc4312b4e1d6e6dc6998f9dcf15a35f006157)",
+ "Bash(git cliff --config cliff.toml --output CHANGELOG_NEW.md)",
+ "Bash(env)",
+ "Bash(if not exist .signpath mkdir .signpath)",
+ "Bash(powershell -Command:*)"
+ ],
+ "deny": [],
+ "ask": []
+ }
+}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..641ffbf
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,21 @@
+name: CI
+
+on:
+ pull_request:
+ branches: [master]
+
+jobs:
+ build:
+ runs-on: windows-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '8.0.x'
+
+ - name: Build
+ run: dotnet build Symlinker/Symlinker.csproj --configuration Release
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..459bb7f
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,131 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+Symlinker is a Windows Forms application (.NET 8) that provides a GUI for creating symbolic links, hard links, and directory junctions on Windows. It wraps the Windows `mklink` command with an easy-to-use interface.
+
+**Target Platform:** Windows 10+ only (as of 2025 modernization)
+
+## Build & Development Commands
+
+### Building the Application
+
+```powershell
+# Build in Debug configuration
+dotnet build "Symlinker/Symlinker.csproj" --configuration Debug
+
+# Build in Release configuration
+dotnet build "Symlinker/Symlinker.csproj" --configuration Release
+```
+
+### Publishing (ClickOnce)
+
+The application uses ClickOnce deployment and is published to GitHub Pages:
+
+```powershell
+# Full build and publish to gh-pages branch
+./release.ps1
+
+# Build only (skip deployment)
+./release.ps1 -OnlyBuild
+```
+
+The `release.ps1` script:
+- Reads the Git tag to determine version (e.g., `v1.2.3` becomes `1.2.3.0`)
+- Uses MSBuild to publish with ClickOnce profile
+- Deploys to `gh-pages` branch for auto-update support
+
+### Running the Application
+
+```powershell
+dotnet run --project "Symlinker/Symlinker.csproj"
+```
+
+## Architecture
+
+### Key Components
+
+1. **Program.cs**: Entry point with UAC elevation logic
+ - Automatically re-launches itself with administrator privileges using the `--engage` flag
+ - Uses Windows API (`BCM_SETSHIELD`) to display UAC shield icon
+
+2. **MainWindow.cs**: Main form and business logic
+ - Handles UI state for file vs. folder symlink modes
+ - Manages three link types: Symbolic Link, Hard Link, Directory Junction
+ - Executes `cmd.exe /c mklink` with appropriate parameters
+ - Supports drag-and-drop for file/folder paths
+
+3. **MainWindow.Designer.cs**: Auto-generated Windows Forms designer code
+ - Contains all UI control initialization
+
+### Link Creation Flow
+
+1. User selects link type (file/folder) via `typeSelectorComboBox`
+2. `Switcher()` method updates UI labels and available link types
+3. User inputs link location, link name, and destination
+4. `CreateLink()` validates paths and checks for conflicts
+5. `SendCommand()` builds and executes mklink command with proper escaping
+6. Process output/error handlers display results via MessageBox
+
+### Important Patterns
+
+- **Command Building**: Uses `string.Format()` with `CultureInfo.InvariantCulture` for mklink command construction
+- **Path Handling**: Wraps paths in quotes to support spaces
+- **Link Type Mapping**:
+ - Symbolic Link: `/D` (directory) or empty (file)
+ - Hard Link: `/H`
+ - Directory Junction: `/J` (folders only)
+
+### Dependencies
+
+Managed via Central Package Management ([Directory.Packages.props](Directory.Packages.props)):
+
+- **Microsoft-WindowsAPICodePack-Core** (1.1.5): Common file dialogs
+- **Microsoft-WindowsAPICodePack-Shell** (1.1.5): Shell integration
+
+## Project Structure
+
+```
+Symlinker/
+├── Symlinker/ # Main project directory
+│ ├── MainWindow.cs # Core form logic
+│ ├── MainWindow.Designer.cs # UI designer code
+│ ├── MainWindow.resx # Form resources
+│ ├── Program.cs # Entry point with UAC elevation
+│ ├── Properties/
+│ │ ├── Resources.Designer.cs # Localized strings
+│ │ └── Settings.Designer.cs # App settings
+│ ├── icon.ico # Application icon
+│ └── Symlinker.csproj
+├── Symlinker.sln # Solution file
+├── Directory.Packages.props # Central package versions
+├── release.ps1 # ClickOnce build/publish script
+└── .github/workflows/
+ └── release.yml # GitHub Actions for tagged releases
+```
+
+## Release Process
+
+Releases are automated via GitHub Actions:
+
+1. Push a Git tag: `git tag v1.2.3 && git push origin v1.2.3`
+2. GitHub Actions workflow ([.github/workflows/release.yml](.github/workflows/release.yml)) triggers
+3. `release.ps1` executes on Windows runner
+4. Application published to `gh-pages` branch
+5. ClickOnce installer auto-updates existing installations
+
+## Known Limitations
+
+- **Drag & Drop**: Does not work when running as administrator (Windows security limitation)
+- **UAC Required**: Creating symlinks requires administrator privileges on Windows
+- **Platform**: Windows 10/11 only (uses .NET 8 Windows-specific APIs)
+
+## Localization
+
+UI strings are managed through resource files:
+- [Symlinker/Properties/Resources.Designer.cs](Symlinker/Properties/Resources.Designer.cs)
+- [Symlinker/MainWindow.resx](Symlinker/MainWindow.resx)
+
+When modifying UI text, update the resource files rather than hardcoding strings.
diff --git a/README.md b/README.md
index 5e3fb99..def1957 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,7 @@ This project has been around since 2009, originally hosted on Google Code and bu
## Featured On
+- [Softpedia](https://www.softpedia.com/get/PORTABLE-SOFTWARE/System/System-Enhancements/Portable-Symbolic-Link-Creator.shtml)
- [addictivetips](http://www.addictivetips.com/windows-tips/symlinker-create-symlink-hardlink-and-directory-junction-in-windows/)
- [TecFlap](https://web.archive.org/web/20150511235232/http://www.tecflap.com/2012/05/29/software-day-winautohide-symlinker-hyperdesktop/) (Archived)
- [Zhacks](https://web.archive.org/web/20170512070430/http://www.zhacks.com/easily-create-symbolic-link-with-mklink-gui-symlinker) (Archived)
@@ -68,16 +69,16 @@ Previous project link: https://code.google.com/p/symlinker/
See [CHANGELOG.md](CHANGELOG.md)
-## Acknowledgments
-
-Free code signing provided by [SignPath.io](https://about.signpath.io/), certificate by [SignPath Foundation](https://signpath.org/)
-
-Committers and reviewers: [Contributors](https://github.com/amd989/Symlinker/graphs/contributors)
-
## License
MIT — see [LICENSE](LICENSE)
## Privacy Policy
-See [PRIVACY.md](PRIVACY.md)
\ No newline at end of file
+See [PRIVACY.md](PRIVACY.md)
+
+## Acknowledgments
+
+Free code signing provided by [SignPath.io](https://about.signpath.io/), certificate by [SignPath Foundation](https://signpath.org/)
+
+Committers and reviewers: [Contributors](https://github.com/amd989/Symlinker/graphs/contributors)
diff --git a/Symlinker/MainWindow.xaml b/Symlinker/MainWindow.xaml
index 969be73..d8bbafb 100644
--- a/Symlinker/MainWindow.xaml
+++ b/Symlinker/MainWindow.xaml
@@ -1,227 +1,482 @@
-
-
-
-
-
-
-
-
-
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
+ xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
+ xmlns:p="clr-namespace:Symlinker.Properties"
+ Title="Symlinker"
+ Width="560" Height="280"
+ ResizeMode="NoResize"
+ WindowStartupLocation="CenterScreen"
+ Icon="icon.ico"
+ TitleCharacterCasing="Normal"
+ BorderThickness="0"
+ GlowBrush="Transparent"
+ NonActiveGlowBrush="Transparent"
+ TitleAlignment="Center"
+ ShowIconOnTitleBar="False"
+ TitleBarHeight="32"
+ WindowTitleBrush="{DynamicResource MahApps.Brushes.Gray10}"
+ NonActiveWindowTitleBrush="{DynamicResource MahApps.Brushes.Gray10}"
+ TitleForeground="{DynamicResource MahApps.Brushes.ThemeForeground}"
+ OverrideDefaultWindowCommandsBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
+ Background="{DynamicResource MahApps.Brushes.Control.Background}"
+ UseLayoutRounding="True"
+ SnapsToDevicePixels="True"
+ TextOptions.TextFormattingMode="Display"
+ TextOptions.TextRenderingMode="ClearType">
- 4
+ 10
-
-
-
-
-
-
-
-
-
-
-
+
-
+
+
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
+
+
-
-
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+ HorizontalAlignment="Center" VerticalAlignment="Center"
+ FontFamily="Segoe UI"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/Symlinker/MainWindow.xaml.cs b/Symlinker/MainWindow.xaml.cs
index 3463fd2..c5850b5 100644
--- a/Symlinker/MainWindow.xaml.cs
+++ b/Symlinker/MainWindow.xaml.cs
@@ -3,15 +3,19 @@ namespace Symlinker
using System;
using System.Diagnostics;
using System.Globalization;
+
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
+ using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using MahApps.Metro.Controls;
+ using MahApps.Metro.Controls.Dialogs;
+ using MahApps.Metro.IconPacks;
using Res = Symlinker.Properties.Resources;
@@ -24,197 +28,184 @@ public partial class MainWindow : MetroWindow
[DllImport("dwmapi.dll")]
private static extern int DwmSetWindowAttribute(nint hwnd, int attr, ref int attrValue, int attrSize);
- private bool isFolder;
+ private const string PlaceholderBrushKey = "MahApps.Brushes.Gray3";
+ private const string PrimaryTextBrushKey = "MahApps.Brushes.ThemeForeground";
+
+ private bool isFolder = true;
+
+ private static readonly MetroDialogSettings FastDialog = new()
+ {
+ AnimateShow = false,
+ AnimateHide = false,
+ };
public MainWindow()
{
InitializeComponent();
-
- linkTypeComboBox.SelectedIndex = 0;
- typeSelectorComboBox.SelectedIndex = 0;
-
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
- // Request Windows 11 rounded corners for this window
var hwnd = new WindowInteropHelper(this).Handle;
var preference = DWMWCP_ROUND;
DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, ref preference, sizeof(int));
+
+ UpdateMode();
}
- private void TypeSelectorComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ private void TypeRadio_Checked(object sender, RoutedEventArgs e)
{
- Switcher();
+ UpdateMode();
}
- private void Switcher()
+ private void UpdateMode()
{
- if (groupBox1Header == null || groupBox2Header == null)
- return;
+ if (folderTypeRadio == null || junctionRadio == null || symLinkRadio == null || hardLinkRadio == null || sourceIcon == null) return;
- if (typeSelectorComboBox.SelectedIndex == 0)
- {
- groupBox1Header.Text = Res.MainWindow_Switcher_Link_Folder;
- groupBox2Header.Text = Res.MainWindow_Switcher_Destination_Folder;
- label2.Text = Res.MainWindow_Switcher_Now_give_a_name_to_the_link_;
- label3.Text = Res.MainWindow_Switcher_Please_select_the_path_to_the_real_folder_you_want_to_link_;
- isFolder = true;
-
- // Add Directory Junction if not present
- bool hasJunction = false;
- foreach (ComboBoxItem item in linkTypeComboBox.Items)
- {
- if (item.Content as string == "Directory Junction")
- {
- hasJunction = true;
- break;
- }
- }
- if (!hasJunction)
- linkTypeComboBox.Items.Add(new ComboBoxItem { Content = "Directory Junction" });
+ isFolder = folderTypeRadio.IsChecked == true;
- linkTypeComboBox.ToolTip = Res.TooltipLinkTypeFolderDescription;
- }
- else
- {
- groupBox1Header.Text = Res.MainWindow_Switcher_Link_File;
- groupBox2Header.Text = Res.MainWindow_Switcher_Destination_File;
- label2.Text = Res.MainWindow_Switcher_Now_give_a_name_to_your_file_;
- label3.Text = Res.MainWindow_Switcher_Please_select_the_path_to_the_real_file_you_want_to_link_;
- isFolder = false;
-
- // Remove Directory Junction for file mode
- ComboBoxItem junctionItem = null;
- foreach (ComboBoxItem item in linkTypeComboBox.Items)
- {
- if (item.Content as string == "Directory Junction")
- {
- junctionItem = item;
- break;
- }
- }
- if (junctionItem != null)
- {
- if (linkTypeComboBox.SelectedIndex == 2)
- linkTypeComboBox.SelectedIndex = 0;
- linkTypeComboBox.Items.Remove(junctionItem);
- }
+ junctionRadio.Visibility = isFolder ? Visibility.Visible : Visibility.Collapsed;
+ hardLinkRadio.Visibility = isFolder ? Visibility.Collapsed : Visibility.Visible;
- linkTypeComboBox.ToolTip = Res.TooltipLinkTypeFileDescription;
+ if (!isFolder && junctionRadio.IsChecked == true)
+ symLinkRadio.IsChecked = true;
+
+ if (isFolder && hardLinkRadio.IsChecked == true)
+ symLinkRadio.IsChecked = true;
+
+ sourceIcon.Kind = isFolder
+ ? PackIconMaterialKind.FolderOutline
+ : PackIconMaterialKind.FileOutline;
+
+ var linkTypeTooltip = isFolder ? Res.TooltipLinkTypeFolderDescription : Res.TooltipLinkTypeFileDescription;
+ symLinkRadio.ToolTip = linkTypeTooltip;
+ hardLinkRadio.ToolTip = linkTypeTooltip;
+ junctionRadio.ToolTip = linkTypeTooltip;
+
+ if (destinationLocationTextBox != null)
+ destinationLocationTextBox.Text = string.Empty;
+
+ if (destinationFolderName != null)
+ {
+ destinationFolderName.Text = isFolder ? Res.PlaceholderTargetFolder : Res.PlaceholderTargetFile;
+ destinationFolderName.SetResourceReference(ForegroundProperty, PlaceholderBrushKey);
}
+ }
- typeSelectorComboBox.ToolTip = Res.TooltipTypeSelectorDescription;
+ private string GetLinkTypeFlag()
+ {
+ if (hardLinkRadio.IsChecked == true) return "/H ";
+ if (junctionRadio.IsChecked == true) return "/J ";
+ return string.Empty;
}
- private string ComboBoxSelection()
+ private void DestinationPath_TextChanged(object sender, TextChangedEventArgs e)
{
- switch (linkTypeComboBox.SelectedIndex)
+ var path = destinationLocationTextBox.Text;
+ if (string.IsNullOrEmpty(path))
{
- case 1:
- return "/H ";
- case 2:
- return "/J ";
- default:
- return string.Empty;
+ destinationFolderName.Text = isFolder ? Res.PlaceholderTargetFolder : Res.PlaceholderTargetFile;
+ destinationFolderName.SetResourceReference(ForegroundProperty, PlaceholderBrushKey);
+ return;
}
+
+ var trimmed = path.TrimEnd('\\', '/');
+ var lastSep = trimmed.LastIndexOfAny(new[] { '\\', '/' });
+ destinationFolderName.Text = lastSep >= 0 ? trimmed[(lastSep + 1)..] : trimmed;
+ destinationFolderName.SetResourceReference(ForegroundProperty, PrimaryTextBrushKey);
}
- private void CreateLink()
+ private async Task CreateLink()
{
try
{
- if (linkLocationTextBox.Text != string.Empty && linkNameTextBox.Text != string.Empty && destinationLocationTextBox.Text != string.Empty)
+ var linkLocation = linkLocationTextBox.Text.Trim();
+ var linkName = linkNameTextBox.Text.Trim();
+ var destination = destinationLocationTextBox.Text.Trim();
+
+ if (string.IsNullOrWhiteSpace(linkLocation) || string.IsNullOrWhiteSpace(linkName) || string.IsNullOrWhiteSpace(destination))
{
- if (isFolder && Directory.Exists(linkLocationTextBox.Text) && Directory.Exists(destinationLocationTextBox.Text))
- {
- var link = string.Format(
- "\"{0}\\{1}\" ", linkLocationTextBox.Text, linkNameTextBox.Text);
+ await this.ShowMessageAsync(Res.MessageBoxErrorTitle, Res.FillBlanks, settings: FastDialog);
+ return;
+ }
- var directories = Directory.GetDirectories(linkLocationTextBox.Text);
+ if (linkName.Contains('"') || linkLocation.Contains('"') || destination.Contains('"'))
+ {
+ await this.ShowMessageAsync(Res.MessageBoxErrorTitle, Res.InvalidQuoteInPath, settings: FastDialog);
+ return;
+ }
- if (directories.Any(e => e.Split('\\').Last().Equals(linkNameTextBox.Text)))
- {
- var answer = MessageBox.Show(
- Res.DialogFolderExists,
- Res.DialogFolderExistsDialog,
- MessageBoxButton.YesNo,
- MessageBoxImage.Warning);
- if (answer == MessageBoxResult.Yes)
- {
- var dir2Delete = directories.First(e => e.Split('\\').Last().Equals(linkNameTextBox.Text));
- Directory.Delete(dir2Delete);
- SendCommand(link);
- return;
- }
-
- MessageBox.Show(
- Res.LinkCreationAborted,
- Res.LinkCreationAbortedWarning,
- MessageBoxButton.OK,
- MessageBoxImage.Stop);
- }
- else
+ var link = $"\"{linkLocation}\\{linkName}\"";
+
+ if (isFolder && Directory.Exists(linkLocation) && Directory.Exists(destination))
+ {
+ var directories = Directory.GetDirectories(linkLocation);
+
+ if (directories.Any(e => Path.GetFileName(e).Equals(linkName, StringComparison.OrdinalIgnoreCase)))
+ {
+ var answer = await this.ShowMessageAsync(
+ Res.DialogFolderExistsDialog,
+ Res.DialogFolderExists,
+ MessageDialogStyle.AffirmativeAndNegative,
+ FastDialog);
+ if (answer == MessageDialogResult.Affirmative)
{
- SendCommand(link);
+ var dir2Delete = directories.First(e => Path.GetFileName(e).Equals(linkName, StringComparison.OrdinalIgnoreCase));
+ Directory.Delete(dir2Delete, true);
+ await SendCommand(link, destination);
+ return;
}
+
+ await this.ShowMessageAsync(Res.LinkCreationAbortedWarning, Res.LinkCreationAborted, settings: FastDialog);
}
- else if (Directory.Exists(linkLocationTextBox.Text) && File.Exists(destinationLocationTextBox.Text))
+ else
{
- var link = string.Format(
- "\"{0}\\{1}\" ", linkLocationTextBox.Text, linkNameTextBox.Text);
-
- var files = Directory.GetFiles(linkLocationTextBox.Text);
- if (files.Any(e => e.Split('\\').Last().Equals(linkNameTextBox.Text)))
- {
- var answer = MessageBox.Show(
- Res.DialogDeleteFile,
- Res.DialogDeleteFileWarning,
- MessageBoxButton.YesNo,
- MessageBoxImage.Warning);
- if (answer == MessageBoxResult.Yes)
- {
- var file2Delete = files.First(e => e.Split('\\').Last().Equals(linkNameTextBox.Text));
- File.Delete(file2Delete);
- SendCommand(link);
- return;
- }
- MessageBox.Show(
- Res.LinkCreationAborted,
- Res.LinkCreationAbortedWarning,
- MessageBoxButton.OK,
- MessageBoxImage.Stop);
- }
- else
+ await SendCommand(link, destination);
+ }
+ }
+ else if (!isFolder && Directory.Exists(linkLocation) && File.Exists(destination))
+ {
+ var files = Directory.GetFiles(linkLocation);
+ if (files.Any(e => Path.GetFileName(e).Equals(linkName, StringComparison.OrdinalIgnoreCase)))
+ {
+ var answer = await this.ShowMessageAsync(
+ Res.DialogDeleteFileWarning,
+ Res.DialogDeleteFile,
+ MessageDialogStyle.AffirmativeAndNegative,
+ FastDialog);
+ if (answer == MessageDialogResult.Affirmative)
{
- SendCommand(link);
+ var file2Delete = files.First(e => Path.GetFileName(e).Equals(linkName, StringComparison.OrdinalIgnoreCase));
+ File.Delete(file2Delete);
+ await SendCommand(link, destination);
+ return;
}
+ await this.ShowMessageAsync(Res.LinkCreationAbortedWarning, Res.LinkCreationAborted, settings: FastDialog);
}
else
{
- MessageBox.Show(Res.FilesOrFolderNotExists, Res.MessageBoxErrorTitle, MessageBoxButton.OK, MessageBoxImage.Warning);
+ await SendCommand(link, destination);
}
}
else
{
- MessageBox.Show(Res.FillBlanks, Res.MessageBoxErrorTitle, MessageBoxButton.OK, MessageBoxImage.Error);
+ await this.ShowMessageAsync(Res.MessageBoxErrorTitle, Res.FilesOrFolderNotExists, settings: FastDialog);
}
}
catch (Exception exception)
{
- MessageBox.Show(Res.MessageBoxExceptionOcurred + exception.Message, Res.MessageBoxErrorTitle, MessageBoxButton.OK, MessageBoxImage.Error);
+ await this.ShowMessageAsync(Res.MessageBoxErrorTitle, Res.MessageBoxExceptionOcurred + "\n" + exception.Message, settings: FastDialog);
}
}
- private void SendCommand(string link)
+ private async Task SendCommand(string link, string destination)
{
try
{
- var target = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", destinationLocationTextBox.Text);
- var typeLink = ComboBoxSelection();
+ var typeLink = GetLinkTypeFlag();
var directory = isFolder ? "/D " : string.Empty;
- var stringCommand = string.Format(CultureInfo.InvariantCulture, "/c mklink {0}{1}{2}{3}", directory, typeLink, link, target);
+ var stringCommand = $"/c mklink {directory}{typeLink}{link} \"{destination}\"";
var processStartInfo = new ProcessStartInfo
{
FileName = "cmd",
@@ -225,36 +216,37 @@ private void SendCommand(string link)
RedirectStandardOutput = true
};
+ var gotOutput = false;
var process = new Process { StartInfo = processStartInfo, EnableRaisingEvents = true };
process.ErrorDataReceived += Process_ErrorDataReceived;
- process.OutputDataReceived += Process_OutputDataReceived;
+ process.OutputDataReceived += (s, ev) =>
+ {
+ if (!string.IsNullOrEmpty(ev.Data))
+ {
+ gotOutput = true;
+ Dispatcher.InvokeAsync(() => this.ShowMessageAsync(Res.MessageBoxSuccessTitle, ev.Data, settings: FastDialog));
+ }
+ };
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
- process.WaitForExit();
- process.Close();
+ await process.WaitForExitAsync();
+
+ if (process.ExitCode == 0 && !gotOutput)
+ await this.ShowMessageAsync(Res.MessageBoxSuccessTitle, Res.LinkSuccessfullyCreated, settings: FastDialog);
+
process.Dispose();
}
catch (Exception)
{
- MessageBox.Show(Res.CmdNotFound, Res.MessageBoxErrorTitle, MessageBoxButton.OK, MessageBoxImage.Error);
- }
- }
-
- private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
- {
- if (!string.IsNullOrEmpty(e.Data))
- {
- MessageBox.Show(e.Data, Res.MessageBoxSuccessTitle, MessageBoxButton.OK, MessageBoxImage.Information);
+ await this.ShowMessageAsync(Res.MessageBoxErrorTitle, Res.CmdNotFound, settings: FastDialog);
}
}
private void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (!string.IsNullOrEmpty(e.Data))
- {
- MessageBox.Show(e.Data, Res.MessageBoxErrorTitle, MessageBoxButton.OK, MessageBoxImage.Error);
- }
+ Dispatcher.InvokeAsync(() => this.ShowMessageAsync(Res.MessageBoxErrorTitle, e.Data, settings: FastDialog));
}
private void ExploreButton1_Click(object sender, RoutedEventArgs e)
@@ -280,30 +272,33 @@ private void ExploreButton2_Click(object sender, RoutedEventArgs e)
}
}
- private void CreateLink_Click(object sender, RoutedEventArgs e)
- {
- CreateLink();
- }
-
- private void AboutButton_Click(object sender, RoutedEventArgs e)
+ private async void CreateLink_Click(object sender, RoutedEventArgs e)
{
- string version;
+ createLinkButton.IsEnabled = false;
try
{
- version = Environment.GetEnvironmentVariable("ClickOnce_CurrentVersion");
+ await CreateLink();
}
- catch
+ finally
+ {
+ createLinkButton.IsEnabled = true;
+ }
+ }
+
+ private async void AboutButton_Click(object sender, RoutedEventArgs e)
+ {
+ var version = Environment.GetEnvironmentVariable("ClickOnce_CurrentVersion");
+ if (string.IsNullOrEmpty(version))
{
var assembly = Assembly.GetExecutingAssembly();
var fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
version = fvi.FileVersion;
}
- MessageBox.Show(
- string.Format(CultureInfo.CurrentCulture, Res.AboutDescription, version),
+ await this.ShowMessageAsync(
Res.MessageBoxAboutTitle,
- MessageBoxButton.OK,
- MessageBoxImage.Information);
+ string.Format(CultureInfo.CurrentUICulture, Res.AboutDescription, version, DateTime.Now.Year),
+ settings: FastDialog);
}
private void TextBox_DragOver(object sender, DragEventArgs e)
@@ -318,17 +313,35 @@ private void TextBox_PreviewDragEnter(object sender, DragEventArgs e)
e.Handled = true;
}
- private void TextBox_Drop(object sender, DragEventArgs e)
+ private void SourceCard_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files != null && files.Length != 0)
{
- var textBox = (TextBox)sender;
- textBox.Text = files[0];
+ var path = files[0];
+ var droppedIsFolder = Directory.Exists(path);
+ if (droppedIsFolder != isFolder)
+ {
+ if (droppedIsFolder)
+ folderTypeRadio.IsChecked = true;
+ else
+ fileTypeRadio.IsChecked = true;
+ }
+ destinationLocationTextBox.Text = path;
}
}
}
+
+ private void LinkCard_Drop(object sender, DragEventArgs e)
+ {
+ if (e.Data.GetDataPresent(DataFormats.FileDrop))
+ {
+ var files = (string[])e.Data.GetData(DataFormats.FileDrop);
+ if (files != null && files.Length != 0 && Directory.Exists(files[0]))
+ linkLocationTextBox.Text = files[0];
+ }
+ }
}
}
diff --git a/Symlinker/Properties/PublishProfiles/ClickOnceProfile.pubxml b/Symlinker/Properties/PublishProfiles/ClickOnceProfile.pubxml
index 9a7d656..1eaedec 100644
--- a/Symlinker/Properties/PublishProfiles/ClickOnceProfile.pubxml
+++ b/Symlinker/Properties/PublishProfiles/ClickOnceProfile.pubxml
@@ -3,7 +3,7 @@
0
- 3.0.0.0
+ 4.0.0.0
True
Release
True
diff --git a/Symlinker/Properties/Resources.Designer.cs b/Symlinker/Properties/Resources.Designer.cs
index 7796723..516bf44 100644
--- a/Symlinker/Properties/Resources.Designer.cs
+++ b/Symlinker/Properties/Resources.Designer.cs
@@ -19,10 +19,10 @@ namespace Symlinker.Properties {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class Resources {
+ public class Resources {
private static global::System.Resources.ResourceManager resourceMan;
@@ -36,7 +36,7 @@ internal Resources() {
/// Returns the cached ResourceManager instance used by this class.
///
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Resources.ResourceManager ResourceManager {
+ public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Symlinker.Properties.Resources", typeof(Resources).Assembly);
@@ -51,7 +51,7 @@ internal Resources() {
/// resource lookups using this strongly typed resource class.
///
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture {
+ public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
@@ -61,263 +61,360 @@ internal Resources() {
}
///
- /// Looks up a localized string similar to © 2010-2025 Alejandro Mora
+ /// Looks up a localized string similar to © 2010-{1} Alejandro Mora
///Version: {0}
///e-mail: mail@alejandro.md
///
///Thanks to Microsoft for the use of their shortcut arrow :).
///
- internal static string AboutDescription {
+ public static string AboutDescription {
get {
return ResourceManager.GetString("AboutDescription", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Cannot find the file needed to create links, the creation stopped.
+ /// Looks up a localized string similar to About.
///
- internal static string CmdNotFound {
+ public static string ButtonAbout {
+ get {
+ return ResourceManager.GetString("ButtonAbout", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Add another.
+ ///
+ public static string ButtonAddAnother {
+ get {
+ return ResourceManager.GetString("ButtonAddAnother", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Create link.
+ ///
+ public static string ButtonCreateLink {
+ get {
+ return ResourceManager.GetString("ButtonCreateLink", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to LINK LOCATION.
+ ///
+ public static string CardHeaderLinkLocation {
+ get {
+ return ResourceManager.GetString("CardHeaderLinkLocation", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to TARGET.
+ ///
+ public static string CardHeaderTarget {
+ get {
+ return ResourceManager.GetString("CardHeaderTarget", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Cannot find the command-line tool needed to create links..
+ ///
+ public static string CmdNotFound {
get {
return ResourceManager.GetString("CmdNotFound", resourceCulture);
}
}
///
- /// Looks up a localized string similar to The link name you are using already exists in the selected directory, would you like to DELETE the file and then create a new link?.
+ /// Looks up a localized string similar to A file with this link name already exists in the selected directory. Would you like to delete it and create a new link?.
///
- internal static string DialogDeleteFile {
+ public static string DialogDeleteFile {
get {
return ResourceManager.GetString("DialogDeleteFile", resourceCulture);
}
}
///
- /// Looks up a localized string similar to File already there....
+ /// Looks up a localized string similar to File already exists.
///
- internal static string DialogDeleteFileWarning {
+ public static string DialogDeleteFileWarning {
get {
return ResourceManager.GetString("DialogDeleteFileWarning", resourceCulture);
}
}
///
- /// Looks up a localized string similar to The link name you are using already exists in the selected directory, would you like to DELETE the folder and then create a new link?.
+ /// Looks up a localized string similar to A folder with this link name already exists in the selected directory. Would you like to delete it and create a new link?.
///
- internal static string DialogFolderExists {
+ public static string DialogFolderExists {
get {
return ResourceManager.GetString("DialogFolderExists", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Folder already there....
+ /// Looks up a localized string similar to Folder already exists.
///
- internal static string DialogFolderExistsDialog {
+ public static string DialogFolderExistsDialog {
get {
return ResourceManager.GetString("DialogFolderExistsDialog", resourceCulture);
}
}
///
- /// Looks up a localized string similar to One of the directories/files does not exists, please provide valid directories/files.
+ /// Looks up a localized string similar to One of the specified directories or files does not exist. Please provide valid paths..
///
- internal static string FilesOrFolderNotExists {
+ public static string FilesOrFolderNotExists {
get {
return ResourceManager.GetString("FilesOrFolderNotExists", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Paths cannot contain quote characters. Please remove any quotes and try again..
+ ///
+ public static string InvalidQuoteInPath {
+ get {
+ return ResourceManager.GetString("InvalidQuoteInPath", resourceCulture);
+ }
+ }
///
- /// Looks up a localized string similar to Please fill all the blanks spaces with the indicated info.
+ /// Looks up a localized string similar to Please fill in all the required fields..
///
- internal static string FillBlanks {
+ public static string FillBlanks {
get {
return ResourceManager.GetString("FillBlanks", resourceCulture);
}
}
-
///
- /// Looks up a localized string similar to Link creation aborted.
+ /// Looks up a localized string similar to Link creation was aborted..
///
- internal static string LinkCreationAborted {
+ public static string LinkCreationAborted {
get {
return ResourceManager.GetString("LinkCreationAborted", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Aborted Operation.
+ /// Looks up a localized string similar to Operation aborted.
///
- internal static string LinkCreationAbortedWarning {
+ public static string LinkCreationAbortedWarning {
get {
return ResourceManager.GetString("LinkCreationAbortedWarning", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Link successfully created.
+ /// Looks up a localized string similar to Link successfully created..
///
- internal static string LinkSuccessfullyCreated {
+ public static string LinkSuccessfullyCreated {
get {
return ResourceManager.GetString("LinkSuccessfullyCreated", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Destination File.
+ /// Looks up a localized string similar to About.
+ ///
+ public static string MessageBoxAboutTitle {
+ get {
+ return ResourceManager.GetString("MessageBoxAboutTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Error.
///
- internal static string MainWindow_Switcher_Destination_File {
+ public static string MessageBoxErrorTitle {
get {
- return ResourceManager.GetString("MainWindow_Switcher_Destination_File", resourceCulture);
+ return ResourceManager.GetString("MessageBoxErrorTitle", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Destination Folder.
+ /// Looks up a localized string similar to An error has occurred:.
///
- internal static string MainWindow_Switcher_Destination_Folder {
+ public static string MessageBoxExceptionOcurred {
get {
- return ResourceManager.GetString("MainWindow_Switcher_Destination_Folder", resourceCulture);
+ return ResourceManager.GetString("MessageBoxExceptionOcurred", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Link File.
+ /// Looks up a localized string similar to Success.
///
- internal static string MainWindow_Switcher_Link_File {
+ public static string MessageBoxSuccessTitle {
get {
- return ResourceManager.GetString("MainWindow_Switcher_Link_File", resourceCulture);
+ return ResourceManager.GetString("MessageBoxSuccessTitle", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Link Folder.
+ /// Looks up a localized string similar to File.
///
- internal static string MainWindow_Switcher_Link_Folder {
+ public static string PillFile {
get {
- return ResourceManager.GetString("MainWindow_Switcher_Link_Folder", resourceCulture);
+ return ResourceManager.GetString("PillFile", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Now give a name to the link:.
+ /// Looks up a localized string similar to Folder.
///
- internal static string MainWindow_Switcher_Now_give_a_name_to_the_link_ {
+ public static string PillFolder {
get {
- return ResourceManager.GetString("MainWindow_Switcher_Now_give_a_name_to_the_link_", resourceCulture);
+ return ResourceManager.GetString("PillFolder", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Now give a name to your file:.
+ /// Looks up a localized string similar to Hard link.
///
- internal static string MainWindow_Switcher_Now_give_a_name_to_your_file_ {
+ public static string PillHardLink {
get {
- return ResourceManager.GetString("MainWindow_Switcher_Now_give_a_name_to_your_file_", resourceCulture);
+ return ResourceManager.GetString("PillHardLink", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Please select the path to the real file you want to link:.
+ /// Looks up a localized string similar to Junction.
///
- internal static string MainWindow_Switcher_Please_select_the_path_to_the_real_file_you_want_to_link_ {
+ public static string PillJunction {
get {
- return ResourceManager.GetString("MainWindow_Switcher_Please_select_the_path_to_the_real_file_you_want_to_link_", resourceCulture);
+ return ResourceManager.GetString("PillJunction", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Please select the path to the real folder you want to link:.
+ /// Looks up a localized string similar to Symbolic link.
///
- internal static string MainWindow_Switcher_Please_select_the_path_to_the_real_folder_you_want_to_link_ {
+ public static string PillSymbolicLink {
get {
- return ResourceManager.GetString("MainWindow_Switcher_Please_select_the_path_to_the_real_folder_you_want_to_link_", resourceCulture);
+ return ResourceManager.GetString("PillSymbolicLink", resourceCulture);
}
}
///
- /// Looks up a localized string similar to About.
+ /// Looks up a localized string similar to Link name.
///
- internal static string MessageBoxAboutTitle {
+ public static string PlaceholderLinkName {
get {
- return ResourceManager.GetString("MessageBoxAboutTitle", resourceCulture);
+ return ResourceManager.GetString("PlaceholderLinkName", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Error.
+ /// Looks up a localized string similar to C:\path\to\location.
///
- internal static string MessageBoxErrorTitle {
+ public static string PlaceholderLinkPath {
get {
- return ResourceManager.GetString("MessageBoxErrorTitle", resourceCulture);
+ return ResourceManager.GetString("PlaceholderLinkPath", resourceCulture);
}
}
///
- /// Looks up a localized string similar to An error has ocurred:.
+ /// Looks up a localized string similar to Target file.
///
- internal static string MessageBoxExceptionOcurred {
+ public static string PlaceholderTargetFile {
get {
- return ResourceManager.GetString("MessageBoxExceptionOcurred", resourceCulture);
+ return ResourceManager.GetString("PlaceholderTargetFile", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Success.
+ /// Looks up a localized string similar to Target folder.
///
- internal static string MessageBoxSuccessTitle {
+ public static string PlaceholderTargetFolder {
get {
- return ResourceManager.GetString("MessageBoxSuccessTitle", resourceCulture);
+ return ResourceManager.GetString("PlaceholderTargetFolder", resourceCulture);
}
}
///
- /// Looks up a localized string similar to This option allows you to select the style of your symbolic link, either
- ///you choose to use symbolic links or hard links.
- ///Use symbolic links as a default..
+ /// Looks up a localized string similar to D:\path\to\target.
///
- internal static string TooltipLinkTypeFileDescription {
+ public static string PlaceholderTargetPath {
+ get {
+ return ResourceManager.GetString("PlaceholderTargetPath", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to About Symlinker.
+ ///
+ public static string TooltipAbout {
+ get {
+ return ResourceManager.GetString("TooltipAbout", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Choose where the link will be created and give it a name.
+ ///Click the icon or drag and drop a folder here..
+ ///
+ public static string TooltipLinkLocationCard {
+ get {
+ return ResourceManager.GetString("TooltipLinkLocationCard", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Choose the link type: symbolic link or hard link.
+ ///Use symbolic links as the default..
+ ///
+ public static string TooltipLinkTypeFileDescription {
get {
return ResourceManager.GetString("TooltipLinkTypeFileDescription", resourceCulture);
}
}
///
- /// Looks up a localized string similar to This option allows you to select the style of your symbolic link, either
- ///you choose to use symbolic links, hard links or directory junctions.
- ///Use symbolic links as a default..
+ /// Looks up a localized string similar to Choose the link type: symbolic link, hard link, or directory junction.
+ ///Use symbolic links as the default..
///
- internal static string TooltipLinkTypeFolderDescription {
+ public static string TooltipLinkTypeFolderDescription {
get {
return ResourceManager.GetString("TooltipLinkTypeFolderDescription", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Symbolic Link types.
+ /// Looks up a localized string similar to Symbolic link types.
///
- internal static string TooltipLinkTypeTitle {
+ public static string TooltipLinkTypeTitle {
get {
return ResourceManager.GetString("TooltipLinkTypeTitle", resourceCulture);
}
}
///
- /// Looks up a localized string similar to With this option you can choose between creating file symbolic links;
- ///this is using a file to point to another file, or folder symbolic links;
- ///this is using folders that point to other folders..
+ /// Looks up a localized string similar to Select the real file or folder the link will point to.
+ ///Click the icon or drag and drop a file/folder here..
+ ///
+ public static string TooltipTargetCard {
+ get {
+ return ResourceManager.GetString("TooltipTargetCard", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Choose between file mode (link a file to another file)
+ ///or folder mode (link a folder to another folder)..
///
- internal static string TooltipTypeSelectorDescription {
+ public static string TooltipTypeSelectorDescription {
get {
return ResourceManager.GetString("TooltipTypeSelectorDescription", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Symbolic Link type selector.
+ /// Looks up a localized string similar to Symbolic link type selector.
///
- internal static string TooltipTypeSelectorTitle {
+ public static string TooltipTypeSelectorTitle {
get {
return ResourceManager.GetString("TooltipTypeSelectorTitle", resourceCulture);
}
diff --git a/Symlinker/Properties/Resources.resx b/Symlinker/Properties/Resources.resx
index b9ed390..e3b66f5 100644
--- a/Symlinker/Properties/Resources.resx
+++ b/Symlinker/Properties/Resources.resx
@@ -118,34 +118,37 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
- The link name you are using already exists in the selected directory, would you like to DELETE the file and then create a new link?
+ A file with this link name already exists in the selected directory. Would you like to delete it and create a new link?
- File already there...
+ File already exists
- Link creation aborted
+ Link creation was aborted.
- Aborted Operation
+ Operation aborted
- One of the directories/files does not exists, please provide valid directories/files
+ One of the specified directories or files does not exist. Please provide valid paths.
+
+
+ Paths cannot contain quote characters. Please remove any quotes and try again.
- Please fill all the blanks spaces with the indicated info
+ Please fill in all the required fields.
- Cannot find the file needed to create links, the creation stopped
+ Cannot find the command-line tool needed to create links.
- Link successfully created
+ Link successfully created.
- The link name you are using already exists in the selected directory, would you like to DELETE the folder and then create a new link?
+ A folder with this link name already exists in the selected directory. Would you like to delete it and create a new link?
- Folder already there...
+ Folder already exists
Success
@@ -153,62 +156,91 @@
Error
-
- Link Folder
+
+ LINK LOCATION
+
+
+ TARGET
+
+
+ Link name
+
+
+ C:\path\to\location
+
+
+ D:\path\to\target
+
+
+ Target folder
-
- Destination Folder
+
+ Target file
-
- Now give a name to the link:
+
+ Folder
-
- Please select the path to the real folder you want to link:
+
+ File
-
- Link File
+
+ Symbolic link
-
- Destination File
+
+ Hard link
-
- Now give a name to your file:
+
+ Junction
-
- Please select the path to the real file you want to link:
+
+ Create link
+
+
+ Add another
About
- © 2010-2025 Alejandro Mora
+ © 2010-{1} Alejandro Mora
Version: {0}
e-mail: mail@alejandro.md
Thanks to Microsoft for the use of their shortcut arrow :)
- An error has ocurred:
+ An error has occurred:
+
+
+ Choose where the link will be created and give it a name.
+Click the icon or drag and drop a folder here.
+
+
+ Select the real file or folder the link will point to.
+Click the icon or drag and drop a file/folder here.
- This option allows you to select the style of your symbolic link, either
-you choose to use symbolic links or hard links.
-Use symbolic links as a default.
+ Choose the link type: symbolic link or hard link.
+Use symbolic links as the default.
- This option allows you to select the style of your symbolic link, either
-you choose to use symbolic links, hard links or directory junctions.
-Use symbolic links as a default.
+ Choose the link type: symbolic link, hard link, or directory junction.
+Use symbolic links as the default.
- Symbolic Link types
+ Symbolic link types
- With this option you can choose between creating file symbolic links;
-this is using a file to point to another file, or folder symbolic links;
-this is using folders that point to other folders.
+ Choose between file mode (link a file to another file)
+or folder mode (link a folder to another folder).
- Symbolic Link type selector
+ Symbolic link type selector
+
+
+ About
+
+
+ About Symlinker
\ No newline at end of file
diff --git a/Symlinker/Properties/app.manifest b/Symlinker/Properties/app.manifest
index 2856403..f13c900 100644
--- a/Symlinker/Properties/app.manifest
+++ b/Symlinker/Properties/app.manifest
@@ -31,6 +31,12 @@
+
+
+ true/pm
+ PerMonitorV2
+
+