From d521e2d3c072c8c5986cdfdaf1f3c78cbf268899 Mon Sep 17 00:00:00 2001 From: dbuechel Date: Fri, 17 Aug 2018 14:48:50 +0200 Subject: [PATCH] SEBWIN-220: Re-integrated kiosk mode "Disable Explorer Shell". --- .../ProcessMonitorOperationTests.cs | 50 +++++++----- .../Behaviour/ClientController.cs | 8 +- .../Operations/ProcessMonitorOperation.cs | 25 +++--- SafeExamBrowser.Client/CompositionRoot.cs | 14 +++- .../ConfigurationRepository.cs | 2 +- .../Monitoring/IProcessMonitor.cs | 10 --- .../SafeExamBrowser.Contracts.csproj | 1 + .../WindowsApi/IExplorerShell.cs | 23 ++++++ .../Processes/ProcessMonitor.cs | 50 ------------ .../Operations/KioskModeOperation.cs | 49 +++++++----- SafeExamBrowser.Runtime/CompositionRoot.cs | 3 +- SafeExamBrowser.WindowsApi/Desktop.cs | 5 ++ SafeExamBrowser.WindowsApi/DesktopFactory.cs | 24 +++--- SafeExamBrowser.WindowsApi/ExplorerShell.cs | 76 +++++++++++++++++++ SafeExamBrowser.WindowsApi/ProcessFactory.cs | 12 +-- .../SafeExamBrowser.WindowsApi.csproj | 1 + 16 files changed, 220 insertions(+), 133 deletions(-) create mode 100644 SafeExamBrowser.Contracts/WindowsApi/IExplorerShell.cs create mode 100644 SafeExamBrowser.WindowsApi/ExplorerShell.cs diff --git a/SafeExamBrowser.Client.UnitTests/Behaviour/Operations/ProcessMonitorOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Behaviour/Operations/ProcessMonitorOperationTests.cs index 20d75fa7..1261d3fa 100644 --- a/SafeExamBrowser.Client.UnitTests/Behaviour/Operations/ProcessMonitorOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Behaviour/Operations/ProcessMonitorOperationTests.cs @@ -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 loggerMock; - private Mock processMonitorMock; - + private Mock logger; + private Mock processMonitor; + private Settings settings; private ProcessMonitorOperation sut; [TestInitialize] public void Initialize() { - loggerMock = new Mock(); - processMonitorMock = new Mock(); + logger = new Mock(); + processMonitor = new Mock(); + 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); } } } diff --git a/SafeExamBrowser.Client/Behaviour/ClientController.cs b/SafeExamBrowser.Client/Behaviour/ClientController.cs index 78b3cff2..5973a041 100644 --- a/SafeExamBrowser.Client/Behaviour/ClientController.cs +++ b/SafeExamBrowser.Client/Behaviour/ClientController.cs @@ -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..."); diff --git a/SafeExamBrowser.Client/Behaviour/Operations/ProcessMonitorOperation.cs b/SafeExamBrowser.Client/Behaviour/Operations/ProcessMonitorOperation.cs index fe61c71e..0516e0da 100644 --- a/SafeExamBrowser.Client/Behaviour/Operations/ProcessMonitorOperation.cs +++ b/SafeExamBrowser.Client/Behaviour/Operations/ProcessMonitorOperation.cs @@ -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(); + } } } } diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs index f6905150..fa8f0a28 100644 --- a/SafeExamBrowser.Client/CompositionRoot.cs +++ b/SafeExamBrowser.Client/CompositionRoot.cs @@ -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); diff --git a/SafeExamBrowser.Configuration/ConfigurationRepository.cs b/SafeExamBrowser.Configuration/ConfigurationRepository.cs index f3f025f6..01a67cb0 100644 --- a/SafeExamBrowser.Configuration/ConfigurationRepository.cs +++ b/SafeExamBrowser.Configuration/ConfigurationRepository.cs @@ -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"; diff --git a/SafeExamBrowser.Contracts/Monitoring/IProcessMonitor.cs b/SafeExamBrowser.Contracts/Monitoring/IProcessMonitor.cs index 102c75f9..1d4a6993 100644 --- a/SafeExamBrowser.Contracts/Monitoring/IProcessMonitor.cs +++ b/SafeExamBrowser.Contracts/Monitoring/IProcessMonitor.cs @@ -26,16 +26,6 @@ namespace SafeExamBrowser.Contracts.Monitoring /// bool BelongsToAllowedProcess(IntPtr window); - /// - /// Terminates the Windows explorer shell, i.e. the taskbar. - /// - void CloseExplorerShell(); - - /// - /// Starts a new instance of the Windows explorer shell. - /// - void StartExplorerShell(); - /// /// Starts monitoring the Windows explorer, i.e. any newly created instances of explorer.exe will trigger the /// event. diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index aab52f7e..1b846b34 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -182,6 +182,7 @@ + diff --git a/SafeExamBrowser.Contracts/WindowsApi/IExplorerShell.cs b/SafeExamBrowser.Contracts/WindowsApi/IExplorerShell.cs new file mode 100644 index 00000000..492d2e96 --- /dev/null +++ b/SafeExamBrowser.Contracts/WindowsApi/IExplorerShell.cs @@ -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 + { + /// + /// Starts the Windows explorer shell, if it isn't already running. + /// + void Start(); + + /// + /// Gracefully terminates the Windows explorer shell, if it is running. + /// + void Terminate(); + } +} diff --git a/SafeExamBrowser.Monitoring/Processes/ProcessMonitor.cs b/SafeExamBrowser.Monitoring/Processes/ProcessMonitor.cs index 83c7470d..e279a233 100644 --- a/SafeExamBrowser.Monitoring/Processes/ProcessMonitor.cs +++ b/SafeExamBrowser.Monitoring/Processes/ProcessMonitor.cs @@ -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")); diff --git a/SafeExamBrowser.Runtime/Behaviour/Operations/KioskModeOperation.cs b/SafeExamBrowser.Runtime/Behaviour/Operations/KioskModeOperation.cs index 27ada7e1..078089b8 100644 --- a/SafeExamBrowser.Runtime/Behaviour/Operations/KioskModeOperation.cs +++ b/SafeExamBrowser.Runtime/Behaviour/Operations/KioskModeOperation.cs @@ -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(); } } } diff --git a/SafeExamBrowser.Runtime/CompositionRoot.cs b/SafeExamBrowser.Runtime/CompositionRoot.cs index 51448ca7..c5b3a037 100644 --- a/SafeExamBrowser.Runtime/CompositionRoot.cs +++ b/SafeExamBrowser.Runtime/CompositionRoot.cs @@ -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); diff --git a/SafeExamBrowser.WindowsApi/Desktop.cs b/SafeExamBrowser.WindowsApi/Desktop.cs index 1598304f..2971b156 100644 --- a/SafeExamBrowser.WindowsApi/Desktop.cs +++ b/SafeExamBrowser.WindowsApi/Desktop.cs @@ -43,5 +43,10 @@ namespace SafeExamBrowser.WindowsApi throw new Win32Exception(Marshal.GetLastWin32Error()); } } + + public override string ToString() + { + return $"'{Name}' [{Handle}]"; + } } } diff --git a/SafeExamBrowser.WindowsApi/DesktopFactory.cs b/SafeExamBrowser.WindowsApi/DesktopFactory.cs index eb37a8c7..a668987e 100644 --- a/SafeExamBrowser.WindowsApi/DesktopFactory.cs +++ b/SafeExamBrowser.WindowsApi/DesktopFactory.cs @@ -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; } } } diff --git a/SafeExamBrowser.WindowsApi/ExplorerShell.cs b/SafeExamBrowser.WindowsApi/ExplorerShell.cs new file mode 100644 index 00000000..b671619b --- /dev/null +++ b/SafeExamBrowser.WindowsApi/ExplorerShell.cs @@ -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..."); + } + } + } +} diff --git a/SafeExamBrowser.WindowsApi/ProcessFactory.cs b/SafeExamBrowser.WindowsApi/ProcessFactory.cs index e87f1911..72a3c2cc 100644 --- a/SafeExamBrowser.WindowsApi/ProcessFactory.cs +++ b/SafeExamBrowser.WindowsApi/ProcessFactory.cs @@ -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; } } } diff --git a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj index c5dfc3e3..15a43937 100644 --- a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj +++ b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj @@ -59,6 +59,7 @@ +