Skip to content

Commit c0da963

Browse files
committed
[Enhancement] Show error message in case of symlink failure (resolve #11)
Issue: #11
1 parent 9b02efd commit c0da963

File tree

8 files changed

+104
-47
lines changed

8 files changed

+104
-47
lines changed

SymlinkCreator/Properties/Annotations.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22

3-
#pragma warning disable 1591
43
// ReSharper disable UnusedMember.Global
54
// ReSharper disable MemberCanBePrivate.Global
65
// ReSharper disable UnusedAutoPropertyAccessor.Global

SymlinkCreator/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,5 @@
5252
// by using the '*' as shown below:
5353
// [assembly: AssemblyVersion("1.0.*")]
5454

55-
[assembly: AssemblyVersion("1.2.4")]
56-
[assembly: AssemblyFileVersion("1.2.4")]
55+
[assembly: AssemblyVersion("1.2.5")]
56+
[assembly: AssemblyFileVersion("1.2.5")]

SymlinkCreator/SymlinkCreator.sln

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio 14
4-
VisualStudioVersion = 14.0.25420.1
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.8.34408.163
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SymlinkCreator", "SymlinkCreator.csproj", "{D9331BF2-AE1F-4E4D-AB46-C9E99189ACE0}"
77
EndProject
@@ -28,11 +28,12 @@ Global
2828
{0446C461-5775-4B25-815C-FC3C99D2A50C}.Debug|x86.ActiveCfg = Debug|Any CPU
2929
{0446C461-5775-4B25-815C-FC3C99D2A50C}.Debug|x86.Build.0 = Debug|Any CPU
3030
{0446C461-5775-4B25-815C-FC3C99D2A50C}.Release|Any CPU.ActiveCfg = Release|Any CPU
31-
{0446C461-5775-4B25-815C-FC3C99D2A50C}.Release|Any CPU.Build.0 = Release|Any CPU
3231
{0446C461-5775-4B25-815C-FC3C99D2A50C}.Release|x86.ActiveCfg = Release|Any CPU
33-
{0446C461-5775-4B25-815C-FC3C99D2A50C}.Release|x86.Build.0 = Release|Any CPU
3432
EndGlobalSection
3533
GlobalSection(SolutionProperties) = preSolution
3634
HideSolutionNode = FALSE
3735
EndGlobalSection
36+
GlobalSection(ExtensibilityGlobals) = postSolution
37+
SolutionGuid = {CF753F23-242E-429C-B9ED-91FD133160A6}
38+
EndGlobalSection
3839
EndGlobal

SymlinkCreator/core/ApplicationConfiguration.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ public static string ApplicationName
3636
}
3737
}
3838

39+
private static string _applicationFileName;
40+
public static string ApplicationFileName
41+
{
42+
get
43+
{
44+
if (!string.IsNullOrEmpty(_applicationFileName))
45+
return _applicationFileName;
46+
47+
_applicationFileName = Assembly.GetExecutingAssembly().GetName().Name;
48+
return _applicationFileName;
49+
}
50+
}
51+
3952
private static string _applicationVersion;
4053
public static string ApplicationVersion
4154
{

SymlinkCreator/core/ScriptExecutor.cs

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ class ScriptExecutor : StreamWriter
99

1010
private readonly string _fileName;
1111

12+
public int ExitCode { get; private set; } = -1;
13+
public string StandardError { get; private set; } = "";
14+
1215
#endregion
1316

1417

@@ -29,19 +32,57 @@ public ScriptExecutor(string fileName) : base(fileName)
2932
/// </summary>
3033
public void ExecuteAsAdmin()
3134
{
32-
Close();
35+
this.Close();
36+
37+
// Wrapper script is required for both executing the actual script as admin and
38+
// redirecting the error output simultaneously.
39+
string wrapperScriptFileName = this._fileName + "_exe.cmd";
40+
string stderrFileName = this._fileName + "_err.txt";
41+
42+
try
43+
{
44+
File.Create(stderrFileName).Dispose();
45+
CreateWrapperScript(wrapperScriptFileName, stderrFileName);
46+
ExecuteWrapperScript(wrapperScriptFileName, stderrFileName);
47+
}
48+
finally
49+
{
50+
File.Delete(wrapperScriptFileName);
51+
File.Delete(stderrFileName);
52+
}
53+
}
54+
55+
#endregion
56+
57+
58+
#region helper methods
59+
60+
private void CreateWrapperScript(string wrapperScriptFileName, string stderrFileName)
61+
{
62+
StreamWriter wrapperScriptStreamWriter = new StreamWriter(wrapperScriptFileName);
63+
// redirect error output to file
64+
wrapperScriptStreamWriter.WriteLine(
65+
"\"" + Path.GetFullPath(this._fileName) + "\" 2> \"" + Path.GetFullPath(stderrFileName) + "\"");
66+
wrapperScriptStreamWriter.Close();
67+
}
3368

69+
private void ExecuteWrapperScript(string wrapperScriptFileName, string stderrFileName)
70+
{
3471
ProcessStartInfo processStartInfo = new ProcessStartInfo
3572
{
36-
FileName = _fileName,
73+
FileName = wrapperScriptFileName,
3774
UseShellExecute = true,
3875
Verb = "runas"
3976
};
40-
Process process = Process.Start(processStartInfo);
41-
if (process == null) return;
42-
43-
process.WaitForExit();
44-
process.Close();
77+
using (Process process = Process.Start(processStartInfo))
78+
{
79+
if (process != null)
80+
{
81+
process.WaitForExit();
82+
this.ExitCode = process.ExitCode;
83+
this.StandardError = File.ReadAllText(stderrFileName);
84+
}
85+
}
4586
}
4687

4788
#endregion

SymlinkCreator/core/SymlinkAgent.cs

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,28 @@ public void CreateSymlinks()
5050

5151
_splittedDestinationPath = GetSplittedPath(_destinationPath);
5252

53-
string scriptFileName = ApplicationConfiguration.ApplicationName + "_" +
54-
DateTime.Now.Ticks.ToString() + ".bat";
53+
string scriptFileName = ApplicationConfiguration.ApplicationFileName + "_" +
54+
DateTime.Now.Ticks.ToString() + ".cmd";
55+
56+
ScriptExecutor scriptExecutor = PrepareScriptExecutor(scriptFileName);
57+
scriptExecutor.ExecuteAsAdmin();
58+
59+
if (!_shouldRetainScriptFile)
60+
File.Delete(scriptFileName);
61+
62+
if (scriptExecutor.ExitCode != 0)
63+
{
64+
throw new ApplicationException("Symlink script exited with an error.\n" + scriptExecutor.StandardError);
65+
}
66+
}
67+
68+
#endregion
69+
70+
71+
#region helper methods
72+
73+
private ScriptExecutor PrepareScriptExecutor(string scriptFileName)
74+
{
5575
ScriptExecutor scriptExecutor = new ScriptExecutor(scriptFileName);
5676

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

86-
scriptExecutor.ExecuteAsAdmin();
87-
88-
if (!_shouldRetainScriptFile)
89-
File.Delete(scriptFileName);
106+
return scriptExecutor;
90107
}
91108

92-
#endregion
93-
94-
95-
#region helper methods
96-
97109
private string[] GetSplittedPath(string path)
98110
{
99111
return path.Split('\\');
@@ -136,11 +148,6 @@ private string GetRelativePath(string[] splittedCurrentPath, string[] splittedTa
136148
return relativePathStringBuilder.ToString();
137149
}
138150

139-
private string GetRelativePath(string currentPath, string targetPath)
140-
{
141-
return GetRelativePath(GetSplittedPath(currentPath), GetSplittedPath(targetPath));
142-
}
143-
144151
#endregion
145152
}
146153
}

SymlinkCreator/ui/mainWindow/MainWindow.xaml.cs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,7 @@ private void AddFolderButton_OnClick(object sender, RoutedEventArgs e)
8989

9090
private void DestinationPathBrowseButton_OnClick(object sender, RoutedEventArgs e)
9191
{
92-
MainWindowViewModel mainWindowViewModel = this.DataContext as MainWindowViewModel;
93-
if (mainWindowViewModel == null) return;
92+
if (!(this.DataContext is MainWindowViewModel mainWindowViewModel)) return;
9493

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

108107
private void DeleteSelectedButton_OnClick(object sender, RoutedEventArgs e)
109108
{
110-
MainWindowViewModel mainWindowViewModel = this.DataContext as MainWindowViewModel;
111-
if (mainWindowViewModel == null) return;
109+
if (!(this.DataContext is MainWindowViewModel mainWindowViewModel)) return;
112110

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

154152
private void CreateSymlinksButton_OnClick(object sender, RoutedEventArgs e)
155153
{
156-
MainWindowViewModel mainWindowViewModel = this.DataContext as MainWindowViewModel;
157-
if (mainWindowViewModel == null) return;
154+
if (!(this.DataContext is MainWindowViewModel mainWindowViewModel)) return;
158155

159156
SymlinkAgent symlinkAgent = new SymlinkAgent(
160157
mainWindowViewModel.FileOrFolderList,
@@ -165,11 +162,11 @@ private void CreateSymlinksButton_OnClick(object sender, RoutedEventArgs e)
165162
try
166163
{
167164
symlinkAgent.CreateSymlinks();
168-
MessageBox.Show("Execution completed.", "Done!", MessageBoxButton.OK, MessageBoxImage.Information);
165+
MessageBox.Show(this, "Execution completed.", "Done!", MessageBoxButton.OK, MessageBoxImage.Information);
169166
}
170-
catch (FileNotFoundException ex)
167+
catch (Exception ex)
171168
{
172-
MessageBox.Show(ex.Message + ":\n" + ex.FileName, "Error!", MessageBoxButton.OK, MessageBoxImage.Error);
169+
MessageBox.Show(this, ex.Message, "Error!", MessageBoxButton.OK, MessageBoxImage.Error);
173170
}
174171
}
175172

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

194191
private void AddToSourceFileOrFolderList(IEnumerable<string> fileOrFolderList)
195192
{
196-
MainWindowViewModel mainWindowViewModel = this.DataContext as MainWindowViewModel;
197-
if (mainWindowViewModel == null) return;
193+
if (!(this.DataContext is MainWindowViewModel mainWindowViewModel)) return;
198194

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

208204
private void AddToSourceFileOrFolderList(string fileOrFolderPath)
209205
{
210-
MainWindowViewModel mainWindowViewModel = this.DataContext as MainWindowViewModel;
211-
if (mainWindowViewModel == null) return;
206+
if (!(this.DataContext is MainWindowViewModel mainWindowViewModel)) return;
212207

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

219214
private void AssignDestinationPath(string destinationPath)
220215
{
221-
MainWindowViewModel mainWindowViewModel = this.DataContext as MainWindowViewModel;
222-
if (mainWindowViewModel == null) return;
216+
if (!(this.DataContext is MainWindowViewModel mainWindowViewModel)) return;
223217

224218
if (Directory.Exists(destinationPath))
225219
mainWindowViewModel.DestinationPath = destinationPath;

SymlinkCreator/ui/utility/NativeAdminShieldIcon.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ public static BitmapSource GetNativeShieldIcon()
3333

3434
if (Environment.OSVersion.Version.Major >= 6)
3535
{
36-
SHSTOCKICONINFO sii = new SHSTOCKICONINFO();
37-
sii.cbSize = (UInt32)Marshal.SizeOf(typeof(SHSTOCKICONINFO));
36+
SHSTOCKICONINFO sii = new SHSTOCKICONINFO
37+
{
38+
cbSize = (UInt32)Marshal.SizeOf(typeof(SHSTOCKICONINFO))
39+
};
3840

3941
Marshal.ThrowExceptionForHR(SHGetStockIconInfo(SHSTOCKICONID.SIID_SHIELD,
4042
SHGSI.SHGSI_ICON | SHGSI.SHGSI_SMALLICON,

0 commit comments

Comments
 (0)