// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using ManagedCommon; using PowerToys.FileLocksmithLib.Interop; namespace PowerToys.FileLocksmithUI.ViewModels { #pragma warning disable CA1708 // Identifiers should differ by more than case public partial class MainViewModel : ObservableObject, IDisposable #pragma warning restore CA1708 // Identifiers should differ by more than case { public IAsyncRelayCommand LoadProcessesCommand { get; } private bool _isLoading; private bool _isElevated; private string[] paths; private bool _disposed; private CancellationTokenSource _cancelProcessWatching; public ObservableCollection Processes { get; } = new(); public bool IsLoading { get { return _isLoading; } set { _isLoading = value; OnPropertyChanged(nameof(IsLoading)); } } public bool IsElevated { get { return _isElevated; } set { _isElevated = value; OnPropertyChanged(nameof(IsElevated)); } } public string[] Paths { get => paths; set { paths = value; OnPropertyChanged(nameof(Paths)); } } public string PathsToString { get { return string.Join("\n", paths); } } public MainViewModel() { paths = NativeMethods.ReadPathsFromFile(); Logger.LogInfo($"Starting FileLocksmith with {paths.Length} files selected."); LoadProcessesCommand = new AsyncRelayCommand(LoadProcessesAsync); } private async Task LoadProcessesAsync() { IsLoading = true; Processes.Clear(); if (_cancelProcessWatching is not null) { _cancelProcessWatching.Cancel(); } _cancelProcessWatching = new CancellationTokenSource(); var processes_found = await FindProcesses(paths); if (processes_found is not null) { foreach (ProcessResult p in processes_found) { Processes.Add(p); WatchProcess(p, _cancelProcessWatching.Token); } } IsLoading = false; } private async Task> FindProcesses(string[] paths) { var results = new List(); await Task.Run(() => { results = NativeMethods.FindProcessesRecursive(paths)?.ToList(); }); return results; } private async void WatchProcess(ProcessResult process, CancellationToken token) { try { Process handle = Process.GetProcessById((int)process.pid); try { await handle.WaitForExitAsync(token); } catch (TaskCanceledException) { // Nothing to do, normal operation } if (handle.HasExited) { Processes.Remove(process); } } catch (Exception ex) { Logger.LogError($"Couldn't add a waiter to wait for a process to exit. PID = {process.pid} and Name = {process.name}.", ex); Processes.Remove(process); // If we couldn't get a handle to the process or it has exited in the meanwhile, don't show it. } } [RelayCommand] public void EndTask(ProcessResult selectedProcess) { try { Process handle = Process.GetProcessById((int)selectedProcess.pid); try { handle.Kill(); } catch (Exception ex) { Logger.LogError($"Couldn't kill process {selectedProcess.name} with PID {selectedProcess.pid}.", ex); } } catch (Exception ex) { Logger.LogError($"Couldn't get a handle to kill process {selectedProcess.name} with PID {selectedProcess.pid}. Likely has been killed already.", ex); Processes.Remove(selectedProcess); // If we couldn't get a handle to the process, remove it from the list, since it's likely been killed already. } } [RelayCommand] public void RestartElevated() { if (NativeMethods.StartAsElevated(paths)) { // TODO gentler exit Environment.Exit(0); } else { // TODO report error? Logger.LogError($"Couldn't restart as elevated."); } } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { _disposed = true; } } } } }