diff --git a/NeatShift/NeatShift/App.xaml.cs b/NeatShift/NeatShift/App.xaml.cs index b43e9ee..b399730 100644 --- a/NeatShift/NeatShift/App.xaml.cs +++ b/NeatShift/NeatShift/App.xaml.cs @@ -21,33 +21,6 @@ public partial class App : Application [SupportedOSPlatform("windows7.0")] protected override void OnStartup(StartupEventArgs e) { - // Check if running as administrator - bool isAdmin = IsRunningAsAdministrator(); - if (!isAdmin) - { - // Restart the application with admin rights - try - { - var processInfo = new ProcessStartInfo - { - UseShellExecute = true, - FileName = Process.GetCurrentProcess().MainModule?.FileName, - Verb = "runas" - }; - - Process.Start(processInfo); - Current.Shutdown(); - return; - } - catch (Exception ex) - { - MessageBox.Show($"Failed to restart with administrator privileges: {ex.Message}", - "Error", - MessageBoxButton.OK, - MessageBoxImage.Error); - } - } - var services = new ServiceCollection(); ConfigureServices(services); diff --git a/NeatShift/NeatShift/Services/AdminManager.cs b/NeatShift/NeatShift/Services/AdminManager.cs new file mode 100644 index 0000000..920315d --- /dev/null +++ b/NeatShift/NeatShift/Services/AdminManager.cs @@ -0,0 +1,82 @@ +using System; +using System.Diagnostics; +using System.Security.Principal; +using System.Threading.Tasks; +using ModernWpf.Controls; +using NeatShift.Views; + +namespace NeatShift.Services +{ + public static class AdminManager + { + private static bool _isAdminGranted = false; + + public static bool IsAdminGranted + { + get + { + if (_isAdminGranted) return true; + + // Double check if we're actually running as admin + var identity = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(identity); + _isAdminGranted = principal.IsInRole(WindowsBuiltInRole.Administrator); + + return _isAdminGranted; + } + } + + public static async Task EnsureAdmin(string reason, string actions) + { + if (IsAdminGranted) return true; + + // Show our custom prompt first + var dialog = new AdminPromptDialog(reason, actions); + if (await dialog.ShowAsync() != ContentDialogResult.Primary) + return false; + + try + { + // Try to restart with admin rights + var processInfo = new ProcessStartInfo + { + UseShellExecute = true, + FileName = Process.GetCurrentProcess().MainModule?.FileName, + Verb = "runas" + }; + + Process.Start(processInfo); + // Shutdown the current instance + System.Windows.Application.Current.Shutdown(); + return true; + } + catch (Exception) + { + return false; + } + } + + public static class Messages + { + public static (string Reason, string Actions) SymbolicLink => ( + "Moving files with symbolic links requires administrator access to create special file system links.", + "• Create symbolic links\n• Maintain file access for applications" + ); + + public static (string Reason, string Actions) SystemRestore => ( + "Creating system restore points requires administrator access to modify system protection settings.", + "• Create system restore points\n• Manage system protection" + ); + + public static (string Reason, string Actions) ViewLinks => ( + "Viewing symbolic links requires administrator access to read special file system attributes.", + "• Read symbolic link information\n• View file system attributes" + ); + + public static (string Reason, string Actions) Backup => ( + "Managing backups requires administrator access to create system restore points and manage file system operations.", + "• Create and manage system restore points\n• Access protected file locations for NeatSaves\n• Manage system protection settings" + ); + } + } +} \ No newline at end of file diff --git a/NeatShift/NeatShift/ViewModels/MainWindowViewModel.cs b/NeatShift/NeatShift/ViewModels/MainWindowViewModel.cs index 27db85d..6f9d7dd 100644 --- a/NeatShift/NeatShift/ViewModels/MainWindowViewModel.cs +++ b/NeatShift/NeatShift/ViewModels/MainWindowViewModel.cs @@ -134,8 +134,12 @@ private async Task ViewSymbolicLinks() if (dialog.ShowDialog() == DialogResult.OK && !string.IsNullOrEmpty(dialog.SelectedPath)) { - var linksDialog = new SymbolicLinksDialog(dialog.SelectedPath); - await linksDialog.ShowAsync(); + var (reason, actions) = AdminManager.Messages.ViewLinks; + if (await AdminManager.EnsureAdmin(reason, actions)) + { + var linksDialog = new SymbolicLinksDialog(dialog.SelectedPath); + await linksDialog.ShowAsync(); + } } } @@ -148,43 +152,51 @@ partial void OnIsOperationInProgressChanged(bool value) } [RelayCommand(CanExecute = nameof(CanExecuteFileOperations))] - private void AddFiles() + private async Task AddFiles() { - var dialog = new Microsoft.Win32.OpenFileDialog + var (reason, actions) = AdminManager.Messages.SymbolicLink; + if (await AdminManager.EnsureAdmin(reason, actions)) { - Multiselect = true, - Title = "Select files to move" - }; + var dialog = new Microsoft.Win32.OpenFileDialog + { + Multiselect = true, + Title = "Select files to move" + }; - if (dialog.ShowDialog() == true) - { - foreach (string file in dialog.FileNames) + if (dialog.ShowDialog() == true) { - if (!string.IsNullOrEmpty(file)) + foreach (string file in dialog.FileNames) { - SourceItems.Add(new FileSystemItem(file)); + if (!string.IsNullOrEmpty(file)) + { + SourceItems.Add(new FileSystemItem(file)); + } } } } } [RelayCommand(CanExecute = nameof(CanExecuteFileOperations))] - private void AddFolder() + private async Task AddFolder() { - using var dialog = new CommonOpenFileDialog + var (reason, actions) = AdminManager.Messages.SymbolicLink; + if (await AdminManager.EnsureAdmin(reason, actions)) { - Title = "Select folders to move", - IsFolderPicker = true, - Multiselect = true - }; + using var dialog = new CommonOpenFileDialog + { + Title = "Select folders to move", + IsFolderPicker = true, + Multiselect = true + }; - if (dialog.ShowDialog() == CommonFileDialogResult.Ok) - { - foreach (string folder in dialog.FileNames) + if (dialog.ShowDialog() == CommonFileDialogResult.Ok) { - if (!string.IsNullOrEmpty(folder)) + foreach (string folder in dialog.FileNames) { - SourceItems.Add(new FileSystemItem(folder)); + if (!string.IsNullOrEmpty(folder)) + { + SourceItems.Add(new FileSystemItem(folder)); + } } } } @@ -272,7 +284,12 @@ private async Task Move() return; } - await MoveFiles(); + // Request admin rights if needed + var (reason, actions) = AdminManager.Messages.SymbolicLink; + if (await AdminManager.EnsureAdmin(reason, actions)) + { + await MoveFiles(); + } } private bool CanExecuteFileOperations() => !IsOperationInProgress; @@ -389,8 +406,12 @@ private async Task ShowFeatureRequest() [RelayCommand] private async Task ManageRestorePoints() { - var dialog = new RestorePointDialog(); - await dialog.ShowAsync(); + var (reason, actions) = AdminManager.Messages.Backup; + if (await AdminManager.EnsureAdmin(reason, actions)) + { + var dialog = new RestorePointDialog(); + await dialog.ShowAsync(); + } } [RelayCommand] @@ -554,8 +575,12 @@ private async Task MoveFiles() [RelayCommand] private async Task ManageNeatSaves() { - var dialog = new NeatSavesManagementDialog(_neatSavesService); - await dialog.ShowAsync(); + var (reason, actions) = AdminManager.Messages.Backup; + if (await AdminManager.EnsureAdmin(reason, actions)) + { + var dialog = new NeatSavesManagementDialog(_neatSavesService); + await dialog.ShowAsync(); + } } [RelayCommand] diff --git a/NeatShift/NeatShift/Views/AdminPromptDialog.xaml b/NeatShift/NeatShift/Views/AdminPromptDialog.xaml new file mode 100644 index 0000000..ff4d646 --- /dev/null +++ b/NeatShift/NeatShift/Views/AdminPromptDialog.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/NeatShift/NeatShift/Views/AdminPromptDialog.xaml.cs b/NeatShift/NeatShift/Views/AdminPromptDialog.xaml.cs new file mode 100644 index 0000000..4dff7d8 --- /dev/null +++ b/NeatShift/NeatShift/Views/AdminPromptDialog.xaml.cs @@ -0,0 +1,14 @@ +using ModernWpf.Controls; + +namespace NeatShift.Views +{ + public partial class AdminPromptDialog : ContentDialog + { + public AdminPromptDialog(string reason, string actions) + { + InitializeComponent(); + ReasonText.Text = reason; + ActionText.Text = actions; + } + } +} \ No newline at end of file diff --git a/NeatShift/NeatShift/app.manifest b/NeatShift/NeatShift/app.manifest index 813fdd8..583156d 100644 --- a/NeatShift/NeatShift/app.manifest +++ b/NeatShift/NeatShift/app.manifest @@ -4,7 +4,7 @@ - +