diff --git a/SymlinkCreator/Properties/Annotations.cs b/SymlinkCreator/Properties/Annotations.cs index 31e7a30..fa57605 100644 --- a/SymlinkCreator/Properties/Annotations.cs +++ b/SymlinkCreator/Properties/Annotations.cs @@ -1,6 +1,5 @@ using System; -#pragma warning disable 1591 // ReSharper disable UnusedMember.Global // ReSharper disable MemberCanBePrivate.Global // ReSharper disable UnusedAutoPropertyAccessor.Global diff --git a/SymlinkCreator/Properties/AssemblyInfo.cs b/SymlinkCreator/Properties/AssemblyInfo.cs index 2e103e3..c4bca25 100644 --- a/SymlinkCreator/Properties/AssemblyInfo.cs +++ b/SymlinkCreator/Properties/AssemblyInfo.cs @@ -52,5 +52,5 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.4")] -[assembly: AssemblyFileVersion("1.2.4")] \ No newline at end of file +[assembly: AssemblyVersion("1.2.5")] +[assembly: AssemblyFileVersion("1.2.5")] \ No newline at end of file diff --git a/SymlinkCreator/SymlinkCreator.sln b/SymlinkCreator/SymlinkCreator.sln index a3d7f68..d72329d 100644 --- a/SymlinkCreator/SymlinkCreator.sln +++ b/SymlinkCreator/SymlinkCreator.sln @@ -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 @@ -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 diff --git a/SymlinkCreator/core/ApplicationConfiguration.cs b/SymlinkCreator/core/ApplicationConfiguration.cs index f057633..6f1c278 100644 --- a/SymlinkCreator/core/ApplicationConfiguration.cs +++ b/SymlinkCreator/core/ApplicationConfiguration.cs @@ -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 { diff --git a/SymlinkCreator/core/ScriptExecutor.cs b/SymlinkCreator/core/ScriptExecutor.cs index 373a9a5..9d433d8 100644 --- a/SymlinkCreator/core/ScriptExecutor.cs +++ b/SymlinkCreator/core/ScriptExecutor.cs @@ -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 @@ -29,19 +32,57 @@ public ScriptExecutor(string fileName) : base(fileName) /// 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 diff --git a/SymlinkCreator/core/SymlinkAgent.cs b/SymlinkCreator/core/SymlinkAgent.cs index f599502..b87e8b5 100644 --- a/SymlinkCreator/core/SymlinkAgent.cs +++ b/SymlinkCreator/core/SymlinkAgent.cs @@ -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 @@ -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('\\'); @@ -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 } } \ No newline at end of file diff --git a/SymlinkCreator/ui/mainWindow/MainWindow.xaml.cs b/SymlinkCreator/ui/mainWindow/MainWindow.xaml.cs index 859dd81..a30e952 100644 --- a/SymlinkCreator/ui/mainWindow/MainWindow.xaml.cs +++ b/SymlinkCreator/ui/mainWindow/MainWindow.xaml.cs @@ -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 { @@ -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 selectedFileOrFolderList = SourceFileOrFolderListView.SelectedItems.Cast().ToList(); foreach (var selectedItem in selectedFileOrFolderList) @@ -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, @@ -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); } } @@ -193,8 +190,7 @@ private void AboutButton_OnClick(object sender, RoutedEventArgs e) private void AddToSourceFileOrFolderList(IEnumerable fileOrFolderList) { - MainWindowViewModel mainWindowViewModel = this.DataContext as MainWindowViewModel; - if (mainWindowViewModel == null) return; + if (!(this.DataContext is MainWindowViewModel mainWindowViewModel)) return; foreach (string fileOrFolder in fileOrFolderList) { @@ -207,8 +203,7 @@ private void AddToSourceFileOrFolderList(IEnumerable 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)) { @@ -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; diff --git a/SymlinkCreator/ui/utility/NativeAdminShieldIcon.cs b/SymlinkCreator/ui/utility/NativeAdminShieldIcon.cs index 266506b..3c53bb0 100644 --- a/SymlinkCreator/ui/utility/NativeAdminShieldIcon.cs +++ b/SymlinkCreator/ui/utility/NativeAdminShieldIcon.cs @@ -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,