Skip to content

Commit

Permalink
[Enhancement] Support for long path aware and resolve #19
Browse files Browse the repository at this point in the history
  • Loading branch information
arnobpl committed Dec 24, 2024
1 parent 3d80ad2 commit 8894993
Show file tree
Hide file tree
Showing 19 changed files with 414 additions and 63 deletions.
45 changes: 41 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Symlink Creator
Symlink Creator is a GUI app for creating symbolic links (symlinks), and it is based on [`mklink`](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/mklink) command. You can create multiple symlinks at a time.
Symlink Creator is a GUI app for creating symbolic links (symlinks), and it is based on the [`mklink`](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/mklink) command. You can create multiple symlinks at a time.

## Use cases
- Suppose, you have a collection of several songs sorted by artists and albums on your PC. You might want a separate collection of your favorite songs which you will store on your mobile devices. In this scenario, the traditional shortcut option through the File Explorer right-click context menu is not enough, because you cannot copy the actual file contents by copying the traditional shortcut files (*\*.lnk*). You might consider duplicating the files which you will store on your mobile devices. But it will waste the storage space of your PC. In this case, Symlink Creator will come in handy. You can easily create a separate collection of songs and transfer them to your mobile devices, without wasting your PC's storage space.
- Suppose, you have a collection of several songs sorted by artists and albums on your PC. You might want a separate collection of your favorite songs which you will store on your mobile devices. In this scenario, the traditional shortcut option through the File Explorer right-click context menu is insufficient, because you cannot copy the actual file contents by copying the traditional shortcut files (*\*.lnk*). You might consider duplicating the files which you will store on your mobile devices. But it will waste the storage space of your PC. In this case, Symlink Creator will come in handy. You can easily create a separate collection of songs and transfer them to your mobile devices, without wasting your PC's storage space.

- Suppose, you have a special folder that is linked to your online storage like Google Drive. You might want some specific files/folders to be backed up from other folders. A traditional shortcut file is not helpful here to back up those files. In this scenario, you can use Symlink Creator for backup purposes without duplicating those files/folders in the special folder.

Expand All @@ -12,19 +12,56 @@ Symlink Creator is a GUI app for creating symbolic links (symlinks), and it is b
Symlink Creator creates *symlinks* which is an NTFS feature. Unlike the traditional shortcut files (*\*.lnk*), symlinks do not have any *file size*. While symlinks may be called advanced shortcut files, they appear to be real files. Unlike duplicated files, symlinks do not waste your storage space. Symlink Creator works for both files and folders.

## How Symlink Creator works
Symlink Creator uses `mklink` command to create symlinks. Symlink Creator first creates a script file that contains `mklink` command lines and executes it. Symlink Creator works in Windows Vista, Windows 7, and Windows 10/11. It does not work in Windows XP because of the lack of the `mklink` command.
- Symlink Creator uses the `mklink` command to create symlinks by generating and executing a script.
- It works on Windows 11/10.
- Prior to version 1.3.0, it also supported Windows 8.1/8, Windows 7, and Windows Vista. Because the [`longPathAware`](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry#enable-long-paths-in-windows-10-version-1607-and-later) feature, introduced in Symlink Creator v1.3.0, is unsupported on earlier Windows versions.
- It does not work on Windows XP due to the absence of the `mklink` command.

## How to use Symlink Creator
![Screenshot](SymlinkCreator/_ReadMe/Screenshot.png "Screenshot of Symlink Creator")
- At the `Source file or folder list`, you can add files or folders which will be copied in the `Destination path` as symlinks.
- Using Symlink Creator's drag-n-drop feature, you can easily create multiple symlinks at a time.
- You can drag-n-drop files/folders directly from File Explorer.
- You can also drag-n-drop the text containing a list of file/folder paths separated by a new line such as:
```
D:\TestingSymlinkCreator/Src/MyFile1.txt
D:\TestingSymlinkCreator/Src/MyFile2.txt
```
- Tick the `Use relative path if possible` option to use relative paths while creating symlinks. In this case, relative paths will be used if both source files/folders and destination files/folders are in the same drive.
- Tick the `Retain script file after execution` option if you want to save the script file for later use like logging purposes.
- Tick the `Retain script file after execution` option if you want to save the script file for later use like logging purposes or other advanced usage.
- Tick the `Hide successful operation dialog` option if you want to only show a dialog when an error occurs.

## Why Symlink Creator needs administrative rights
It has been stated before that Symlink Creator uses the `mklink` command to create symlinks. The `mklink` command requires administrative privilege to create symlinks. You can find more information [here](https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links).

## Donation
If you enjoy using Symlink Creator and also want to buy me a cup of tea, you are most welcome. You can send me a gift here: [PayPal](https://paypal.me/arnobpl)

You can also send me crypto tokens to the following addresses:

<table>
  <thead>
    <tr>
      <th>Blockchain</th>
      <th>QR Code and Address</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Ethereum</td>
      <td>
        <img src="SymlinkCreator/_ReadMe/QR-Ethereum.png" alt="Ethereum QR Code" width="200"><br>
        <code>0x2536B9A9a6b49234db2006482f43d02BEE6FDd07</code>
      </td>
    </tr>
    <tr>
      <td>Bitcoin</td>
      <td>
        <img src="SymlinkCreator/_ReadMe/QR-Bitcoin.png" alt="Bitcoin QR Code" width="200"><br>
        <code>bc1qwhwqal63y629ltnyhvr0txl5xngnhh9dv9u5yf</code>
      </td>
    </tr>
  </tbody>
</table>

Thank you for using Symlink Creator and sharing feedback. Happy Symlinking!
4 changes: 2 additions & 2 deletions SymlinkCreator/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,5 @@
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]

[assembly: AssemblyVersion("1.2.8")]
[assembly: AssemblyFileVersion("1.2.8")]
[assembly: AssemblyVersion("1.3.0")]
[assembly: AssemblyFileVersion("1.3.0")]
12 changes: 12 additions & 0 deletions SymlinkCreator/SymlinkCreator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
<ApplicationIcon>resources\icons\MainIcon.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup />
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Reference Include="Costura, Version=5.7.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>packages\Costura.Fody.5.7.0\lib\netstandard1.0\Costura.dll</HintPath>
Expand Down Expand Up @@ -224,10 +227,14 @@
<Compile Include="core\ScriptExecutor.cs" />
<Compile Include="core\SymlinkAgent.cs" />
<Compile Include="Properties\Annotations.cs" />
<Compile Include="ui\aboutWindow\AboutWindow.xaml.cs">
<DependentUpon>AboutWindow.xaml</DependentUpon>
</Compile>
<Compile Include="ui\mainWindow\MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
</Compile>
<Compile Include="ui\mainWindow\MainWindowViewModel.cs" />
<Compile Include="ui\utility\LongPathAware.cs" />
<Compile Include="ui\utility\NativeAdminShieldIcon.cs" />
<Compile Include="ui\utility\WindowMaximizeButton.cs" />
</ItemGroup>
Expand All @@ -239,12 +246,17 @@
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="app.manifest" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Resource Include="resources\icons\MainIcon.ico" />
</ItemGroup>
<ItemGroup>
<Page Include="ui\aboutWindow\AboutWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="ui\mainWindow\MainWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down
Binary file added SymlinkCreator/_ReadMe/QR-Bitcoin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added SymlinkCreator/_ReadMe/QR-Ethereum.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified SymlinkCreator/_ReadMe/Screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions SymlinkCreator/app.manifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>

<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>

<!-- Windows 10/11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />

</application>
</compatibility>

<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>

</assembly>
5 changes: 4 additions & 1 deletion SymlinkCreator/core/ApplicationConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Reflection;
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;

Expand Down Expand Up @@ -96,6 +97,8 @@ public static string ApplicationCompany

public static string CompanyWebAddress => "https://github.com/arnobpl/SymlinkCreator";

public static Uri CompanyWebAddressUri => new Uri(CompanyWebAddress);

#endregion
}
}
4 changes: 2 additions & 2 deletions SymlinkCreator/core/ScriptExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ private void CreateWrapperScript(string wrapperScriptFileName, string stderrFile
{
StreamWriter wrapperScriptStreamWriter = new StreamWriter(wrapperScriptFileName);
AddUnicodeSupport(wrapperScriptStreamWriter);
// redirect error output to file
// Redirect error output to file
wrapperScriptStreamWriter.WriteLine(
"\"" + Path.GetFullPath(this._fileName) + "\" 2> \"" + Path.GetFullPath(stderrFileName) + "\"");
wrapperScriptStreamWriter.Close();
Expand All @@ -102,7 +102,7 @@ private void ExecuteWrapperScript(string wrapperScriptFileName, string stderrFil

private void AddUnicodeSupport(StreamWriter streamWriter)
{
// set code page to UTF-8 to support unicode file paths
// Set code page to UTF-8 to support unicode file paths
streamWriter.WriteLine("chcp 65001 >NUL");
}

Expand Down
8 changes: 4 additions & 4 deletions SymlinkCreator/core/SymlinkAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ public SymlinkAgent(IEnumerable<string> sourceFileOrFolderList, string destinati

public void CreateSymlinks()
{
// check for destination path
// Check for destination path
if (!Directory.Exists(_destinationPath))
{
throw new FileNotFoundException("Destination path does not exist", _destinationPath);
}

// remove the last '\' character from the path if exists
// Remove the last '\' character from the path if exists
if (_destinationPath[_destinationPath.Length - 1] == '\\')
_destinationPath = _destinationPath.Substring(0, _destinationPath.Length - 1);

Expand Down Expand Up @@ -74,7 +74,7 @@ private ScriptExecutor PrepareScriptExecutor(string scriptFileName)
{
ScriptExecutor scriptExecutor = new ScriptExecutor(scriptFileName);

// go to destination path
// Go to destination path
scriptExecutor.WriteLine(_splittedDestinationPath[0]);
scriptExecutor.WriteLine("cd \"" + _destinationPath + "\"");

Expand All @@ -85,7 +85,7 @@ private ScriptExecutor PrepareScriptExecutor(string scriptFileName)
string commandLineTargetPath = sourceFilePath;
if (_shouldUseRelativePath)
{
// check if both root drives are same
// Check if both root drives are same
if (splittedSourceFilePath.First() == _splittedDestinationPath.First())
{
commandLineTargetPath = GetRelativePath(_splittedDestinationPath, splittedSourceFilePath);
Expand Down
54 changes: 54 additions & 0 deletions SymlinkCreator/ui/aboutWindow/AboutWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<Window x:Class="SymlinkCreator.ui.aboutWindow.AboutWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SymlinkCreator.ui.aboutWindow"
xmlns:core="clr-namespace:SymlinkCreator.core"
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="About"
Width="400"
ResizeMode="NoResize"
WindowStyle="SingleBorderWindow"
SizeToContent="Height">
<Window.Resources>
<Thickness x:Key="DefaultMargin">5</Thickness>
<system:Double x:Key="DefaultMinButtonWidth">80</system:Double>
<system:Double x:Key="DefaultMinButtonHeight">20</system:Double>

<Style TargetType="Button">
<Setter Property="Margin" Value="{StaticResource DefaultMargin}" />
<Setter Property="MinWidth" Value="{StaticResource DefaultMinButtonWidth}" />
<Setter Property="MinHeight" Value="{StaticResource DefaultMinButtonHeight}" />
</Style>

<Style TargetType="TextBlock">
<Setter Property="Margin" Value="{StaticResource DefaultMargin}" />
</Style>
</Window.Resources>
<StackPanel Margin="{StaticResource DefaultMargin}" Orientation="Vertical">
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} v{1}">
<Binding Source="{x:Static core:ApplicationConfiguration.ApplicationName}" />
<Binding Source="{x:Static core:ApplicationConfiguration.ApplicationVersion}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>

<TextBlock Text="Developed by Arnob Paul. Thank you for using this application! :)" />

<TextBlock>
<Run Text="Visit the developer's website:" />
<LineBreak />
<Hyperlink NavigateUri="{x:Static core:ApplicationConfiguration.CompanyWebAddressUri}" Click="Hyperlink_OnClick">
<Run Text="{x:Static core:ApplicationConfiguration.CompanyWebAddress}" />
</Hyperlink>
</TextBlock>

<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="OK" IsDefault="True" Click="OkButton_OnClick" />
</StackPanel>
</StackPanel>
</Window>
45 changes: 45 additions & 0 deletions SymlinkCreator/ui/aboutWindow/AboutWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Documents;

namespace SymlinkCreator.ui.aboutWindow
{
public partial class AboutWindow : Window
{
#region constructor

public AboutWindow()
{
InitializeComponent();
}

#endregion


#region control event handles

private void Hyperlink_OnClick(object sender, RoutedEventArgs e)
{
Hyperlink hyperlink = sender as Hyperlink;
if (hyperlink?.NavigateUri != null)
{
try
{
Process.Start(new ProcessStartInfo(hyperlink.NavigateUri.ToString()) { UseShellExecute = true });
}
catch (Exception ex)
{
MessageBox.Show($"Failed to open link: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}

private void OkButton_OnClick(object sender, RoutedEventArgs e)
{
Close();
}

#endregion
}
}
28 changes: 20 additions & 8 deletions SymlinkCreator/ui/mainWindow/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
d:DataContext="{d:DesignData Type=local:MainWindowViewModel, IsDesignTimeCreatable=True}"
Title="{x:Static core:ApplicationConfiguration.ApplicationName}"
MinWidth="600"
MinHeight="300"
Width="600"
Height="300">
<Window.Title>
<MultiBinding StringFormat="{}{0} v{1}">
<Binding Source="{x:Static core:ApplicationConfiguration.ApplicationName}" />
<Binding Source="{x:Static core:ApplicationConfiguration.ApplicationVersion}" />
</MultiBinding>
</Window.Title>
<Window.Resources>
<Thickness x:Key="DefaultMargin">5</Thickness>
<system:Double x:Key="DefaultMinButtonWidth">80</system:Double>
Expand Down Expand Up @@ -74,13 +79,13 @@
</DockPanel.Resources>

<StackPanel DockPanel.Dock="Left">
<Button Content="Add files" Click="AddFilesButton_OnClick" />
<Button Content="Add folders" Click="AddFoldersButton_OnClick" />
<Button Content="Add files" Click="AddFilesButton_OnClick" ToolTip="Add files to the list" />
<Button Content="Add folders" Click="AddFoldersButton_OnClick" ToolTip="Add folders to the list" />
</StackPanel>

<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
<Button Content="Delete selected" Click="DeleteSelectedButton_OnClick" />
<Button Content="Clear list" Click="ClearListButton_OnClick" />
<Button Content="Remove selected" Click="RemoveSelectedButton_OnClick" ToolTip="Remove selected items from the list" />
<Button Content="Clear list" Click="ClearListButton_OnClick" ToolTip="Clear all items from the list" />
</StackPanel>
</DockPanel>
</Grid>
Expand Down Expand Up @@ -111,7 +116,7 @@
<TextBox Grid.Column="0"
Text="{Binding DestinationPath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ToolTip="{Binding DestinationPath}"
AllowDrop="True" Drop="DestinationPathTextBox_OnDrop"
AllowDrop="True" Drop="DestinationPathTextBox_OnDrop" PreviewDrop="DestinationPathTextBox_OnDrop"
PreviewDragOver="DestinationPathTextBox_OnPreviewDragOver" />

<Button Grid.Column="1" Content="Browse" Click="DestinationPathBrowseButton_OnClick" />
Expand All @@ -120,8 +125,15 @@
<Separator Grid.Row="2" Margin="{StaticResource DefaultMargin}" />

<StackPanel Grid.Row="3">
<CheckBox Content="Use relative path if possible" IsChecked="{Binding ShouldUseRelativePath}" />
<CheckBox Content="Retain script file after execution" IsChecked="{Binding ShouldRetainScriptFile}" />
<CheckBox Content="Use relative path if possible"
IsChecked="{Binding ShouldUseRelativePath}"
ToolTip="Check this to use a relative path if both source and destination are in the same drive." />
<CheckBox Content="Retain script file after execution"
IsChecked="{Binding ShouldRetainScriptFile}"
ToolTip="Check this to save the script in the application's location after creating symlinks." />
<CheckBox Content="Hide successful operation dialog"
IsChecked="{Binding HideSuccessfulOperationDialog}"
ToolTip="Check this to only show a dialog when an error occurs." />
</StackPanel>

<DockPanel Grid.Row="4" LastChildFill="False">
Expand Down
Loading

0 comments on commit 8894993

Please sign in to comment.