Skip to content

Commit

Permalink
[Enhancement] Show error message in case of symlink failure (resolve #11
Browse files Browse the repository at this point in the history
)

Issue: #11
  • Loading branch information
arnobpl committed Jan 13, 2024
1 parent 9b02efd commit c0da963
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 47 deletions.
1 change: 0 additions & 1 deletion SymlinkCreator/Properties/Annotations.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;

#pragma warning disable 1591
// ReSharper disable UnusedMember.Global
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
Expand Down
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.4")]
[assembly: AssemblyFileVersion("1.2.4")]
[assembly: AssemblyVersion("1.2.5")]
[assembly: AssemblyFileVersion("1.2.5")]
9 changes: 5 additions & 4 deletions SymlinkCreator/SymlinkCreator.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
# Visual Studio Version 17
VisualStudioVersion = 17.8.34408.163
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SymlinkCreator", "SymlinkCreator.csproj", "{D9331BF2-AE1F-4E4D-AB46-C9E99189ACE0}"
EndProject
Expand All @@ -28,11 +28,12 @@ Global
{0446C461-5775-4B25-815C-FC3C99D2A50C}.Debug|x86.ActiveCfg = Debug|Any CPU
{0446C461-5775-4B25-815C-FC3C99D2A50C}.Debug|x86.Build.0 = Debug|Any CPU
{0446C461-5775-4B25-815C-FC3C99D2A50C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0446C461-5775-4B25-815C-FC3C99D2A50C}.Release|Any CPU.Build.0 = Release|Any CPU
{0446C461-5775-4B25-815C-FC3C99D2A50C}.Release|x86.ActiveCfg = Release|Any CPU
{0446C461-5775-4B25-815C-FC3C99D2A50C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CF753F23-242E-429C-B9ED-91FD133160A6}
EndGlobalSection
EndGlobal
13 changes: 13 additions & 0 deletions SymlinkCreator/core/ApplicationConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ public static string ApplicationName
}
}

private static string _applicationFileName;
public static string ApplicationFileName
{
get
{
if (!string.IsNullOrEmpty(_applicationFileName))
return _applicationFileName;

_applicationFileName = Assembly.GetExecutingAssembly().GetName().Name;
return _applicationFileName;
}
}

private static string _applicationVersion;
public static string ApplicationVersion
{
Expand Down
55 changes: 48 additions & 7 deletions SymlinkCreator/core/ScriptExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ class ScriptExecutor : StreamWriter

private readonly string _fileName;

public int ExitCode { get; private set; } = -1;
public string StandardError { get; private set; } = "";

#endregion


Expand All @@ -29,19 +32,57 @@ public ScriptExecutor(string fileName) : base(fileName)
/// </summary>
public void ExecuteAsAdmin()
{
Close();
this.Close();

// Wrapper script is required for both executing the actual script as admin and
// redirecting the error output simultaneously.
string wrapperScriptFileName = this._fileName + "_exe.cmd";
string stderrFileName = this._fileName + "_err.txt";

try
{
File.Create(stderrFileName).Dispose();
CreateWrapperScript(wrapperScriptFileName, stderrFileName);
ExecuteWrapperScript(wrapperScriptFileName, stderrFileName);
}
finally
{
File.Delete(wrapperScriptFileName);
File.Delete(stderrFileName);
}
}

#endregion


#region helper methods

private void CreateWrapperScript(string wrapperScriptFileName, string stderrFileName)
{
StreamWriter wrapperScriptStreamWriter = new StreamWriter(wrapperScriptFileName);
// redirect error output to file
wrapperScriptStreamWriter.WriteLine(
"\"" + Path.GetFullPath(this._fileName) + "\" 2> \"" + Path.GetFullPath(stderrFileName) + "\"");
wrapperScriptStreamWriter.Close();
}

private void ExecuteWrapperScript(string wrapperScriptFileName, string stderrFileName)
{
ProcessStartInfo processStartInfo = new ProcessStartInfo
{
FileName = _fileName,
FileName = wrapperScriptFileName,
UseShellExecute = true,
Verb = "runas"
};
Process process = Process.Start(processStartInfo);
if (process == null) return;

process.WaitForExit();
process.Close();
using (Process process = Process.Start(processStartInfo))
{
if (process != null)
{
process.WaitForExit();
this.ExitCode = process.ExitCode;
this.StandardError = File.ReadAllText(stderrFileName);
}
}
}

#endregion
Expand Down
39 changes: 23 additions & 16 deletions SymlinkCreator/core/SymlinkAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,28 @@ public void CreateSymlinks()

_splittedDestinationPath = GetSplittedPath(_destinationPath);

string scriptFileName = ApplicationConfiguration.ApplicationName + "_" +
DateTime.Now.Ticks.ToString() + ".bat";
string scriptFileName = ApplicationConfiguration.ApplicationFileName + "_" +
DateTime.Now.Ticks.ToString() + ".cmd";

ScriptExecutor scriptExecutor = PrepareScriptExecutor(scriptFileName);
scriptExecutor.ExecuteAsAdmin();

if (!_shouldRetainScriptFile)
File.Delete(scriptFileName);

if (scriptExecutor.ExitCode != 0)
{
throw new ApplicationException("Symlink script exited with an error.\n" + scriptExecutor.StandardError);
}
}

#endregion


#region helper methods

private ScriptExecutor PrepareScriptExecutor(string scriptFileName)
{
ScriptExecutor scriptExecutor = new ScriptExecutor(scriptFileName);

// set code page to UTF-8 to support unicode file paths
Expand Down Expand Up @@ -83,17 +103,9 @@ public void CreateSymlinks()
"\"" + commandLineTargetPath + "\"");
}

scriptExecutor.ExecuteAsAdmin();

if (!_shouldRetainScriptFile)
File.Delete(scriptFileName);
return scriptExecutor;
}

#endregion


#region helper methods

private string[] GetSplittedPath(string path)
{
return path.Split('\\');
Expand Down Expand Up @@ -136,11 +148,6 @@ private string GetRelativePath(string[] splittedCurrentPath, string[] splittedTa
return relativePathStringBuilder.ToString();
}

private string GetRelativePath(string currentPath, string targetPath)
{
return GetRelativePath(GetSplittedPath(currentPath), GetSplittedPath(targetPath));
}

#endregion
}
}
24 changes: 9 additions & 15 deletions SymlinkCreator/ui/mainWindow/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,7 @@ private void AddFolderButton_OnClick(object sender, RoutedEventArgs e)

private void DestinationPathBrowseButton_OnClick(object sender, RoutedEventArgs e)
{
MainWindowViewModel mainWindowViewModel = this.DataContext as MainWindowViewModel;
if (mainWindowViewModel == null) return;
if (!(this.DataContext is MainWindowViewModel mainWindowViewModel)) return;

CommonOpenFileDialog folderBrowserDialog = new CommonOpenFileDialog
{
Expand All @@ -107,8 +106,7 @@ private void DestinationPathBrowseButton_OnClick(object sender, RoutedEventArgs

private void DeleteSelectedButton_OnClick(object sender, RoutedEventArgs e)
{
MainWindowViewModel mainWindowViewModel = this.DataContext as MainWindowViewModel;
if (mainWindowViewModel == null) return;
if (!(this.DataContext is MainWindowViewModel mainWindowViewModel)) return;

List<string> selectedFileOrFolderList = SourceFileOrFolderListView.SelectedItems.Cast<string>().ToList();
foreach (var selectedItem in selectedFileOrFolderList)
Expand Down Expand Up @@ -153,8 +151,7 @@ private void DestinationPathTextBox_OnPreviewDragOver(object sender, DragEventAr

private void CreateSymlinksButton_OnClick(object sender, RoutedEventArgs e)
{
MainWindowViewModel mainWindowViewModel = this.DataContext as MainWindowViewModel;
if (mainWindowViewModel == null) return;
if (!(this.DataContext is MainWindowViewModel mainWindowViewModel)) return;

SymlinkAgent symlinkAgent = new SymlinkAgent(
mainWindowViewModel.FileOrFolderList,
Expand All @@ -165,11 +162,11 @@ private void CreateSymlinksButton_OnClick(object sender, RoutedEventArgs e)
try
{
symlinkAgent.CreateSymlinks();
MessageBox.Show("Execution completed.", "Done!", MessageBoxButton.OK, MessageBoxImage.Information);
MessageBox.Show(this, "Execution completed.", "Done!", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (FileNotFoundException ex)
catch (Exception ex)
{
MessageBox.Show(ex.Message + ":\n" + ex.FileName, "Error!", MessageBoxButton.OK, MessageBoxImage.Error);
MessageBox.Show(this, ex.Message, "Error!", MessageBoxButton.OK, MessageBoxImage.Error);
}
}

Expand All @@ -193,8 +190,7 @@ private void AboutButton_OnClick(object sender, RoutedEventArgs e)

private void AddToSourceFileOrFolderList(IEnumerable<string> fileOrFolderList)
{
MainWindowViewModel mainWindowViewModel = this.DataContext as MainWindowViewModel;
if (mainWindowViewModel == null) return;
if (!(this.DataContext is MainWindowViewModel mainWindowViewModel)) return;

foreach (string fileOrFolder in fileOrFolderList)
{
Expand All @@ -207,8 +203,7 @@ private void AddToSourceFileOrFolderList(IEnumerable<string> fileOrFolderList)

private void AddToSourceFileOrFolderList(string fileOrFolderPath)
{
MainWindowViewModel mainWindowViewModel = this.DataContext as MainWindowViewModel;
if (mainWindowViewModel == null) return;
if (!(this.DataContext is MainWindowViewModel mainWindowViewModel)) return;

if (!mainWindowViewModel.FileOrFolderList.Contains(fileOrFolderPath))
{
Expand All @@ -218,8 +213,7 @@ private void AddToSourceFileOrFolderList(string fileOrFolderPath)

private void AssignDestinationPath(string destinationPath)
{
MainWindowViewModel mainWindowViewModel = this.DataContext as MainWindowViewModel;
if (mainWindowViewModel == null) return;
if (!(this.DataContext is MainWindowViewModel mainWindowViewModel)) return;

if (Directory.Exists(destinationPath))
mainWindowViewModel.DestinationPath = destinationPath;
Expand Down
6 changes: 4 additions & 2 deletions SymlinkCreator/ui/utility/NativeAdminShieldIcon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ public static BitmapSource GetNativeShieldIcon()

if (Environment.OSVersion.Version.Major >= 6)
{
SHSTOCKICONINFO sii = new SHSTOCKICONINFO();
sii.cbSize = (UInt32)Marshal.SizeOf(typeof(SHSTOCKICONINFO));
SHSTOCKICONINFO sii = new SHSTOCKICONINFO
{
cbSize = (UInt32)Marshal.SizeOf(typeof(SHSTOCKICONINFO))
};

Marshal.ThrowExceptionForHR(SHGetStockIconInfo(SHSTOCKICONID.SIID_SHIELD,
SHGSI.SHGSI_ICON | SHGSI.SHGSI_SMALLICON,
Expand Down

0 comments on commit c0da963

Please sign in to comment.