Skip to content

Commit

Permalink
Admin on demand (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
BytexGrid authored Jan 8, 2025
1 parent e909a26 commit f401b76
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 56 deletions.
27 changes: 0 additions & 27 deletions NeatShift/NeatShift/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
82 changes: 82 additions & 0 deletions NeatShift/NeatShift/Services/AdminManager.cs
Original file line number Diff line number Diff line change
@@ -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"
);
}
}
}
81 changes: 53 additions & 28 deletions NeatShift/NeatShift/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}

Expand All @@ -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));
}
}
}
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down
25 changes: 25 additions & 0 deletions NeatShift/NeatShift/Views/AdminPromptDialog.xaml
Original file line number Diff line number Diff line change
@@ -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>
14 changes: 14 additions & 0 deletions NeatShift/NeatShift/Views/AdminPromptDialog.xaml.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
2 changes: 1 addition & 1 deletion NeatShift/NeatShift/app.manifest
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down

0 comments on commit f401b76

Please sign in to comment.