SEBWIN-320: Implemented scaffolding for reset utility as console application.

This commit is contained in:
dbuechel 2019-07-16 14:39:59 +02:00
parent 58043a2ed9
commit 71854b33d8
16 changed files with 742 additions and 0 deletions

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>

View file

@ -0,0 +1,63 @@
/*
* Copyright (c) 2019 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 SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Logging;
using SafeExamBrowser.ResetUtility.Procedure;
namespace SafeExamBrowser.ResetUtility
{
internal class CompositionRoot
{
private ILogger logger;
internal ProcedureStep InitialStep { get; private set; }
internal NativeMethods NativeMethods { get; private set; }
internal void BuildObjectGraph()
{
InitializeLogging();
var context = new Context
{
Logger = logger,
};
InitialStep = new Initialization(context);
NativeMethods = new NativeMethods();
}
internal void LogStartupInformation()
{
logger.Log($"# Application started at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
logger.Log(string.Empty);
}
internal void LogShutdownInformation()
{
logger.Log(string.Empty);
logger?.Log($"# Application terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
private void InitializeLogging()
{
var appDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), nameof(SafeExamBrowser));
var logFolder = Path.Combine(appDataFolder, "Logs");
var logFilePrefix = DateTime.Now.ToString("yyyy-MM-dd\\_HH\\hmm\\mss\\s");
var logFilePath = Path.Combine(logFolder, $"{logFilePrefix}_{nameof(ResetUtility)}.log");
var logFileWriter = new LogFileWriter(new DefaultLogFormatter(), logFilePath);
logger = new Logger();
logger.LogLevel = LogLevel.Debug;
logger.Subscribe(logFileWriter);
logFileWriter.Initialize();
}
}
}

View file

@ -0,0 +1,17 @@
/*
* Copyright (c) 2019 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 SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.ResetUtility
{
internal class Context
{
internal ILogger Logger { get; set; }
}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2019 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.Runtime.InteropServices;
namespace SafeExamBrowser.ResetUtility
{
internal class NativeMethods
{
private const int GWL_STYLE = -16;
private const int MF_BYCOMMAND = 0x0;
private const int SC_MAXIMIZE = 0xF030;
private const int SC_MINIMIZE = 0xF020;
private const int SC_SIZE = 0xF000;
private const int WS_MAXIMIZEBOX = 0x10000;
private const int WS_MINIMIZEBOX = 0x20000;
internal void TryDisableSystemMenu()
{
try
{
// DeleteMenu(GetSystemMenu(GetConsoleWindow(), false), SC_MINIMIZE, MF_BYCOMMAND);
DeleteMenu(GetSystemMenu(GetConsoleWindow(), false), SC_MAXIMIZE, MF_BYCOMMAND);
DeleteMenu(GetSystemMenu(GetConsoleWindow(), false), SC_SIZE, MF_BYCOMMAND);
// SetWindowLong(GetConsoleWindow(), GWL_STYLE, GetWindowLong(GetConsoleWindow(), GWL_STYLE) & ~WS_MINIMIZEBOX);
SetWindowLong(GetConsoleWindow(), GWL_STYLE, GetWindowLong(GetConsoleWindow(), GWL_STYLE) & ~WS_MAXIMIZEBOX);
}
catch(Exception) { }
}
[DllImport("user32.dll")]
private static extern int DeleteMenu(IntPtr hMenu, int nPosition, int wFlags);
[DllImport("kernel32.dll")]
private static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll")]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
}
}

View file

@ -0,0 +1,95 @@
/*
* Copyright (c) 2019 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.Security.Principal;
using System.Threading;
namespace SafeExamBrowser.ResetUtility.Procedure
{
internal class Initialization : ProcedureStep
{
private static readonly Mutex mutex = new Mutex(true, "safe_exam_browser_reset_mutex");
public Initialization(Context context) : base(context)
{
}
internal override ProcedureStepResult Execute()
{
Initialize();
if (IsSingleInstance() && HasAdminPrivileges() && SebNotRunning())
{
return ProcedureStepResult.Continue;
}
else
{
return ProcedureStepResult.Terminate;
}
}
internal override ProcedureStep GetNextStep()
{
return new MainMenu(Context);
}
private bool IsSingleInstance()
{
var isSingle = mutex.WaitOne(TimeSpan.Zero, true);
if (isSingle)
{
Logger.Info("There is currently no other instance running.");
}
else
{
Logger.Error("There is currently another instance running! Terminating...");
ShowError("You can only run one instance of the Reset Utility at a time! Press any key to exit...");
}
return isSingle;
}
private bool HasAdminPrivileges()
{
var identity = WindowsIdentity.GetCurrent();
var principal = new WindowsPrincipal(identity);
var isAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator);
if (isAdmin)
{
Logger.Info($"User '{identity.Name}' is running the application with administrator privileges.");
}
else
{
Logger.Error($"User '{identity.Name}' is running the application without administrator privileges! Terminating...");
ShowError("This application must be run with administrator privileges! Press any key to exit...");
}
return isAdmin;
}
private bool SebNotRunning()
{
var isRunning = Mutex.TryOpenExisting("safe_exam_browser_runtime_mutex", out _);
if (isRunning)
{
Logger.Error("SEB is currently running! Terminating...");
ShowError("This application must not be run while SEB is running! Press any key to exit...");
}
else
{
Logger.Info("SEB is currently not running.");
}
return !isRunning;
}
}
}

View file

@ -0,0 +1,102 @@
/*
* Copyright (c) 2019 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.Collections.Generic;
using System.Linq;
namespace SafeExamBrowser.ResetUtility.Procedure
{
internal class MainMenu : ProcedureStep
{
private IList<Option> options;
public MainMenu(Context context) : base(context)
{
options = new List<Option>
{
new Option { IsSelected = true, NextStep = new Restore(Context), Result = ProcedureStepResult.Continue, Text = "Restore system configuration via backup mechanism" },
new Option { NextStep = new Reset(Context), Result = ProcedureStepResult.Continue, Text = "Reset system configuration to default values" },
new Option { NextStep = new ShowLog(Context), Result = ProcedureStepResult.Continue, Text = "Show application log" },
new Option { Result = ProcedureStepResult.Terminate, Text = "Exit" }
};
}
internal override ProcedureStepResult Execute()
{
PrintMenu();
for (var key = Console.ReadKey(true).Key; key != ConsoleKey.Enter; key = Console.ReadKey(true).Key)
{
if (key == ConsoleKey.UpArrow || key == ConsoleKey.DownArrow)
{
SelectNextOption(key);
PrintMenu();
}
}
return options.First(o => o.IsSelected).Result;
}
internal override ProcedureStep GetNextStep()
{
return options.First(o => o.IsSelected).NextStep;
}
private void PrintMenu()
{
Initialize();
Console.WriteLine("Please choose one of the following options:");
Console.WriteLine();
foreach (var option in options)
{
Console.WriteLine(option.ToString());
}
Console.WriteLine();
Console.WriteLine("Use the up/down arrow keys and enter to navigate the menu.");
}
private void SelectNextOption(ConsoleKey key)
{
var current = options.First(o => o.IsSelected);
var currentIndex = options.IndexOf(current);
var nextIndex = default(int);
if (key == ConsoleKey.UpArrow)
{
nextIndex = --currentIndex < 0 ? options.Count - 1 : currentIndex;
}
if (key == ConsoleKey.DownArrow)
{
nextIndex = ++currentIndex == options.Count ? 0 : currentIndex;
}
var next = options.ElementAt(nextIndex);
current.IsSelected = false;
next.IsSelected = true;
}
private class Option
{
public bool IsSelected { get; set; }
public ProcedureStep NextStep { get; set; }
public ProcedureStepResult Result { get; set; }
public string Text { get; set; }
public override string ToString()
{
return $"[{(IsSelected ? "x" : " ")}] {Text}";
}
}
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2019 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 SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.ResetUtility.Procedure
{
internal abstract class ProcedureStep
{
protected Context Context { get; }
protected ILogger Logger => Context.Logger;
protected ConsoleColor BackgroundColor => ConsoleColor.Black;
protected ConsoleColor ForegroundColor => ConsoleColor.White;
internal ProcedureStep(Context context)
{
Context = context;
}
internal abstract ProcedureStepResult Execute();
internal abstract ProcedureStep GetNextStep();
protected void Initialize()
{
var title = "SEB Reset Utility";
Console.SetBufferSize(Console.WindowWidth, Console.WindowHeight);
Console.SetWindowSize(Console.BufferWidth, Console.BufferHeight);
Console.BackgroundColor = BackgroundColor;
Console.ForegroundColor = ForegroundColor;
Console.Clear();
Console.BackgroundColor = ConsoleColor.Gray;
Console.ForegroundColor = ConsoleColor.Black;
Console.Write(new String(' ', Console.BufferWidth));
Console.Write(new String(' ', (int) Math.Floor((Console.BufferWidth - title.Length) / 2.0)));
Console.Write(title);
Console.Write(new String(' ', (int) Math.Ceiling((Console.BufferWidth - title.Length) / 2.0)));
Console.Write(new String(' ', Console.BufferWidth));
Console.BackgroundColor = BackgroundColor;
Console.ForegroundColor = ForegroundColor;
Console.SetCursorPosition(0, 4);
Console.CursorVisible = false;
}
protected void ShowError(string message)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(message);
Console.ForegroundColor = ForegroundColor;
Console.ReadKey();
}
}
}

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2019 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.ResetUtility.Procedure
{
internal enum ProcedureStepResult
{
Continue,
Terminate
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2019 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;
namespace SafeExamBrowser.ResetUtility.Procedure
{
internal class Reset : ProcedureStep
{
public Reset(Context context) : base(context)
{
}
internal override ProcedureStepResult Execute()
{
throw new NotImplementedException();
}
internal override ProcedureStep GetNextStep()
{
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2019 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;
namespace SafeExamBrowser.ResetUtility.Procedure
{
internal class Restore : ProcedureStep
{
public Restore(Context context) : base(context)
{
}
internal override ProcedureStepResult Execute()
{
throw new NotImplementedException();
}
internal override ProcedureStep GetNextStep()
{
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,93 @@
/*
* Copyright (c) 2019 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 SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.ResetUtility.Procedure
{
internal class ShowLog : ProcedureStep
{
public ShowLog(Context context) : base(context)
{
}
internal override ProcedureStepResult Execute()
{
Initialize();
foreach (var item in Logger.GetLog())
{
if (item is ILogMessage message)
{
PrintMessage(message);
}
if (item is ILogText text)
{
PrintText(text);
}
}
Console.WriteLine();
Console.WriteLine("Press any key to return to the main menu.");
Console.ReadKey(true);
return ProcedureStepResult.Continue;
}
internal override ProcedureStep GetNextStep()
{
return new MainMenu(Context);
}
private void PrintMessage(ILogMessage message)
{
var date = message.DateTime.ToString("HH:mm:ss.fff");
var severity = message.Severity.ToString().ToUpper();
var threadId = message.ThreadInfo.Id < 10 ? $"0{message.ThreadInfo.Id}" : message.ThreadInfo.Id.ToString();
var threadName = message.ThreadInfo.HasName ? ": " + message.ThreadInfo.Name : string.Empty;
var threadInfo = $"[{threadId}{threadName}]";
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.Write($"{date} {threadInfo} - ");
Console.ForegroundColor = GetColorFor(message.Severity);
Console.WriteLine($"{severity}: { message.Message}");
Console.ForegroundColor = ForegroundColor;
}
private void PrintText(ILogText text)
{
var isHeader = text.Text.StartsWith("/* ");
var isComment = text.Text.StartsWith("# ");
if (isHeader || isComment)
{
Console.ForegroundColor = ConsoleColor.DarkGreen;
}
Console.WriteLine(text.Text);
Console.ForegroundColor = ForegroundColor;
}
private ConsoleColor GetColorFor(LogLevel severity)
{
switch (severity)
{
case LogLevel.Debug:
return ConsoleColor.Gray;
case LogLevel.Error:
return ConsoleColor.Red;
case LogLevel.Warning:
return ConsoleColor.DarkYellow;
default:
return ForegroundColor;
}
}
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2019 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 SafeExamBrowser.ResetUtility.Procedure;
namespace SafeExamBrowser.ResetUtility
{
public class Program
{
public static void Main(string[] args)
{
new Program().Run();
}
public void Run()
{
var instances = new CompositionRoot();
instances.BuildObjectGraph();
instances.LogStartupInformation();
instances.NativeMethods.TryDisableSystemMenu();
for (var step = instances.InitialStep; ; step = step.GetNextStep())
{
var result = step.Execute();
if (result == ProcedureStepResult.Terminate)
{
break;
}
}
instances.LogShutdownInformation();
}
}
}

View file

@ -0,0 +1,33 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SafeExamBrowser.ResetUtility")]
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.ResetUtility")]
[assembly: AssemblyCopyright("Copyright © 2019 ETH Zürich, Educational Development and Technology (LET)")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("bc229e80-ff93-424f-9930-d9c07d9b57b4")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0.0")]

View file

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{BC229E80-FF93-424F-9930-D9C07D9B57B4}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>SafeExamBrowser.ResetUtility</RootNamespace>
<AssemblyName>SafeExamBrowser.ResetUtility</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>SafeExamBrowser.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<Compile Include="Context.cs" />
<Compile Include="CompositionRoot.cs" />
<Compile Include="NativeMethods.cs" />
<Compile Include="Procedure\Initialization.cs" />
<Compile Include="Procedure\MainMenu.cs" />
<Compile Include="Procedure\ProcedureStep.cs" />
<Compile Include="Procedure\ProcedureStepResult.cs" />
<Compile Include="Procedure\Reset.cs" />
<Compile Include="Procedure\Restore.cs" />
<Compile Include="Procedure\ShowLog.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Contracts\SafeExamBrowser.Contracts.csproj">
<Project>{47da5933-bef8-4729-94e6-abde2db12262}</Project>
<Name>SafeExamBrowser.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Lockdown\SafeExamBrowser.Lockdown.csproj">
<Project>{386b6042-3e12-4753-9fc6-c88ea4f97030}</Project>
<Name>SafeExamBrowser.Lockdown</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Logging\SafeExamBrowser.Logging.csproj">
<Project>{e107026c-2011-4552-a7d8-3a0d37881df6}</Project>
<Name>SafeExamBrowser.Logging</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="SafeExamBrowser.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

View file

@ -66,6 +66,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Lockdown",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Lockdown.UnitTests", "SafeExamBrowser.Lockdown.UnitTests\SafeExamBrowser.Lockdown.UnitTests.csproj", "{90378E33-3898-4A7C-BE2D-2F3EFCFC4BB5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.ResetUtility", "SafeExamBrowser.ResetUtility\SafeExamBrowser.ResetUtility.csproj", "{BC229E80-FF93-424F-9930-D9C07D9B57B4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -282,6 +284,14 @@ Global
{90378E33-3898-4A7C-BE2D-2F3EFCFC4BB5}.Release|Any CPU.Build.0 = Release|Any CPU
{90378E33-3898-4A7C-BE2D-2F3EFCFC4BB5}.Release|x86.ActiveCfg = Release|x86
{90378E33-3898-4A7C-BE2D-2F3EFCFC4BB5}.Release|x86.Build.0 = Release|x86
{BC229E80-FF93-424F-9930-D9C07D9B57B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BC229E80-FF93-424F-9930-D9C07D9B57B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BC229E80-FF93-424F-9930-D9C07D9B57B4}.Debug|x86.ActiveCfg = Debug|x86
{BC229E80-FF93-424F-9930-D9C07D9B57B4}.Debug|x86.Build.0 = Debug|x86
{BC229E80-FF93-424F-9930-D9C07D9B57B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BC229E80-FF93-424F-9930-D9C07D9B57B4}.Release|Any CPU.Build.0 = Release|Any CPU
{BC229E80-FF93-424F-9930-D9C07D9B57B4}.Release|x86.ActiveCfg = Release|x86
{BC229E80-FF93-424F-9930-D9C07D9B57B4}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE