Admin on demand (#9)

This commit is contained in:
BytexGrid
2025-01-09 02:25:59 +05:30
committed by GitHub
parent e909a26571
commit f401b768a9
6 changed files with 177 additions and 58 deletions

View File

@@ -21,33 +21,6 @@ namespace NeatShift
[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);

View File

@@ -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<bool> 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"
);
}
}
}

View File

@@ -134,8 +134,12 @@ namespace NeatShift.ViewModels
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 @@ namespace NeatShift.ViewModels
}
[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"
};
if (dialog.ShowDialog() == true)
{
foreach (string file in dialog.FileNames)
var dialog = new Microsoft.Win32.OpenFileDialog
{
if (!string.IsNullOrEmpty(file))
Multiselect = true,
Title = "Select files to move"
};
if (dialog.ShowDialog() == true)
{
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
};
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
{
foreach (string folder in dialog.FileNames)
using var dialog = new CommonOpenFileDialog
{
if (!string.IsNullOrEmpty(folder))
Title = "Select folders to move",
IsFolderPicker = true,
Multiselect = true
};
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
{
foreach (string folder in dialog.FileNames)
{
SourceItems.Add(new FileSystemItem(folder));
if (!string.IsNullOrEmpty(folder))
{
SourceItems.Add(new FileSystemItem(folder));
}
}
}
}
@@ -272,7 +284,12 @@ namespace NeatShift.ViewModels
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 @@ namespace NeatShift.ViewModels
[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 @@ namespace NeatShift.ViewModels
[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]

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8" ?>
<ui:ContentDialog
x:Class="NeatShift.Views.AdminPromptDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="http://schemas.modernwpf.com/2019"
Title="Administrator Access Required"
PrimaryButtonText="Continue"
CloseButtonText="Cancel"
DefaultButton="Primary">
<StackPanel Margin="0,10,0,0">
<TextBlock x:Name="ReasonText"
TextWrapping="Wrap"
Margin="0,0,0,20"/>
<TextBlock Text="This action requires administrator privileges to:"
FontWeight="SemiBold"
Margin="0,0,0,10"/>
<TextBlock x:Name="ActionText"
TextWrapping="Wrap"
Margin="20,0,0,0"/>
</StackPanel>
</ui:ContentDialog>

View File

@@ -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;
}
}
}

View File

@@ -4,7 +4,7 @@
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>