SEBWIN-220: Re-integrated kiosk mode "Disable Explorer Shell".
This commit is contained in:
parent
729133ac78
commit
d521e2d3c0
16 changed files with 220 additions and 133 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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...");
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
23
SafeExamBrowser.Contracts/WindowsApi/IExplorerShell.cs
Normal file
23
SafeExamBrowser.Contracts/WindowsApi/IExplorerShell.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"));
|
||||||
|
|
|
@ -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}]";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -43,5 +43,10 @@ namespace SafeExamBrowser.WindowsApi
|
||||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"'{Name}' [{Handle}]";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
76
SafeExamBrowser.WindowsApi/ExplorerShell.cs
Normal file
76
SafeExamBrowser.WindowsApi/ExplorerShell.cs
Normal 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...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
Loading…
Reference in a new issue