SEBWIN-220: Re-integrated kiosk mode "Disable Explorer Shell".

This commit is contained in:
dbuechel 2018-08-17 14:48:50 +02:00
parent 729133ac78
commit d521e2d3c0
16 changed files with 220 additions and 133 deletions

View file

@ -9,6 +9,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Client.Behaviour.Operations;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
@ -17,46 +18,57 @@ namespace SafeExamBrowser.Client.UnitTests.Behaviour.Operations
[TestClass]
public class ProcessMonitorOperationTests
{
private Mock<ILogger> loggerMock;
private Mock<IProcessMonitor> processMonitorMock;
private Mock<ILogger> logger;
private Mock<IProcessMonitor> processMonitor;
private Settings settings;
private ProcessMonitorOperation sut;
[TestInitialize]
public void Initialize()
{
loggerMock = new Mock<ILogger>();
processMonitorMock = new Mock<IProcessMonitor>();
logger = new Mock<ILogger>();
processMonitor = new Mock<IProcessMonitor>();
settings = new Settings();
sut = new ProcessMonitorOperation(loggerMock.Object, processMonitorMock.Object);
sut = new ProcessMonitorOperation(logger.Object, processMonitor.Object,settings);
}
[TestMethod]
public void MustPerformCorrectly()
public void MustObserveExplorerWithDisableExplorerShell()
{
var order = 0;
var counter = 0;
var start = 0;
var stop = 0;
processMonitorMock.Setup(p => p.CloseExplorerShell()).Callback(() => Assert.AreEqual(++order, 1));
processMonitorMock.Setup(p => p.StartMonitoringExplorer()).Callback(() => Assert.AreEqual(++order, 2));
settings.KioskMode = KioskMode.DisableExplorerShell;
processMonitor.Setup(p => p.StartMonitoringExplorer()).Callback(() => start = ++counter);
processMonitor.Setup(p => p.StopMonitoringExplorer()).Callback(() => stop = ++counter);
sut.Perform();
sut.Revert();
processMonitorMock.Verify(p => p.CloseExplorerShell(), Times.Once);
processMonitorMock.Verify(p => p.StartMonitoringExplorer(), Times.Once);
processMonitor.Verify(p => p.StartMonitoringExplorer(), Times.Once);
processMonitor.Verify(p => p.StopMonitoringExplorer(), Times.Once);
Assert.AreEqual(1, start);
Assert.AreEqual(2, stop);
}
[TestMethod]
public void MustRevertCorrectly()
public void MustNotObserveExplorerWithOtherKioskModes()
{
var order = 0;
processMonitorMock.Setup(p => p.StopMonitoringExplorer()).Callback(() => Assert.AreEqual(++order, 1));
processMonitorMock.Setup(p => p.StartExplorerShell()).Callback(() => Assert.AreEqual(++order, 2));
settings.KioskMode = KioskMode.CreateNewDesktop;
sut.Perform();
sut.Revert();
processMonitorMock.Verify(p => p.StopMonitoringExplorer(), Times.Once);
processMonitorMock.Verify(p => p.StartExplorerShell(), Times.Once);
settings.KioskMode = KioskMode.None;
sut.Perform();
sut.Revert();
processMonitor.Verify(p => p.StartMonitoringExplorer(), Times.Never);
processMonitor.Verify(p => p.StopMonitoringExplorer(), Times.Never);
}
}
}

View file

@ -25,12 +25,14 @@ using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
using SafeExamBrowser.Contracts.UserInterface.Windows;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Client.Behaviour
{
internal class ClientController : IClientController
{
private IDisplayMonitor displayMonitor;
private IExplorerShell explorerShell;
private ILogger logger;
private IMessageBox messageBox;
private IOperationSequence operations;
@ -64,6 +66,7 @@ namespace SafeExamBrowser.Client.Behaviour
public ClientController(
IDisplayMonitor displayMonitor,
IExplorerShell explorerShell,
ILogger logger,
IMessageBox messageBox,
IOperationSequence operations,
@ -76,6 +79,7 @@ namespace SafeExamBrowser.Client.Behaviour
IWindowMonitor windowMonitor)
{
this.displayMonitor = displayMonitor;
this.explorerShell = explorerShell;
this.logger = logger;
this.messageBox = messageBox;
this.operations = operations;
@ -188,8 +192,8 @@ namespace SafeExamBrowser.Client.Behaviour
private void ProcessMonitor_ExplorerStarted()
{
logger.Info("Trying to shut down explorer...");
processMonitor.CloseExplorerShell();
logger.Info("Trying to terminate Windows explorer...");
explorerShell.Terminate();
logger.Info("Reinitializing working area...");
displayMonitor.InitializePrimaryDisplay(taskbar.GetAbsoluteHeight());
logger.Info("Reinitializing taskbar bounds...");

View file

@ -7,6 +7,7 @@
*/
using SafeExamBrowser.Contracts.Behaviour.OperationModel;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
@ -18,26 +19,26 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
{
private ILogger logger;
private IProcessMonitor processMonitor;
private Settings settings;
public IProgressIndicator ProgressIndicator { private get; set; }
public ProcessMonitorOperation(ILogger logger, IProcessMonitor processMonitor)
public ProcessMonitorOperation(ILogger logger, IProcessMonitor processMonitor, Settings settings)
{
this.logger = logger;
this.processMonitor = processMonitor;
this.settings = settings;
}
public OperationResult Perform()
{
logger.Info("Initializing process monitoring...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_WaitExplorerTermination, true);
processMonitor.CloseExplorerShell();
processMonitor.StartMonitoringExplorer();
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeProcessMonitoring);
// TODO: Implement process monitoring...
if (settings.KioskMode == KioskMode.DisableExplorerShell)
{
processMonitor.StartMonitoringExplorer();
}
return OperationResult.Success;
}
@ -52,12 +53,10 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
logger.Info("Stopping process monitoring...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StopProcessMonitoring);
// TODO: Implement process monitoring...
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_WaitExplorerStartup, true);
processMonitor.StopMonitoringExplorer();
processMonitor.StartExplorerShell();
if (settings.KioskMode == KioskMode.DisableExplorerShell)
{
processMonitor.StopMonitoringExplorer();
}
}
}
}

View file

@ -23,6 +23,7 @@ using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
using SafeExamBrowser.Contracts.WindowsApi;
@ -53,6 +54,7 @@ namespace SafeExamBrowser.Client
private IClientHost clientHost;
private ILogger logger;
private IMessageBox messageBox;
private IProcessMonitor processMonitor;
private INativeMethods nativeMethods;
private IRuntimeProxy runtimeProxy;
private ISystemInfo systemInfo;
@ -75,11 +77,12 @@ namespace SafeExamBrowser.Client
text = new Text(logger);
messageBox = new MessageBox(text);
processMonitor = new ProcessMonitor(new ModuleLogger(logger, typeof(ProcessMonitor)), nativeMethods);
uiFactory = new UserInterfaceFactory(text);
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), new ModuleLogger(logger, typeof(RuntimeProxy)));
var displayMonitor = new DisplayMonitor(new ModuleLogger(logger, typeof(DisplayMonitor)), nativeMethods);
var processMonitor = new ProcessMonitor(new ModuleLogger(logger, typeof(ProcessMonitor)), nativeMethods);
var explorerShell = new ExplorerShell(new ModuleLogger(logger, typeof(ExplorerShell)), nativeMethods);
var windowMonitor = new WindowMonitor(new ModuleLogger(logger, typeof(WindowMonitor)), nativeMethods);
Taskbar = new Taskbar(new ModuleLogger(logger, typeof(Taskbar)));
@ -94,7 +97,7 @@ namespace SafeExamBrowser.Client
// TODO
//operations.Enqueue(new DelayedInitializationOperation(BuildKeyboardInterceptorOperation));
//operations.Enqueue(new WindowMonitorOperation(logger, windowMonitor));
//operations.Enqueue(new ProcessMonitorOperation(logger, processMonitor));
operations.Enqueue(new DelayedInitializationOperation(BuildProcessMonitorOperation));
operations.Enqueue(new DisplayMonitorOperation(displayMonitor, logger, Taskbar));
operations.Enqueue(new DelayedInitializationOperation(BuildTaskbarOperation));
operations.Enqueue(new DelayedInitializationOperation(BuildBrowserOperation));
@ -104,7 +107,7 @@ namespace SafeExamBrowser.Client
var sequence = new OperationSequence(logger, operations);
ClientController = new ClientController(displayMonitor, logger, messageBox, sequence, processMonitor, runtimeProxy, shutdown, Taskbar, text, uiFactory, windowMonitor);
ClientController = new ClientController(displayMonitor, explorerShell, logger, messageBox, sequence, processMonitor, runtimeProxy, shutdown, Taskbar, text, uiFactory, windowMonitor);
}
internal void LogStartupInformation()
@ -191,6 +194,11 @@ namespace SafeExamBrowser.Client
return operation;
}
private IOperation BuildProcessMonitorOperation()
{
return new ProcessMonitorOperation(logger, processMonitor, configuration.Settings);
}
private IOperation BuildTaskbarOperation()
{
var keyboardLayout = new KeyboardLayout(new ModuleLogger(logger, typeof(KeyboardLayout)), text);

View file

@ -81,7 +81,7 @@ namespace SafeExamBrowser.Configuration
CurrentSettings = new Settings();
CurrentSettings.KioskMode = KioskMode.CreateNewDesktop;
CurrentSettings.KioskMode = KioskMode.DisableExplorerShell;
CurrentSettings.ServicePolicy = ServicePolicy.Optional;
CurrentSettings.Browser.StartUrl = "https://www.safeexambrowser.org/testing";

View file

@ -26,16 +26,6 @@ namespace SafeExamBrowser.Contracts.Monitoring
/// </summary>
bool BelongsToAllowedProcess(IntPtr window);
/// <summary>
/// Terminates the Windows explorer shell, i.e. the taskbar.
/// </summary>
void CloseExplorerShell();
/// <summary>
/// Starts a new instance of the Windows explorer shell.
/// </summary>
void StartExplorerShell();
/// <summary>
/// Starts monitoring the Windows explorer, i.e. any newly created instances of <c>explorer.exe</c> will trigger the
/// <see cref="ExplorerStarted"/> event.

View file

@ -182,6 +182,7 @@
<Compile Include="WindowsApi\IBounds.cs" />
<Compile Include="WindowsApi\IDesktop.cs" />
<Compile Include="WindowsApi\IDesktopFactory.cs" />
<Compile Include="WindowsApi\IExplorerShell.cs" />
<Compile Include="WindowsApi\INativeMethods.cs" />
<Compile Include="WindowsApi\IProcess.cs" />
<Compile Include="WindowsApi\IProcessFactory.cs" />

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.WindowsApi
{
public interface IExplorerShell
{
/// <summary>
/// Starts the Windows explorer shell, if it isn't already running.
/// </summary>
void Start();
/// <summary>
/// Gracefully terminates the Windows explorer shell, if it is running.
/// </summary>
void Terminate();
}
}

View file

@ -8,10 +8,7 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.Threading;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.Monitoring.Events;
@ -53,53 +50,6 @@ namespace SafeExamBrowser.Monitoring.Processes
return true;
}
public void CloseExplorerShell()
{
var processId = nativeMethods.GetShellProcessId();
var explorerProcesses = Process.GetProcessesByName("explorer");
var shellProcess = explorerProcesses.FirstOrDefault(p => p.Id == processId);
if (shellProcess != null)
{
logger.Info($"Found explorer shell processes with PID = {processId}. Sending close message...");
nativeMethods.PostCloseMessageToShell();
while (!shellProcess.HasExited)
{
shellProcess.Refresh();
Thread.Sleep(20);
}
logger.Info($"Successfully terminated explorer shell process with PID = {processId}.");
}
else
{
logger.Info("The explorer shell seems to already be terminated. Skipping this step...");
}
}
public void StartExplorerShell()
{
var process = new Process();
var explorerPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe");
logger.Info("Restarting explorer shell...");
process.StartInfo.CreateNoWindow = true;
process.StartInfo.FileName = explorerPath;
process.Start();
while (nativeMethods.GetShellWindowHandle() == IntPtr.Zero)
{
Thread.Sleep(20);
}
process.Refresh();
logger.Info($"Explorer shell successfully started with PID = {process.Id}.");
process.Close();
}
public void StartMonitoringExplorer()
{
explorerWatcher = new ManagementEventWatcher(@"\\.\root\CIMV2", GetQueryFor("explorer.exe"));

View file

@ -20,6 +20,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
{
private IConfigurationRepository configuration;
private IDesktopFactory desktopFactory;
private IExplorerShell explorerShell;
private KioskMode kioskMode;
private ILogger logger;
private IProcessFactory processFactory;
@ -31,11 +32,13 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
public KioskModeOperation(
IConfigurationRepository configuration,
IDesktopFactory desktopFactory,
IExplorerShell explorerShell,
ILogger logger,
IProcessFactory processFactory)
{
this.configuration = configuration;
this.desktopFactory = desktopFactory;
this.explorerShell = explorerShell;
this.logger = logger;
this.processFactory = processFactory;
}
@ -53,7 +56,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
CreateNewDesktop();
break;
case KioskMode.DisableExplorerShell:
DisableExplorerShell();
TerminateExplorerShell();
break;
}
@ -62,7 +65,20 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
public OperationResult Repeat()
{
// TODO: Depends on new kiosk mode!
var oldMode = kioskMode;
var newMode = configuration.CurrentSettings.KioskMode;
if (newMode == oldMode)
{
logger.Info($"New kiosk mode '{newMode}' is equal to the currently active '{oldMode}', skipping re-initialization...");
}
else
{
Revert();
Perform();
}
kioskMode = newMode;
return OperationResult.Success;
}
@ -86,12 +102,14 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
private void CreateNewDesktop()
{
originalDesktop = desktopFactory.GetCurrent();
logger.Info($"Current desktop is {ToString(originalDesktop)}.");
logger.Info($"Current desktop is {originalDesktop}.");
newDesktop = desktopFactory.CreateNew(nameof(SafeExamBrowser));
logger.Info($"Created new desktop {ToString(newDesktop)}.");
logger.Info($"Created new desktop {newDesktop}.");
newDesktop.Activate();
logger.Info("Successfully activated new desktop.");
processFactory.StartupDesktop = newDesktop;
logger.Info("Successfully activated new desktop.");
}
private void CloseNewDesktop()
@ -100,37 +118,34 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
{
originalDesktop.Activate();
processFactory.StartupDesktop = originalDesktop;
logger.Info($"Switched back to original desktop {ToString(originalDesktop)}.");
logger.Info($"Switched back to original desktop {originalDesktop}.");
}
else
{
logger.Warn($"No original desktop found when attempting to revert kiosk mode '{kioskMode}'!");
logger.Warn($"No original desktop found when attempting to close new desktop!");
}
if (newDesktop != null)
{
newDesktop.Close();
logger.Info($"Closed new desktop {ToString(newDesktop)}.");
logger.Info($"Closed new desktop {newDesktop}.");
}
else
{
logger.Warn($"No new desktop found when attempting to revert kiosk mode '{kioskMode}'!");
logger.Warn($"No new desktop found when attempting to close new desktop!");
}
}
private void DisableExplorerShell()
private void TerminateExplorerShell()
{
// TODO
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_WaitExplorerTermination, true);
explorerShell.Terminate();
}
private void RestartExplorerShell()
{
// TODO
}
private string ToString(IDesktop desktop)
{
return $"'{desktop.Name}' [{desktop.Handle}]";
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_WaitExplorerStartup, true);
explorerShell.Start();
}
}
}

View file

@ -51,6 +51,7 @@ namespace SafeExamBrowser.Runtime
var text = new Text(logger);
var messageBox = new MessageBox(text);
var desktopFactory = new DesktopFactory(new ModuleLogger(logger, typeof(DesktopFactory)));
var explorerShell = new ExplorerShell(new ModuleLogger(logger, typeof(ExplorerShell)), nativeMethods);
var processFactory = new ProcessFactory(new ModuleLogger(logger, typeof(ProcessFactory)));
var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), logger);
var resourceLoader = new ResourceLoader();
@ -68,7 +69,7 @@ namespace SafeExamBrowser.Runtime
sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost));
sessionOperations.Enqueue(new ServiceOperation(configuration, logger, serviceProxy, text));
sessionOperations.Enqueue(new ClientTerminationOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, TEN_SECONDS));
sessionOperations.Enqueue(new KioskModeOperation(configuration, desktopFactory, logger, processFactory));
sessionOperations.Enqueue(new KioskModeOperation(configuration, desktopFactory, explorerShell, logger, processFactory));
sessionOperations.Enqueue(new ClientOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, TEN_SECONDS));
var bootstrapSequence = new OperationSequence(logger, bootstrapOperations);

View file

@ -43,5 +43,10 @@ namespace SafeExamBrowser.WindowsApi
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
public override string ToString()
{
return $"'{Name}' [{Handle}]";
}
}
}

View file

@ -37,9 +37,11 @@ namespace SafeExamBrowser.WindowsApi
throw new Win32Exception(Marshal.GetLastWin32Error());
}
logger.Debug($"Successfully created desktop '{name}' [{handle}].");
var desktop = new Desktop(handle, name);
return new Desktop(handle, name);
logger.Debug($"Successfully created desktop {desktop}.");
return desktop;
}
public IDesktop GetCurrent()
@ -49,17 +51,15 @@ namespace SafeExamBrowser.WindowsApi
var name = String.Empty;
var nameLength = 0;
if (handle != IntPtr.Zero)
if (handle == IntPtr.Zero)
{
logger.Debug($"Found desktop handle {handle} for thread {threadId}. Attempting to get desktop name...");
}
else
{
logger.Error($"Failed to get desktop handle for thread {threadId}!");
logger.Error($"Failed to get desktop handle for thread with ID = {threadId}!");
throw new Win32Exception(Marshal.GetLastWin32Error());
}
logger.Debug($"Found desktop handle for thread with ID = {threadId}. Attempting to get desktop name...");
User32.GetUserObjectInformation(handle, Constant.UOI_NAME, IntPtr.Zero, 0, ref nameLength);
var namePointer = Marshal.AllocHGlobal(nameLength);
@ -67,7 +67,7 @@ namespace SafeExamBrowser.WindowsApi
if (!success)
{
logger.Error($"Failed to retrieve name for desktop with handle {handle}!");
logger.Error($"Failed to retrieve name for desktop with handle = {handle}!");
throw new Win32Exception(Marshal.GetLastWin32Error());
}
@ -75,9 +75,11 @@ namespace SafeExamBrowser.WindowsApi
name = Marshal.PtrToStringAnsi(namePointer);
Marshal.FreeHGlobal(namePointer);
logger.Debug($"Successfully determined current desktop as '{name}' [{handle}].");
var desktop = new Desktop(handle, name);
return new Desktop(handle, name);
logger.Debug($"Successfully determined current desktop {desktop}.");
return desktop;
}
}
}

View file

@ -0,0 +1,76 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.IO;
using System.Linq;
using System.Threading;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.WindowsApi
{
public class ExplorerShell : IExplorerShell
{
private ILogger logger;
private INativeMethods nativeMethods;
public ExplorerShell(ILogger logger, INativeMethods nativeMethods)
{
this.logger = logger;
this.nativeMethods = nativeMethods;
}
public void Start()
{
var process = new System.Diagnostics.Process();
var explorerPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe");
logger.Info("Restarting explorer shell...");
process.StartInfo.CreateNoWindow = true;
process.StartInfo.FileName = explorerPath;
process.Start();
while (nativeMethods.GetShellWindowHandle() == IntPtr.Zero)
{
Thread.Sleep(20);
}
process.Refresh();
logger.Info($"Explorer shell successfully started with PID = {process.Id}.");
process.Close();
}
public void Terminate()
{
var processId = nativeMethods.GetShellProcessId();
var explorerProcesses = System.Diagnostics.Process.GetProcessesByName("explorer");
var shellProcess = explorerProcesses.FirstOrDefault(p => p.Id == processId);
if (shellProcess != null)
{
logger.Debug($"Found explorer shell processes with PID = {processId}. Sending close message...");
nativeMethods.PostCloseMessageToShell();
while (!shellProcess.HasExited)
{
shellProcess.Refresh();
Thread.Sleep(20);
}
logger.Info($"Successfully terminated explorer shell process with PID = {processId}.");
}
else
{
logger.Info("The explorer shell seems to already be terminated. Skipping this step...");
}
}
}
}

View file

@ -41,18 +41,18 @@ namespace SafeExamBrowser.WindowsApi
var success = Kernel32.CreateProcess(null, commandLine, IntPtr.Zero, IntPtr.Zero, true, Constant.NORMAL_PRIORITY_CLASS, IntPtr.Zero, null, ref startupInfo, ref processInfo);
if (success)
{
logger.Info($"Successfully started process '{Path.GetFileName(path)}' with ID {processInfo.dwProcessId}.");
}
else
if (!success)
{
logger.Error($"Failed to start process '{Path.GetFileName(path)}'!");
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return new Process(processInfo.dwProcessId);
var process = new Process(processInfo.dwProcessId);
logger.Info($"Successfully started process '{Path.GetFileName(path)}' with ID = {process.Id}.");
return process;
}
}
}

View file

@ -59,6 +59,7 @@
<Compile Include="Delegates\HookDelegate.cs" />
<Compile Include="Desktop.cs" />
<Compile Include="DesktopFactory.cs" />
<Compile Include="ExplorerShell.cs" />
<Compile Include="Monitoring\MouseHook.cs" />
<Compile Include="Process.cs" />
<Compile Include="ProcessFactory.cs" />