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 Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq; using Moq;
using SafeExamBrowser.Client.Behaviour.Operations; using SafeExamBrowser.Client.Behaviour.Operations;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.Contracts.Monitoring;
@ -17,46 +18,57 @@ namespace SafeExamBrowser.Client.UnitTests.Behaviour.Operations
[TestClass] [TestClass]
public class ProcessMonitorOperationTests public class ProcessMonitorOperationTests
{ {
private Mock<ILogger> loggerMock; private Mock<ILogger> logger;
private Mock<IProcessMonitor> processMonitorMock; private Mock<IProcessMonitor> processMonitor;
private Settings settings;
private ProcessMonitorOperation sut; private ProcessMonitorOperation sut;
[TestInitialize] [TestInitialize]
public void Initialize() public void Initialize()
{ {
loggerMock = new Mock<ILogger>(); logger = new Mock<ILogger>();
processMonitorMock = new Mock<IProcessMonitor>(); processMonitor = new Mock<IProcessMonitor>();
settings = new Settings();
sut = new ProcessMonitorOperation(loggerMock.Object, processMonitorMock.Object); sut = new ProcessMonitorOperation(logger.Object, processMonitor.Object,settings);
} }
[TestMethod] [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)); settings.KioskMode = KioskMode.DisableExplorerShell;
processMonitorMock.Setup(p => p.StartMonitoringExplorer()).Callback(() => Assert.AreEqual(++order, 2)); processMonitor.Setup(p => p.StartMonitoringExplorer()).Callback(() => start = ++counter);
processMonitor.Setup(p => p.StopMonitoringExplorer()).Callback(() => stop = ++counter);
sut.Perform(); sut.Perform();
sut.Revert();
processMonitorMock.Verify(p => p.CloseExplorerShell(), Times.Once); processMonitor.Verify(p => p.StartMonitoringExplorer(), Times.Once);
processMonitorMock.Verify(p => p.StartMonitoringExplorer(), Times.Once); processMonitor.Verify(p => p.StopMonitoringExplorer(), Times.Once);
Assert.AreEqual(1, start);
Assert.AreEqual(2, stop);
} }
[TestMethod] [TestMethod]
public void MustRevertCorrectly() public void MustNotObserveExplorerWithOtherKioskModes()
{ {
var order = 0; settings.KioskMode = KioskMode.CreateNewDesktop;
processMonitorMock.Setup(p => p.StopMonitoringExplorer()).Callback(() => Assert.AreEqual(++order, 1));
processMonitorMock.Setup(p => p.StartExplorerShell()).Callback(() => Assert.AreEqual(++order, 2));
sut.Perform();
sut.Revert(); sut.Revert();
processMonitorMock.Verify(p => p.StopMonitoringExplorer(), Times.Once); settings.KioskMode = KioskMode.None;
processMonitorMock.Verify(p => p.StartExplorerShell(), Times.Once);
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.MessageBox;
using SafeExamBrowser.Contracts.UserInterface.Taskbar; using SafeExamBrowser.Contracts.UserInterface.Taskbar;
using SafeExamBrowser.Contracts.UserInterface.Windows; using SafeExamBrowser.Contracts.UserInterface.Windows;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Client.Behaviour namespace SafeExamBrowser.Client.Behaviour
{ {
internal class ClientController : IClientController internal class ClientController : IClientController
{ {
private IDisplayMonitor displayMonitor; private IDisplayMonitor displayMonitor;
private IExplorerShell explorerShell;
private ILogger logger; private ILogger logger;
private IMessageBox messageBox; private IMessageBox messageBox;
private IOperationSequence operations; private IOperationSequence operations;
@ -64,6 +66,7 @@ namespace SafeExamBrowser.Client.Behaviour
public ClientController( public ClientController(
IDisplayMonitor displayMonitor, IDisplayMonitor displayMonitor,
IExplorerShell explorerShell,
ILogger logger, ILogger logger,
IMessageBox messageBox, IMessageBox messageBox,
IOperationSequence operations, IOperationSequence operations,
@ -76,6 +79,7 @@ namespace SafeExamBrowser.Client.Behaviour
IWindowMonitor windowMonitor) IWindowMonitor windowMonitor)
{ {
this.displayMonitor = displayMonitor; this.displayMonitor = displayMonitor;
this.explorerShell = explorerShell;
this.logger = logger; this.logger = logger;
this.messageBox = messageBox; this.messageBox = messageBox;
this.operations = operations; this.operations = operations;
@ -188,8 +192,8 @@ namespace SafeExamBrowser.Client.Behaviour
private void ProcessMonitor_ExplorerStarted() private void ProcessMonitor_ExplorerStarted()
{ {
logger.Info("Trying to shut down explorer..."); logger.Info("Trying to terminate Windows explorer...");
processMonitor.CloseExplorerShell(); explorerShell.Terminate();
logger.Info("Reinitializing working area..."); logger.Info("Reinitializing working area...");
displayMonitor.InitializePrimaryDisplay(taskbar.GetAbsoluteHeight()); displayMonitor.InitializePrimaryDisplay(taskbar.GetAbsoluteHeight());
logger.Info("Reinitializing taskbar bounds..."); logger.Info("Reinitializing taskbar bounds...");

View file

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

View file

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

View file

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

View file

@ -26,16 +26,6 @@ namespace SafeExamBrowser.Contracts.Monitoring
/// </summary> /// </summary>
bool BelongsToAllowedProcess(IntPtr window); 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> /// <summary>
/// Starts monitoring the Windows explorer, i.e. any newly created instances of <c>explorer.exe</c> will trigger the /// Starts monitoring the Windows explorer, i.e. any newly created instances of <c>explorer.exe</c> will trigger the
/// <see cref="ExplorerStarted"/> event. /// <see cref="ExplorerStarted"/> event.

View file

@ -182,6 +182,7 @@
<Compile Include="WindowsApi\IBounds.cs" /> <Compile Include="WindowsApi\IBounds.cs" />
<Compile Include="WindowsApi\IDesktop.cs" /> <Compile Include="WindowsApi\IDesktop.cs" />
<Compile Include="WindowsApi\IDesktopFactory.cs" /> <Compile Include="WindowsApi\IDesktopFactory.cs" />
<Compile Include="WindowsApi\IExplorerShell.cs" />
<Compile Include="WindowsApi\INativeMethods.cs" /> <Compile Include="WindowsApi\INativeMethods.cs" />
<Compile Include="WindowsApi\IProcess.cs" /> <Compile Include="WindowsApi\IProcess.cs" />
<Compile Include="WindowsApi\IProcessFactory.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;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management; using System.Management;
using System.Threading;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.Monitoring.Events; using SafeExamBrowser.Contracts.Monitoring.Events;
@ -53,53 +50,6 @@ namespace SafeExamBrowser.Monitoring.Processes
return true; 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() public void StartMonitoringExplorer()
{ {
explorerWatcher = new ManagementEventWatcher(@"\\.\root\CIMV2", GetQueryFor("explorer.exe")); explorerWatcher = new ManagementEventWatcher(@"\\.\root\CIMV2", GetQueryFor("explorer.exe"));

View file

@ -20,6 +20,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
{ {
private IConfigurationRepository configuration; private IConfigurationRepository configuration;
private IDesktopFactory desktopFactory; private IDesktopFactory desktopFactory;
private IExplorerShell explorerShell;
private KioskMode kioskMode; private KioskMode kioskMode;
private ILogger logger; private ILogger logger;
private IProcessFactory processFactory; private IProcessFactory processFactory;
@ -31,11 +32,13 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
public KioskModeOperation( public KioskModeOperation(
IConfigurationRepository configuration, IConfigurationRepository configuration,
IDesktopFactory desktopFactory, IDesktopFactory desktopFactory,
IExplorerShell explorerShell,
ILogger logger, ILogger logger,
IProcessFactory processFactory) IProcessFactory processFactory)
{ {
this.configuration = configuration; this.configuration = configuration;
this.desktopFactory = desktopFactory; this.desktopFactory = desktopFactory;
this.explorerShell = explorerShell;
this.logger = logger; this.logger = logger;
this.processFactory = processFactory; this.processFactory = processFactory;
} }
@ -53,7 +56,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
CreateNewDesktop(); CreateNewDesktop();
break; break;
case KioskMode.DisableExplorerShell: case KioskMode.DisableExplorerShell:
DisableExplorerShell(); TerminateExplorerShell();
break; break;
} }
@ -62,7 +65,20 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
public OperationResult Repeat() 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; return OperationResult.Success;
} }
@ -86,12 +102,14 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
private void CreateNewDesktop() private void CreateNewDesktop()
{ {
originalDesktop = desktopFactory.GetCurrent(); originalDesktop = desktopFactory.GetCurrent();
logger.Info($"Current desktop is {ToString(originalDesktop)}."); logger.Info($"Current desktop is {originalDesktop}.");
newDesktop = desktopFactory.CreateNew(nameof(SafeExamBrowser)); newDesktop = desktopFactory.CreateNew(nameof(SafeExamBrowser));
logger.Info($"Created new desktop {ToString(newDesktop)}."); logger.Info($"Created new desktop {newDesktop}.");
newDesktop.Activate(); newDesktop.Activate();
logger.Info("Successfully activated new desktop.");
processFactory.StartupDesktop = newDesktop; processFactory.StartupDesktop = newDesktop;
logger.Info("Successfully activated new desktop.");
} }
private void CloseNewDesktop() private void CloseNewDesktop()
@ -100,37 +118,34 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
{ {
originalDesktop.Activate(); originalDesktop.Activate();
processFactory.StartupDesktop = originalDesktop; processFactory.StartupDesktop = originalDesktop;
logger.Info($"Switched back to original desktop {ToString(originalDesktop)}."); logger.Info($"Switched back to original desktop {originalDesktop}.");
} }
else 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) if (newDesktop != null)
{ {
newDesktop.Close(); newDesktop.Close();
logger.Info($"Closed new desktop {ToString(newDesktop)}."); logger.Info($"Closed new desktop {newDesktop}.");
} }
else 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() private void RestartExplorerShell()
{ {
// TODO ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_WaitExplorerStartup, true);
} explorerShell.Start();
private string ToString(IDesktop desktop)
{
return $"'{desktop.Name}' [{desktop.Handle}]";
} }
} }
} }

View file

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

View file

@ -43,5 +43,10 @@ namespace SafeExamBrowser.WindowsApi
throw new Win32Exception(Marshal.GetLastWin32Error()); 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()); 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() public IDesktop GetCurrent()
@ -49,17 +51,15 @@ namespace SafeExamBrowser.WindowsApi
var name = String.Empty; var name = String.Empty;
var nameLength = 0; 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..."); logger.Error($"Failed to get desktop handle for thread with ID = {threadId}!");
}
else
{
logger.Error($"Failed to get desktop handle for thread {threadId}!");
throw new Win32Exception(Marshal.GetLastWin32Error()); 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); User32.GetUserObjectInformation(handle, Constant.UOI_NAME, IntPtr.Zero, 0, ref nameLength);
var namePointer = Marshal.AllocHGlobal(nameLength); var namePointer = Marshal.AllocHGlobal(nameLength);
@ -67,7 +67,7 @@ namespace SafeExamBrowser.WindowsApi
if (!success) 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()); throw new Win32Exception(Marshal.GetLastWin32Error());
} }
@ -75,9 +75,11 @@ namespace SafeExamBrowser.WindowsApi
name = Marshal.PtrToStringAnsi(namePointer); name = Marshal.PtrToStringAnsi(namePointer);
Marshal.FreeHGlobal(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); var success = Kernel32.CreateProcess(null, commandLine, IntPtr.Zero, IntPtr.Zero, true, Constant.NORMAL_PRIORITY_CLASS, IntPtr.Zero, null, ref startupInfo, ref processInfo);
if (success) if (!success)
{
logger.Info($"Successfully started process '{Path.GetFileName(path)}' with ID {processInfo.dwProcessId}.");
}
else
{ {
logger.Error($"Failed to start process '{Path.GetFileName(path)}'!"); logger.Error($"Failed to start process '{Path.GetFileName(path)}'!");
throw new Win32Exception(Marshal.GetLastWin32Error()); 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="Delegates\HookDelegate.cs" />
<Compile Include="Desktop.cs" /> <Compile Include="Desktop.cs" />
<Compile Include="DesktopFactory.cs" /> <Compile Include="DesktopFactory.cs" />
<Compile Include="ExplorerShell.cs" />
<Compile Include="Monitoring\MouseHook.cs" /> <Compile Include="Monitoring\MouseHook.cs" />
<Compile Include="Process.cs" /> <Compile Include="Process.cs" />
<Compile Include="ProcessFactory.cs" /> <Compile Include="ProcessFactory.cs" />