feature: Bootstrapper secure server discovery

This commit is contained in:
Gary Sharp
2026-01-22 15:26:23 +11:00
parent 71fa53bfb2
commit e1f1973520
40 changed files with 2094 additions and 460 deletions
+1
View File
@@ -141,6 +141,7 @@
<Compile Include="Extensions\EnrolExtensions.cs" /> <Compile Include="Extensions\EnrolExtensions.cs" />
<Compile Include="Extensions\WhoAmIExtensions.cs" /> <Compile Include="Extensions\WhoAmIExtensions.cs" />
<Compile Include="Interop\Certificates.cs" /> <Compile Include="Interop\Certificates.cs" />
<Compile Include="Interop\EndpointDiscovery.cs" />
<Compile Include="Interop\Hardware.cs" /> <Compile Include="Interop\Hardware.cs" />
<Compile Include="Interop\LocalAuthentication.cs" /> <Compile Include="Interop\LocalAuthentication.cs" />
<Compile Include="Interop\Native\NetworkConnectionStatuses.cs" /> <Compile Include="Interop\Native\NetworkConnectionStatuses.cs" />
+8 -7
View File
@@ -10,19 +10,18 @@ namespace Disco.Client
{ {
public static class ErrorReporting public static class ErrorReporting
{ {
private const string ServicePathTemplate = "http://DISCO:9292/Services/Client/ClientError";
public static string DeviceIdentifier { get; set; } public static string DeviceIdentifier { get; set; }
public static string EnrolmentSessionId { get; set; } public static string EnrolmentSessionId { get; set; }
public static void ReportError(Exception Ex, bool ReportToServer) public static void ReportError(Exception exception, bool reportToServer)
{ {
bool isClientServiceException = Ex is ClientServiceException; bool isClientServiceException = exception is ClientServiceException;
ErrorReport report = new ErrorReport() ErrorReport report = new ErrorReport()
{ {
DeviceIdentifier = DeviceIdentifier, DeviceIdentifier = DeviceIdentifier,
SessionId = EnrolmentSessionId, SessionId = EnrolmentSessionId,
JsonException = Ex.IntenseExceptionSerialization() JsonException = exception.IntenseExceptionSerialization()
}; };
try try
@@ -38,7 +37,7 @@ namespace Disco.Client
catch (Exception) { } catch (Exception) { }
// Don't log server errors back to the server // Don't log server errors back to the server
if (!isClientServiceException && ReportToServer) if (!isClientServiceException && reportToServer)
{ {
try try
{ {
@@ -49,7 +48,7 @@ namespace Disco.Client
try try
{ {
Presentation.WriteFatalError(Ex); Presentation.WriteFatalError(exception);
} }
catch (Exception) { } catch (Exception) { }
} }
@@ -85,7 +84,9 @@ namespace Disco.Client
string reportJson = JsonConvert.SerializeObject(report); string reportJson = JsonConvert.SerializeObject(report);
string reportResponse; string reportResponse;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(ServicePathTemplate); var serverUri = new Uri(Program.ServerUrl ?? new Uri("http://disco:9292"), "/Services/Client/ClientError");
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serverUri);
request.UserAgent = $"Disco-Client/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}"; request.UserAgent = $"Disco-Client/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}";
request.ContentType = "application/json"; request.ContentType = "application/json";
request.Method = WebRequestMethods.Http.Post; request.Method = WebRequestMethods.Http.Post;
@@ -1,30 +1,23 @@
using Disco.Models.ClientServices; using Disco.Models.ClientServices;
using Newtonsoft.Json; using Newtonsoft.Json;
using System;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Reflection; using System.Reflection;
namespace Disco.Client.Extensions namespace Disco.Client.Extensions
{ {
public static class ClientServicesExtensions internal static class ClientServicesExtensions
{ {
//#if DEBUG public static ResponseType Post<ResponseType>(this ServiceBase<ResponseType> service, bool authenticated)
// public const string ServicePathAuthenticatedTemplate = "http://WS-GSHARP:57252/Services/Client/Authenticated/{0}";
// public const string ServicePathUnauthenticatedTemplate = "http://WS-GSHARP:57252/Services/Client/Unauthenticated/{0}";
//#else
public const string ServicePathAuthenticatedTemplate = "http://DISCO:9292/Services/Client/Authenticated/{0}";
public const string ServicePathUnauthenticatedTemplate = "http://DISCO:9292/Services/Client/Unauthenticated/{0}";
//#endif
public static ResponseType Post<ResponseType>(this ServiceBase<ResponseType> Service, bool Authenticated)
{ {
ResponseType serviceResponse; ResponseType serviceResponse;
string serviceUrl; Uri serviceUrl;
if (Authenticated) if (authenticated)
serviceUrl = string.Format(ServicePathAuthenticatedTemplate, Service.Feature); serviceUrl = new Uri(Program.ServerUrl, $"/Services/Client/Authenticated/{service.Feature}");
else else
serviceUrl = string.Format(ServicePathUnauthenticatedTemplate, Service.Feature); serviceUrl = new Uri(Program.ServerUrl, $"/Services/Client/Unauthenticated/{service.Feature}");
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serviceUrl); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serviceUrl);
request.UserAgent = $"Disco-Client/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}"; request.UserAgent = $"Disco-Client/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}";
@@ -39,7 +32,7 @@ namespace Disco.Client.Extensions
{ {
using (var jsonWriter = new JsonTextWriter(requestWriter)) using (var jsonWriter = new JsonTextWriter(requestWriter))
{ {
jsonSerializer.Serialize(jsonWriter, Service); jsonSerializer.Serialize(jsonWriter, service);
} }
} }
+317
View File
@@ -0,0 +1,317 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Threading;
namespace Disco.Client.Interop
{
internal class EndpointDiscovery
{
[DllImport("dnsapi", EntryPoint = "DnsQuery_W", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
private static extern int DnsQuery([MarshalAs(UnmanagedType.VBByRefStr)] ref string pszName, NativeDnsQueryTypes wType, NativeDnsQueryOptions options, int aipServers, ref IntPtr ppQueryResults, int pReserved);
[DllImport("dnsapi", CharSet = CharSet.Auto, SetLastError = true)]
private static extern void DnsRecordListFree(IntPtr pRecordList, int FreeType);
private const int DNS_ERROR_RCODE_NAME_ERROR = 0x232B;
private const int DNS_ERROR_BAD_PACKET = 0x251E;
public static Tuple<Uri, string> DiscoverServer(Uri forcedServerUri)
{
// 1. Check first command line argument for server name
if (forcedServerUri != null)
return Tuple.Create(forcedServerUri, "Manual");
// 2. Check for a DNS SRV record for _discoict._tcp.domain
var domainSuffixes = new List<string>();
var primaryDomain = IPGlobalProperties.GetIPGlobalProperties().DomainName;
if (!string.IsNullOrEmpty(primaryDomain))
domainSuffixes.Add(primaryDomain);
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up);
foreach (var ni in networkInterfaces)
{
var domainSuffix = ni.GetIPProperties().DnsSuffix;
if (!string.IsNullOrWhiteSpace(domainSuffix))
{
if (domainSuffix.Equals("mshome.net", StringComparison.OrdinalIgnoreCase))
continue;
if (!domainSuffixes.Contains(domainSuffix, StringComparer.OrdinalIgnoreCase))
domainSuffixes.Add(domainSuffix);
}
}
foreach (var domain in domainSuffixes)
{
var dnsRecords = GetSRVRecords("_discoict._tcp." + domain);
if (dnsRecords.Count > 0)
{
var firstRecord = dnsRecords.OrderBy(r => r.Priority).ThenByDescending(r => r.Weight).First();
if (firstRecord.Port == 443)
return Tuple.Create(new Uri($"https://{firstRecord.Target}"), "SRV");
else
return Tuple.Create(new Uri($"https://{firstRecord.Target}:{firstRecord.Port}"), "SRV");
}
}
// 3. Detect VicSmart network and try resolving with Disco ICT Online Services
if (TryResolveVicSmartServer(domainSuffixes, out var vicSmartServerUrl))
return Tuple.Create(vicSmartServerUrl, "VicSmart");
// 4. Legacy: Ping 'disco' and assume port 9292
using (Ping p = new Ping())
{
try
{
PingReply pr = p.Send("disco", 2000);
if (pr.Status == IPStatus.Success)
return Tuple.Create(new Uri("http://disco:9292"), "Legacy");
}
catch (Exception)
{
}
}
throw new Exception("Could not locate Disco ICT server on the network.");
}
private static bool TryResolveVicSmartServer(List<string> domainSuffixes, out Uri serverUrl)
{
if (IsVicSmartNetwork(domainSuffixes))
{
var potentialVicSmartAddresses = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up)
.SelectMany(ni => ni.GetIPProperties().UnicastAddresses)
.Where(ua => ua.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
.Select(ua => ua.Address.GetAddressBytes())
.Where(a => a[0] == 10)
.Select(a => (ushort)((a[1] >> 4) & 0x000F) | ((a[1] << 4) & 0x00F0) | ((a[2] << 12) & 0xF000) | ((a[2] << 4) & 0x0F00))
.Distinct()
.Select(a => $"{a:x4}.vicsmart.discoict.com")
.ToList();
foreach (var potentialAddress in potentialVicSmartAddresses)
{
var records = GetTxtRecords(potentialAddress);
foreach (var record in records)
{
if (!record.Content.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
continue;
if (Uri.TryCreate(record.Content, UriKind.Absolute, out var discoveredUri))
{
serverUrl = discoveredUri;
return true;
}
}
}
}
serverUrl = null;
return false;
}
private static bool IsVicSmartNetwork(List<string> domainSuffixes)
{
if (domainSuffixes.Any(s => string.Equals("services.education.vic.gov.au", s, StringComparison.OrdinalIgnoreCase)) ||
domainSuffixes.Any(s => string.Equals("education.vic.gov.au", s, StringComparison.OrdinalIgnoreCase))
)
return true;
IPHostEntry doeWanDnsEntry;
try
{
doeWanDnsEntry = Dns.GetHostEntry("broadband.doe.wan");
if (doeWanDnsEntry.AddressList.Length > 0)
return true;
}
catch (Exception)
{ }
return false;
}
private static List<DnsTxtRecord> GetTxtRecords(string name)
{
IntPtr resourceRecordsPointer = IntPtr.Zero;
var records = new List<DnsTxtRecord>();
var retry = 5;
retry:
try
{
int queryResult = DnsQuery(ref name, NativeDnsQueryTypes.DNS_TYPE_TEXT, NativeDnsQueryOptions.DNS_QUERY_STANDARD, 0, ref resourceRecordsPointer, 0);
if (queryResult != 0)
{
if (queryResult == DNS_ERROR_RCODE_NAME_ERROR)
return records;
else if (queryResult == DNS_ERROR_BAD_PACKET && retry > 0)
{
// Sometimes a BAD_PACKET error is returned, retry a few times
Thread.Sleep(200);
retry--;
goto retry;
}
else
throw new Win32Exception(queryResult);
}
NativeDnsTxtRecord record;
for (var resourceRecordPointer = resourceRecordsPointer; !resourceRecordPointer.Equals(IntPtr.Zero); resourceRecordPointer = record.pNext)
{
record = Marshal.PtrToStructure<NativeDnsTxtRecord>(resourceRecordPointer);
if (record.wType == (ushort)NativeDnsQueryTypes.DNS_TYPE_TEXT)
records.Add(DnsTxtRecord.FromNativeRecord(record));
}
}
finally
{
if (resourceRecordsPointer != IntPtr.Zero)
DnsRecordListFree(resourceRecordsPointer, 0);
}
return records;
}
private static List<DnsSrvRecord> GetSRVRecords(string name)
{
IntPtr resourceRecordsPointer = IntPtr.Zero;
var records = new List<DnsSrvRecord>();
var retry = 5;
retry:
try
{
int queryResult = DnsQuery(ref name, NativeDnsQueryTypes.DNS_TYPE_SRV, NativeDnsQueryOptions.DNS_QUERY_STANDARD, 0, ref resourceRecordsPointer, 0);
if (queryResult != 0)
{
if (queryResult == DNS_ERROR_RCODE_NAME_ERROR)
return records;
else if (queryResult == DNS_ERROR_BAD_PACKET && retry > 0)
{
// Sometimes a BAD_PACKET error is returned, retry a few times
Thread.Sleep(200);
retry--;
goto retry;
}
else
throw new Win32Exception(queryResult);
}
NativeDnsSrvRecord record;
for (var resourceRecordPointer = resourceRecordsPointer; !resourceRecordPointer.Equals(IntPtr.Zero); resourceRecordPointer = record.pNext)
{
record = Marshal.PtrToStructure<NativeDnsSrvRecord>(resourceRecordPointer);
if (record.wType == (ushort)NativeDnsQueryTypes.DNS_TYPE_SRV)
records.Add(DnsSrvRecord.FromNativeRecord(record));
}
}
finally
{
if (resourceRecordsPointer != IntPtr.Zero)
DnsRecordListFree(resourceRecordsPointer, 0);
}
return records;
}
private enum NativeDnsQueryOptions
{
DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE = 1,
DNS_QUERY_BYPASS_CACHE = 8,
DNS_QUERY_DONT_RESET_TTL_VALUES = 0x100000,
DNS_QUERY_NO_HOSTS_FILE = 0x40,
DNS_QUERY_NO_LOCAL_NAME = 0x20,
DNS_QUERY_NO_NETBT = 0x80,
DNS_QUERY_NO_RECURSION = 4,
DNS_QUERY_NO_WIRE_QUERY = 0x10,
DNS_QUERY_RESERVED = -16777216,
DNS_QUERY_RETURN_MESSAGE = 0x200,
DNS_QUERY_STANDARD = 0,
DNS_QUERY_TREAT_AS_FQDN = 0x1000,
DNS_QUERY_USE_TCP_ONLY = 2,
DNS_QUERY_WIRE_ONLY = 0x100
}
private enum NativeDnsQueryTypes
{
DNS_TYPE_TEXT = 0x0010,
DNS_TYPE_SRV = 0x0021
}
[StructLayout(LayoutKind.Sequential)]
private struct NativeDnsSrvRecord
{
public IntPtr pNext;
[MarshalAs(UnmanagedType.LPWStr)]
public string pName;
public ushort wType;
public ushort wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
[MarshalAs(UnmanagedType.LPWStr)]
public string pNameTarget;
public ushort wPriority;
public ushort wWeight;
public ushort wPort;
public ushort Pad;
}
private class DnsSrvRecord
{
public string Name { get; set; }
public int Type { get; set; }
public int Ttl { get; set; }
public string Target { get; set; }
public int Priority { get; set; }
public int Weight { get; set; }
public int Port { get; set; }
public static DnsSrvRecord FromNativeRecord(NativeDnsSrvRecord nativeRecord)
{
return new DnsSrvRecord
{
Name = nativeRecord.pName,
Type = nativeRecord.wType,
Ttl = nativeRecord.dwTtl,
Target = nativeRecord.pNameTarget,
Priority = nativeRecord.wPriority,
Weight = nativeRecord.wWeight,
Port = nativeRecord.wPort
};
}
}
[StructLayout(LayoutKind.Sequential)]
private struct NativeDnsTxtRecord
{
public IntPtr pNext;
[MarshalAs(UnmanagedType.LPWStr)]
public string pName;
public ushort wType;
public ushort wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
public uint dwStringLength;
[MarshalAs(UnmanagedType.LPWStr)]
public string pStringArray;
}
private class DnsTxtRecord
{
public string Name { get; set; }
public int Type { get; set; }
public int Ttl { get; set; }
public string Content { get; set; }
public static DnsTxtRecord FromNativeRecord(NativeDnsTxtRecord nativeRecord)
{
return new DnsTxtRecord
{
Name = nativeRecord.pName,
Type = nativeRecord.wType,
Ttl = nativeRecord.dwTtl,
Content = nativeRecord.pStringArray,
};
}
}
}
}
+15 -3
View File
@@ -1,6 +1,7 @@
using Disco.Client.Extensions; using Disco.Client.Extensions;
using Disco.Client.Interop; using Disco.Client.Interop;
using System; using System;
using System.Net;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@@ -26,7 +27,7 @@ namespace Disco.Client
} }
public static void UpdateStatus(string SubHeading, string Message, bool ShowProgress, int Progress) public static void UpdateStatus(string SubHeading, string Message, bool ShowProgress, int Progress)
{ {
Console.WriteLine($"#{SubHeading.EscapeMessage()},{Message.EscapeMessage()},{ShowProgress.ToString()},{Progress.ToString()}"); Console.WriteLine($"#{SubHeading.EscapeMessage()},{Message.EscapeMessage()},{ShowProgress},{Progress}");
} }
public static void TryDelay(int Milliseconds) public static void TryDelay(int Milliseconds)
{ {
@@ -38,6 +39,11 @@ namespace Disco.Client
{ {
StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
message.AppendLine($"Version: {Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}"); message.AppendLine($"Version: {Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}");
message.Append($"Server: {Program.ServerUrl})");
if (Program.ServerUrl.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
message.AppendLine(" [Secure]");
else
message.AppendLine(" [Insecure]");
message.AppendLine($"Device: {Hardware.Information.SerialNumber} ({Hardware.Information.Manufacturer} {Hardware.Information.Model})"); message.AppendLine($"Device: {Hardware.Information.SerialNumber} ({Hardware.Information.Manufacturer} {Hardware.Information.Model})");
Console.ForegroundColor = ConsoleColor.Yellow; Console.ForegroundColor = ConsoleColor.Yellow;
UpdateStatus("Preparation Client Started", message.ToString(), false, 0); UpdateStatus("Preparation Client Started", message.ToString(), false, 0);
@@ -48,12 +54,18 @@ namespace Disco.Client
{ {
Console.ForegroundColor = ConsoleColor.Magenta; Console.ForegroundColor = ConsoleColor.Magenta;
ClientServiceException clientServiceException = ex as ClientServiceException; if (ex is ClientServiceException clientServiceException)
if (clientServiceException != null)
{ {
UpdateStatus($"An error occurred during {clientServiceException.ServiceFeature}", UpdateStatus($"An error occurred during {clientServiceException.ServiceFeature}",
clientServiceException.Message, false, 0); clientServiceException.Message, false, 0);
} }
else if (ex is WebException exWeb &&
exWeb.Response is HttpWebResponse webResponse &&
webResponse.StatusCode == HttpStatusCode.InternalServerError)
{
UpdateStatus("Something went wrong on the server",
"Review logs for more information (Configuration > Logging)", false, 0);
}
else else
{ {
StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
+59 -6
View File
@@ -1,6 +1,8 @@
using Disco.Client.Extensions; using Disco.Client.Extensions;
using Disco.Client.Interop;
using Disco.Models.ClientServices; using Disco.Models.ClientServices;
using System; using System;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@@ -11,6 +13,9 @@ namespace Disco.Client
public static bool IsAuthenticated { get; set; } public static bool IsAuthenticated { get; set; }
public static bool RebootRequired { get; set; } public static bool RebootRequired { get; set; }
public static bool AllowUninstall { get; set; } public static bool AllowUninstall { get; set; }
public static int BootstrapperVersion { get; private set; } = 1;
public static int BootstrapperProcessId { get; private set; } = -1;
public static Uri ServerUrl { get; private set; }
[STAThread] [STAThread]
public static void Main(string[] args) public static void Main(string[] args)
@@ -24,12 +29,15 @@ namespace Disco.Client
{ {
Console.WriteLine("Waiting for Debugger to Attach"); Console.WriteLine("Waiting for Debugger to Attach");
System.Threading.Thread.Sleep(1000); System.Threading.Thread.Sleep(1000);
} while (!System.Diagnostics.Debugger.IsAttached); } while (!Debugger.IsAttached);
} }
#endif #endif
// Initialize Environment Settings // Initialize Environment Settings
SetupEnvironment(); SetupEnvironment(args);
if (ServerUrl == null)
keepProcessing = DiscoverDiscoIct();
// Report to Bootstrapper // Report to Bootstrapper
Presentation.WriteBanner(); Presentation.WriteBanner();
@@ -45,7 +53,7 @@ namespace Disco.Client
Presentation.WriteFooter(RebootRequired, AllowUninstall, !keepProcessing); Presentation.WriteFooter(RebootRequired, AllowUninstall, !keepProcessing);
} }
public static void SetupEnvironment() public static void SetupEnvironment(string[] args)
{ {
// Hookup Unhandled Error Handling // Hookup Unhandled Error Handling
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.CurrentDomain_UnhandledException; AppDomain.CurrentDomain.UnhandledException += ErrorReporting.CurrentDomain_UnhandledException;
@@ -54,21 +62,66 @@ namespace Disco.Client
WebRequest.DefaultWebProxy = new WebProxy(); WebRequest.DefaultWebProxy = new WebProxy();
// Override Http 100 Continue Behaviour // Override Http 100 Continue Behaviour
ServicePointManager.Expect100Continue = false; ServicePointManager.Expect100Continue = false;
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
// Assume success unless otherwise notified // Assume success unless otherwise notified
AllowUninstall = true; AllowUninstall = true;
if (args != null && args.Length == 3)
{
// Parse Bootstrapper Version
int parsedVersion;
if (int.TryParse(args[0], out parsedVersion))
BootstrapperVersion = parsedVersion;
// Parse Bootstrapper Process ID
int parsedProcessId;
if (int.TryParse(args[1], out parsedProcessId))
BootstrapperProcessId = parsedProcessId;
// Parse Server URL
Uri parsedUri;
if (Uri.TryCreate(args[2], UriKind.Absolute, out parsedUri))
ServerUrl = parsedUri;
}
else
{
BootstrapperVersion = 1;
BootstrapperProcessId = -1;
ServerUrl = null;
}
// Detect Disco.Bootstrapper - Create Enable UI Delay if Running // Detect Disco.Bootstrapper - Create Enable UI Delay if Running
Presentation.DelayUI = false;
try try
{ {
Presentation.DelayUI = (System.Diagnostics.Process.GetProcessesByName("Disco.ClientBootstrapper").Length > 0); if (BootstrapperProcessId != -1)
{
var parentProcess = Process.GetProcessById(BootstrapperProcessId);
Presentation.DelayUI = !parentProcess.HasExited;
}
} }
catch (Exception) catch (Exception)
{ {
Presentation.DelayUI = true; // Add Delays on Error
} }
} }
public static bool DiscoverDiscoIct()
{
try
{
Presentation.UpdateStatus("Detecting Disco ICT", "Locating Disco ICT Server, Please wait...", true, -1);
Presentation.TryDelay(3000);
ServerUrl = EndpointDiscovery.DiscoverServer(null).Item1;
// Complete
return true;
}
catch (Exception ex)
{
ErrorReporting.ReportError(ex, false);
}
return false;
}
public static bool WhoAmI() public static bool WhoAmI()
{ {
try try
@@ -144,7 +197,7 @@ namespace Disco.Client
var secondsConsumed = (DateTimeOffset.Now - startTime).TotalSeconds; var secondsConsumed = (DateTimeOffset.Now - startTime).TotalSeconds;
var progress = (int)((secondsConsumed / totalSeconds) * 100); var progress = (int)((secondsConsumed / totalSeconds) * 100);
Presentation.UpdateStatus($"Pending Device Enrolment Approval: {response.PendingIdentifier}", $"Waiting for enrolment session '{response.PendingIdentifier}' to be approved.{Environment.NewLine}Reason: {response.PendingReason}", true, progress); Presentation.UpdateStatus($"Pending Device Enrolment Approval: {response.PendingIdentifier}", $"Server: {Program.ServerUrl}{Environment.NewLine}Reason: {response.PendingReason}", true, progress);
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(10)); System.Threading.Thread.Sleep(TimeSpan.FromSeconds(10));
} }
else else
+2 -2
View File
@@ -1,9 +1,9 @@
@ECHO OFF @ECHO OFF
IF /I "%USERDOMAIN%"=="NT AUTHORITY" GOTO RunAsNetworkService IF /I "%USERDOMAIN%"=="NT AUTHORITY" GOTO RunAsNetworkService
Disco.Client.exe Disco.Client.exe %1 %2 %3
EXIT /B 0 EXIT /B 0
:RunAsNetworkService :RunAsNetworkService
ECHO #Running,Launching Preparation Client, Please wait...{newline}Starting client as 'NT AUTHORITY\Network Service',true,-1 ECHO #Running,Launching Preparation Client, Please wait...{newline}Starting client as 'NT AUTHORITY\Network Service',true,-1
PsExec -acceptula -i -u "NT AUTHORITY\Network Service" -w "%CD%" "%CD%\Start.bat" PsExec -acceptula -i -u "NT AUTHORITY\Network Service" -w "%CD%" "%CD%\Start.bat %1 %2 %3"
EXIT /B 0 EXIT /B 0
+178 -189
View File
@@ -1,4 +1,6 @@
using System; using Disco.Client.Interop;
using Disco.ClientBootstrapper.Interop;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
@@ -6,46 +8,191 @@ using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace Disco.ClientBootstrapper namespace Disco.ClientBootstrapper
{ {
class BootstrapperLoop internal class BootstrapperLoop
{ {
private readonly Func<CancellationToken, Task> completeCallback;
public Thread LoopThread; private readonly CancellationToken cancellationToken;
public delegate void LoopCompleteCallback(); private readonly IStatus statusUI;
private LoopCompleteCallback mLoopCompleteCallback; private readonly Uri forcedServerUrl;
private IStatus statusUI;
private string tempWorkingDirectory; private string tempWorkingDirectory;
private StringBuilder errorMessage;
private Process clientProcess; private Process clientProcess;
//#if DEBUG public BootstrapperLoop(IStatus statusUI, Uri forcedServerUrl, Func<CancellationToken, Task> callback, CancellationToken cancellationToken)
// public const string DiscoServerName = "WS-GSHARP";
// public const int DiscoServerPort = 57252;
//#else
public const string DiscoServerName = "DISCO";
public const int DiscoServerPort = 9292;
//#endif
public BootstrapperLoop(IStatus StatusUI, LoopCompleteCallback Callback)
{ {
statusUI = StatusUI; this.statusUI = statusUI;
mLoopCompleteCallback = Callback; this.forcedServerUrl = forcedServerUrl;
errorMessage = new StringBuilder(); completeCallback = callback;
this.cancellationToken = cancellationToken;
} }
public void Start() public void Start()
{ {
LoopThread = new Thread(new ThreadStart(loopHost)); Task.Factory.StartNew(async () =>
LoopThread.Start(); {
await Loop(forcedServerUrl, cancellationToken);
}, cancellationToken);
} }
private void loopHost() private async Task Loop(Uri forcedServerUrl, CancellationToken cancellationToken)
{ {
try try
{ {
loop(); statusUI.UpdateStatus("System Preparation (Bootstrapper)", "Starting", "Please wait...", true, -1);
tempWorkingDirectory = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), @"Disco\Temp");
if (!Directory.Exists(tempWorkingDirectory))
Directory.CreateDirectory(tempWorkingDirectory);
// Check for Network Connectivity
statusUI.UpdateStatus(null, "Detecting Network", "Checking network connectivity, Please wait...", true, -1);
if (!NetworkInterop.HasNetworkConnectivity())
{
statusUI.UpdateStatus(null, "Detecting Network", "No network connectivity detected, Diagnosing...", true, -1);
statusUI_WriteAdapterInfo();
if (!NetworkInterop.HasNetworkConnectivity())
{
// Check for Wireless
var hasWireless = (NetworkInterop.NetworkAdapters.Count(na => na.IsWireless) > 0);
if (hasWireless)
{
// True: Do wireless loop
statusUI.UpdateStatus(null, "Configuring Wireless Network", "Wireless adapter detected, Configuring...", true, -1);
await NetworkInterop.ConfigureWireless(cancellationToken);
statusUI.UpdateStatus(null, "Waiting for Wireless Network", null, true, 0);
for (int i = 0; i < 30; i++)
{
statusUI_WriteAdapterInfo();
statusUI.UpdateStatus(null, null, null, true, i);
await Program.SleepThread(2000, false, cancellationToken);
if (NetworkInterop.HasNetworkConnectivity())
break;
}
if (!NetworkInterop.HasNetworkConnectivity())
{
statusUI.UpdateStatus(null, "Wireless Network Failed", "Unable to connect to the wireless network, please connect the network cable...", false);
await Program.SleepThread(3000, false, cancellationToken);
}
}
if (!NetworkInterop.HasNetworkConnectivity())
{
// Instruct user to connect network cable
statusUI.UpdateStatus(null, "Please connect the network cable", null);
for (int i = 0; i < 30; i++)
{
statusUI_WriteAdapterInfo();
statusUI.UpdateStatus(null, null, null, true, i);
await Program.SleepThread(2000, false, cancellationToken);
if (NetworkInterop.HasNetworkConnectivity())
break;
}
}
}
if (!NetworkInterop.HasNetworkConnectivity())
{
// Client Failed
if (completeCallback != null)
await completeCallback(cancellationToken);
return;
}
}
Tuple<Uri, string> serverDiscovery;
statusUI.UpdateStatus(null, "Detecting Disco ICT", "Locating Disco ICT Server, Please wait...", true, -1);
try
{
serverDiscovery = EndpointDiscovery.DiscoverServer(forcedServerUrl);
statusUI.UpdateStatus(null, null, $"{serverDiscovery.Item1} ({serverDiscovery.Item2})", true, -1);
}
catch (Exception)
{
statusUI.UpdateStatus(null, null, "Failed to locate Disco ICT Server, exiting...", true, -1);
await Program.SleepThread(2000, false, cancellationToken);
throw;
}
// Download Client
statusUI.UpdateStatus(null, "Downloading", "Retrieving Preparation Client, Please wait...", true, -1);
string clientSourceLocation = Path.Combine(tempWorkingDirectory, "PreparationClient.zip");
using (var webClient = new WebClient())
{
// Don't use a proxy when downloading the Client
webClient.Proxy = new WebProxy();
webClient.Headers.Add("X-DiscoICT-Discovery", serverDiscovery.Item2);
try
{
webClient.DownloadFile(new Uri(serverDiscovery.Item1, "/Services/Client/PreparationClient"), clientSourceLocation);
}
catch (WebException ex)
{
if (ex.Response != null &&
ex.Response is HttpWebResponse response)
{
if (response.StatusCode == HttpStatusCode.BadRequest)
{
statusUI.UpdateStatus(null, "Download failed: Bad Request", response.StatusDescription, true, -1);
await Program.SleepThread(5000, false, cancellationToken);
}
else if (response.StatusCode == HttpStatusCode.InternalServerError)
{
statusUI.UpdateStatus(null, "Download failed: Something went wrong on the server", "Review logs for more information (Configuration > Logging)", true, -1);
await Program.SleepThread(5000, false, cancellationToken);
}
}
throw;
}
}
// Unzip Client
statusUI.UpdateStatus(null, "Extracting", "Retrieving Preparation Client, Please wait...", true, -1);
string clientLocation = Path.Combine(tempWorkingDirectory, "PreparationClient");
if (Directory.Exists(clientLocation))
Directory.Delete(clientLocation, true);
Directory.CreateDirectory(clientLocation);
using (var clientSource = Ionic.Zip.ZipFile.Read(clientSourceLocation))
{
clientSource.ExtractAll(clientLocation, Ionic.Zip.ExtractExistingFileAction.OverwriteSilently);
}
// Launch Client
statusUI.UpdateStatus("System Preparation (Client)", "Running", "Launching Preparation Client, Please wait...", true, -1);
ProcessStartInfo clientProcessStart = new ProcessStartInfo(Path.Combine(clientLocation, "Start.bat"), $"2 {Process.GetCurrentProcess().Id} {serverDiscovery.Item1}")
{
WorkingDirectory = clientLocation,
CreateNoWindow = true,
RedirectStandardOutput = true,
UseShellExecute = false,
};
using (clientProcess = Process.Start(clientProcessStart))
{
// Read StdOutput until End
try
{
clientProcess.OutputDataReceived += new DataReceivedEventHandler(clientProcess_OutputDataReceived);
clientProcess.BeginOutputReadLine();
clientProcess.WaitForExit();
}
catch (Exception) { throw; }
finally
{
try { clientProcess.CloseMainWindow(); }
catch (Exception) { }
}
}
clientProcess = null;
// Cleanup
if (Directory.Exists(tempWorkingDirectory))
Directory.Delete(tempWorkingDirectory, true);
CertificateInterop.RemoveTempCerts();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -53,167 +200,20 @@ namespace Disco.ClientBootstrapper
return; return;
if (ex.GetType() == typeof(ThreadInterruptedException)) if (ex.GetType() == typeof(ThreadInterruptedException))
return; return;
Program.WriteAppError(ex); if (ex.GetType() == typeof(OperationCanceledException))
throw;
}
}
private void loop()
{
#if Debug
statusUI.UpdateStatus("Waiting for Debugger", "Please wait...", true, -1);
try
{
do
{
System.Threading.Thread.Sleep(10);
} while (!System.Diagnostics.Debugger.IsAttached);
}
catch (Exception ex)
{
statusUI.UpdateStatus("Error", ex.Message, true, -1);
return;
}
#else
statusUI.UpdateStatus("System Preparation (Bootstrapper)", "Starting", "Please wait...", true, -1);
#endif
tempWorkingDirectory = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Disco\\Temp");
if (!Directory.Exists(tempWorkingDirectory))
Directory.CreateDirectory(tempWorkingDirectory);
// Check for Network Connectivity
statusUI.UpdateStatus(null, "Detecting Network", "Checking network connectivity, Please wait...", true, -1);
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
{
statusUI.UpdateStatus(null, "Detecting Network", "No network connectivity detected, Diagnosing...", true, -1);
statusUI_WriteAdapterInfo();
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
{
// Check for Wireless
var hasWireless = (Interop.NetworkInterop.NetworkAdapters.Count(na => na.IsWireless) > 0);
if (hasWireless)
{
// True: Do wireless loop
statusUI.UpdateStatus(null, "Configuring Wireless Network", "Wireless adapter detected, Configuring...", true, -1);
Interop.NetworkInterop.ConfigureWireless();
statusUI.UpdateStatus(null, "Waiting for Wireless Network", null, true, 0);
for (int i = 0; i < 100; i++)
{
statusUI_WriteAdapterInfo();
statusUI.UpdateStatus(null, null, null, true, i);
Program.SleepThread(500, false);
if (Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
break;
}
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
{
statusUI.UpdateStatus(null, "Wireless Network Failed", "Unable to connect to the wireless network, please connect the network cable...", false);
Program.SleepThread(3000, false);
}
}
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
{
// Instruct user to connect network cable
statusUI.UpdateStatus(null, "Please connect the network cable", null);
for (int i = 0; i < 100; i++)
{
statusUI_WriteAdapterInfo();
statusUI.UpdateStatus(null, null, null, true, i);
Program.SleepThread(500, false);
if (Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
break;
}
}
}
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
{
// Client Failed
if (mLoopCompleteCallback != null)
{
mLoopCompleteCallback.BeginInvoke(null, null);
}
return; return;
} Program.WriteAppError(ex);
} }
// Download Client
statusUI.UpdateStatus(null, "Downloading", "Retrieving Preparation Client, Please wait...", true, -1);
string clientSourceLocation = Path.Combine(tempWorkingDirectory, "PreparationClient.zip");
using (var webClient = new WebClient())
{
// Don't use a proxy when downloading the Client
webClient.Proxy = new WebProxy();
webClient.DownloadFile($"http://{DiscoServerName}:{DiscoServerPort}/Services/Client/PreparationClient", clientSourceLocation);
}
// Unzip Client
statusUI.UpdateStatus(null, "Extracting", "Retrieving Preparation Client, Please wait...", true, -1);
string clientLocation = Path.Combine(tempWorkingDirectory, "PreparationClient");
if (Directory.Exists(clientLocation))
Directory.Delete(clientLocation, true);
Directory.CreateDirectory(clientLocation);
using (var clientSource = Ionic.Zip.ZipFile.Read(clientSourceLocation))
{
clientSource.ExtractAll(clientLocation, Ionic.Zip.ExtractExistingFileAction.OverwriteSilently);
}
// Launch Client
statusUI.UpdateStatus("System Preparation (Client)", "Running", "Launching Preparation Client, Please wait...", true, -1);
ProcessStartInfo clientProcessStart = new ProcessStartInfo(Path.Combine(clientLocation, "Start.bat"))
{
WorkingDirectory = clientLocation,
CreateNoWindow = true,
RedirectStandardOutput = true,
UseShellExecute = false,
};
using (clientProcess = Process.Start(clientProcessStart))
{
// Read StdOutput until End
try
{
clientProcess.OutputDataReceived += new DataReceivedEventHandler(clientProcess_OutputDataReceived);
clientProcess.BeginOutputReadLine();
clientProcess.WaitForExit();
}
catch (Exception) { throw; }
finally
{
try { clientProcess.CloseMainWindow(); }
catch (Exception) { }
}
}
clientProcess = null;
// Cleanup
if (Directory.Exists(tempWorkingDirectory))
Directory.Delete(tempWorkingDirectory, true);
Interop.CertificateInterop.RemoveTempCerts();
// Pause if Error
if (errorMessage.Length > 0)
{
Program.SleepThread(10000, true);
}
// End Of Loop // End Of Loop
if (mLoopCompleteCallback != null) if (completeCallback != null)
{ await completeCallback(cancellationToken);
mLoopCompleteCallback.BeginInvoke(null, null);
}
} }
void statusUI_WriteAdapterInfo() private void statusUI_WriteAdapterInfo()
{ {
var info = new StringBuilder(); var info = new StringBuilder();
foreach (var na in Interop.NetworkInterop.NetworkAdapters) foreach (var na in NetworkInterop.NetworkAdapters)
{ {
if (na.IsWireless) if (na.IsWireless)
{ {
@@ -228,11 +228,10 @@ namespace Disco.ClientBootstrapper
} }
void clientProcess_OutputDataReceived(object sender, DataReceivedEventArgs e) private void clientProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
{ {
if (!string.IsNullOrWhiteSpace(e.Data)) if (!string.IsNullOrWhiteSpace(e.Data))
{ {
Debug.WriteLine($"OUTPUT: {e.Data}");
var data = e.Data.Substring(1).Split(new char[] { ',' }); var data = e.Data.Substring(1).Split(new char[] { ',' });
switch (e.Data[0]) switch (e.Data[0])
{ {
@@ -249,15 +248,5 @@ namespace Disco.ClientBootstrapper
} }
} }
//void clientProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
//{
// if (!string.IsNullOrEmpty(e.Data))
// {
// System.Diagnostics.Debug.WriteLine(string.Format("ERROR: {0}", e.Data));
// this.errorMessage.AppendLine(e.Data);
// statusUI.UpdateStatus(null, "An Error Occurred", this.errorMessage.ToString(), false);
// }
//}
} }
} }
@@ -90,6 +90,9 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\Disco.Client\Interop\EndpointDiscovery.cs">
<Link>Interop\EndpointDiscovery.cs</Link>
</Compile>
<Compile Include="..\Resources\Libraries\DotNetZip\Source\BZip2\BitWriter.cs"> <Compile Include="..\Resources\Libraries\DotNetZip\Source\BZip2\BitWriter.cs">
<Link>DotNetZip\BZip2\BitWriter.cs</Link> <Link>DotNetZip\BZip2\BitWriter.cs</Link>
</Compile> </Compile>
+16 -16
View File
@@ -7,22 +7,22 @@ namespace Disco.ClientBootstrapper
{ {
private delegate void dUpdateStatus(string Heading, string SubHeading, string Message, bool? ShowProgress, int? Progress); private delegate void dUpdateStatus(string Heading, string SubHeading, string Message, bool? ShowProgress, int? Progress);
private dUpdateStatus mUpdateStatus; private readonly dUpdateStatus mUpdateStatus;
public FormStatus() public FormStatus()
{ {
InitializeComponent(); InitializeComponent();
var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
this.labelVersion.Text = $"v{version.ToString(3)}"; labelVersion.Text = $"v{version.ToString(3)}";
this.FormClosed += new FormClosedEventHandler(FormStatus_FormClosed); FormClosed += new FormClosedEventHandler(FormStatus_FormClosed);
mUpdateStatus = new dUpdateStatus(UpdateStatusDo); mUpdateStatus = new dUpdateStatus(UpdateStatusDo);
Cursor.Hide(); Cursor.Hide();
} }
void FormStatus_FormClosed(object sender, FormClosedEventArgs e) private void FormStatus_FormClosed(object sender, FormClosedEventArgs e)
{ {
Cursor.Show(); Cursor.Show();
Program.ExitApplication(); Program.ExitApplication();
@@ -32,43 +32,43 @@ namespace Disco.ClientBootstrapper
{ {
try try
{ {
this.Invoke(mUpdateStatus, Heading, SubHeading, Message, ShowProgress, Progress); Invoke(mUpdateStatus, Heading, SubHeading, Message, ShowProgress, Progress);
} }
catch (Exception) { } catch (Exception) { }
} }
private void UpdateStatusDo(string Heading, string SubHeading, string Message, bool? ShowProgress, int? Progress) private void UpdateStatusDo(string Heading, string SubHeading, string Message, bool? ShowProgress, int? Progress)
{ {
if (Heading != null) if (Heading != null)
if (this.labelHeading.Text != Heading) if (labelHeading.Text != Heading)
this.labelHeading.Text = Heading; labelHeading.Text = Heading;
if (SubHeading != null) if (SubHeading != null)
if (this.labelSubHeading.Text != SubHeading) if (labelSubHeading.Text != SubHeading)
this.labelSubHeading.Text = SubHeading; labelSubHeading.Text = SubHeading;
if (Message != null) if (Message != null)
if (this.labelMessage.Text != Message) if (labelMessage.Text != Message)
this.labelMessage.Text = Message; labelMessage.Text = Message;
if (ShowProgress.HasValue) if (ShowProgress.HasValue)
{ {
if (ShowProgress.Value) if (ShowProgress.Value)
{ {
this.progressBar.Visible = true; progressBar.Visible = true;
if (Progress.HasValue) if (Progress.HasValue)
{ {
if (Progress.Value >= 0) if (Progress.Value >= 0)
{ {
this.progressBar.Value = Math.Min(Progress.Value, 100); progressBar.Value = Math.Min(Progress.Value, 100);
this.progressBar.Style = ProgressBarStyle.Continuous; progressBar.Style = ProgressBarStyle.Continuous;
} }
else else
{ {
this.progressBar.Style = ProgressBarStyle.Marquee; progressBar.Style = ProgressBarStyle.Marquee;
} }
} }
} }
else else
{ {
this.progressBar.Visible = false; progressBar.Visible = false;
} }
} }
} }
+32 -37
View File
@@ -1,54 +1,49 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace Disco.ClientBootstrapper namespace Disco.ClientBootstrapper
{ {
class InstallLoop internal class InstallLoop
{ {
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private readonly string installLocation;
private readonly string wimImageId;
private readonly string tempPath;
private readonly Action completeCallback;
private readonly Uri forcedServerUrl;
public Thread LoopThread; public InstallLoop(string installLocation, string wimImageId, string tempPath, Action completeCallback, Uri forcedServerUrl)
public delegate void CompleteCallback();
private CompleteCallback mCompleteCallback;
private string InstallLocation;
private string WimImageId;
private string TempPath;
public InstallLoop(string InstallLocation, string WimImageId, string TempPath)
{ {
this.InstallLocation = InstallLocation; this.installLocation = installLocation;
this.WimImageId = WimImageId; this.wimImageId = wimImageId;
this.TempPath = TempPath; this.tempPath = tempPath;
this.completeCallback = completeCallback;
this.forcedServerUrl = forcedServerUrl;
} }
public void Start(CompleteCallback Callback) public void Start()
{ {
mCompleteCallback = Callback; var cancellationToken = cancellationTokenSource.Token;
LoopThread = new Thread(new ThreadStart(loopHost)); Task.Run(async () =>
LoopThread.Start();
}
private void loopHost()
{
try
{ {
try
//Program.Status.UpdateStatus(null, null, "Testing UI");
//Program.SleepThread(5000, false);
Interop.InstallInterop.Install(InstallLocation, WimImageId, TempPath);
if (mCompleteCallback != null)
{ {
mCompleteCallback.BeginInvoke(null, null); await Interop.InstallInterop.Install(installLocation, wimImageId, tempPath, forcedServerUrl, cancellationToken);
completeCallback?.BeginInvoke(null, null);
} }
} catch (Exception ex)
catch (Exception ex) {
{ if (ex.GetType() == typeof(ThreadAbortException))
if (ex.GetType() == typeof(ThreadAbortException)) return;
return; if (ex.GetType() == typeof(ThreadInterruptedException))
if (ex.GetType() == typeof(ThreadInterruptedException)) return;
return; if (ex.GetType() == typeof(OperationCanceledException))
Program.WriteAppError(ex); return;
throw; Program.WriteAppError(ex);
} throw;
}
}, cancellationToken);
} }
} }
} }
@@ -4,6 +4,8 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace Disco.ClientBootstrapper.Interop namespace Disco.ClientBootstrapper.Interop
{ {
@@ -20,12 +22,12 @@ namespace Disco.ClientBootstrapper.Interop
//Remove(StoreName.Root, StoreLocation.LocalMachine, _tempCerts); //Remove(StoreName.Root, StoreLocation.LocalMachine, _tempCerts);
} }
} }
public static void AddTempCerts() public static async Task AddTempCerts(CancellationToken cancellationToken)
{ {
if (_tempCerts == null) if (_tempCerts == null)
_tempCerts = new List<string>(); _tempCerts = new List<string>();
var inlineCertificateLocation = Program.InlinePath.Value; var inlineCertificateLocation = Path.GetDirectoryName(typeof(Program).Assembly.Location);
// Root Certificates // Root Certificates
try try
@@ -35,6 +37,7 @@ namespace Disco.ClientBootstrapper.Interop
{ {
foreach (var certFile in CertFiles) foreach (var certFile in CertFiles)
{ {
cancellationToken.ThrowIfCancellationRequested();
var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password"); var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password");
var result = Add(StoreName.Root, StoreLocation.LocalMachine, cert); var result = Add(StoreName.Root, StoreLocation.LocalMachine, cert);
if (result) if (result)
@@ -42,7 +45,7 @@ namespace Disco.ClientBootstrapper.Interop
if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp")) if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp"))
_tempCerts.Add(cert.SerialNumber); _tempCerts.Add(cert.SerialNumber);
Program.Status.UpdateStatus(null, null, $"Added Root Certificate: {cert.ShortSubjectName()}"); Program.Status.UpdateStatus(null, null, $"Added Root Certificate: {cert.ShortSubjectName()}");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
} }
} }
} }
@@ -60,6 +63,7 @@ namespace Disco.ClientBootstrapper.Interop
{ {
foreach (var certFile in CertFiles) foreach (var certFile in CertFiles)
{ {
cancellationToken.ThrowIfCancellationRequested();
var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password"); var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password");
var result = Add(StoreName.CertificateAuthority, StoreLocation.LocalMachine, cert); var result = Add(StoreName.CertificateAuthority, StoreLocation.LocalMachine, cert);
if (result) if (result)
@@ -67,7 +71,7 @@ namespace Disco.ClientBootstrapper.Interop
if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp")) if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp"))
_tempCerts.Add(cert.SerialNumber); _tempCerts.Add(cert.SerialNumber);
Program.Status.UpdateStatus(null, null, $"Added Intermediate Certificate: {cert.ShortSubjectName()}"); Program.Status.UpdateStatus(null, null, $"Added Intermediate Certificate: {cert.ShortSubjectName()}");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
} }
} }
} }
@@ -85,6 +89,7 @@ namespace Disco.ClientBootstrapper.Interop
{ {
foreach (var certFile in CertFiles) foreach (var certFile in CertFiles)
{ {
cancellationToken.ThrowIfCancellationRequested();
var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet); var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
var result = Add(StoreName.My, StoreLocation.LocalMachine, cert); var result = Add(StoreName.My, StoreLocation.LocalMachine, cert);
if (result) if (result)
@@ -92,7 +97,7 @@ namespace Disco.ClientBootstrapper.Interop
if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp")) if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp"))
_tempCerts.Add(cert.SerialNumber); _tempCerts.Add(cert.SerialNumber);
Program.Status.UpdateStatus(null, null, $"Added Host Certificate: {cert.ShortSubjectName()}"); Program.Status.UpdateStatus(null, null, $"Added Host Certificate: {cert.ShortSubjectName()}");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
} }
} }
} }
@@ -4,15 +4,17 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Disco.ClientBootstrapper.Interop namespace Disco.ClientBootstrapper.Interop
{ {
public static class InstallInterop public static class InstallInterop
{ {
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags); private static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags);
[Flags] [Flags]
enum MoveFileFlags private enum MoveFileFlags
{ {
MOVEFILE_REPLACE_EXISTING = 0x00000001, MOVEFILE_REPLACE_EXISTING = 0x00000001,
MOVEFILE_COPY_ALLOWED = 0x00000002, MOVEFILE_COPY_ALLOWED = 0x00000002,
@@ -22,19 +24,19 @@ namespace Disco.ClientBootstrapper.Interop
MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x00000020 MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x00000020
} }
private static void Install(string RootFilesystemLocation, RegistryKey RootRegistryLocation, string FilesystemInstallLocation, string VirtualRootFilesystemLocation) private static async Task Install(string rootFilesystemLocation, RegistryKey rootRegistryLocation, string filesystemInstallLocation, string virtualRootFilesystemLocation, Uri forcedServerUrl, CancellationToken cancellationToken)
{ {
var SourceLocation = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); var SourceLocation = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
var InstallLocation = Path.Combine(RootFilesystemLocation, FilesystemInstallLocation); var InstallLocation = Path.Combine(rootFilesystemLocation, filesystemInstallLocation);
var BootstrapperCmdLinePath = Path.Combine(VirtualRootFilesystemLocation, FilesystemInstallLocation, "Disco.ClientBootstrapper.exe"); var BootstrapperCmdLinePath = Path.Combine(virtualRootFilesystemLocation, filesystemInstallLocation, "Disco.ClientBootstrapper.exe");
var GroupPolicyScriptsIniLocation = Path.Combine(RootFilesystemLocation, "Windows\\System32\\GroupPolicy\\Machine\\Scripts\\scripts.ini"); var GroupPolicyScriptsIniLocation = Path.Combine(rootFilesystemLocation, @"Windows\System32\GroupPolicy\Machine\Scripts\scripts.ini");
var GroupPolicyScriptsIniBackupLocation = Path.Combine(RootFilesystemLocation, "Windows\\System32\\GroupPolicy\\Machine\\Scripts\\disco_scripts.ini"); var GroupPolicyScriptsIniBackupLocation = Path.Combine(rootFilesystemLocation, @"Windows\System32\GroupPolicy\Machine\Scripts\disco_scripts.ini");
// Create file system Location // Create file system Location
#region "Create File System Location" #region "Create File System Location"
Program.Status.UpdateStatus(null, null, "Creating Installation Location"); Program.Status.UpdateStatus(null, null, "Creating Installation Location");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
if (Directory.Exists(InstallLocation)) if (Directory.Exists(InstallLocation))
{ {
// Try and Delete Directory // Try and Delete Directory
@@ -52,19 +54,23 @@ namespace Disco.ClientBootstrapper.Interop
var installDir = Directory.CreateDirectory(InstallLocation); var installDir = Directory.CreateDirectory(InstallLocation);
installDir.Attributes = installDir.Attributes | FileAttributes.Hidden; installDir.Attributes = installDir.Attributes | FileAttributes.Hidden;
} }
cancellationToken.ThrowIfCancellationRequested();
#endregion #endregion
// Copy files to file system location // Copy files to file system location
#region "Copy to File System" #region "Copy to File System"
Program.Status.UpdateStatus(null, null, "Copying Files"); Program.Status.UpdateStatus(null, null, "Copying Files");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
// Copy Bootstrapper // Copy Bootstrapper
// ie: Executing Assembly // ie: Executing Assembly
File.Copy(System.Reflection.Assembly.GetExecutingAssembly().Location, Path.Combine(InstallLocation, "Disco.ClientBootstrapper.exe")); File.Copy(System.Reflection.Assembly.GetExecutingAssembly().Location, Path.Combine(InstallLocation, "Disco.ClientBootstrapper.exe"));
cancellationToken.ThrowIfCancellationRequested();
foreach (var file in Directory.EnumerateFiles(SourceLocation)) foreach (var file in Directory.EnumerateFiles(SourceLocation))
{ {
cancellationToken.ThrowIfCancellationRequested();
var fileName = Path.GetFileName(file); var fileName = Path.GetFileName(file);
// Only Copy Certain Files // Only Copy Certain Files
@@ -86,7 +92,7 @@ namespace Disco.ClientBootstrapper.Interop
// Backup & Create Group Policy Scripts.ini // Backup & Create Group Policy Scripts.ini
#region "Group Policy Scripts.ini" #region "Group Policy Scripts.ini"
Program.Status.UpdateStatus(null, null, "Creating Group Policy Script Entry"); Program.Status.UpdateStatus(null, null, "Creating Group Policy Script Entry");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
// Backup // Backup
if (!File.Exists(GroupPolicyScriptsIniBackupLocation)) if (!File.Exists(GroupPolicyScriptsIniBackupLocation))
{ {
@@ -95,6 +101,7 @@ namespace Disco.ClientBootstrapper.Interop
File.Move(GroupPolicyScriptsIniLocation, GroupPolicyScriptsIniBackupLocation); File.Move(GroupPolicyScriptsIniLocation, GroupPolicyScriptsIniBackupLocation);
} }
} }
cancellationToken.ThrowIfCancellationRequested();
// Create // Create
if (File.Exists(GroupPolicyScriptsIniLocation)) if (File.Exists(GroupPolicyScriptsIniLocation))
@@ -105,56 +112,67 @@ namespace Disco.ClientBootstrapper.Interop
{ {
using (var scriptsIniStreamWriter = new StreamWriter(scriptsIniStream, Encoding.Unicode)) using (var scriptsIniStreamWriter = new StreamWriter(scriptsIniStream, Encoding.Unicode))
{ {
scriptsIniStreamWriter.Write($"[Startup]{Environment.NewLine}0CmdLine={BootstrapperCmdLinePath}{Environment.NewLine}0Parameters=/AllowUninstall"); scriptsIniStreamWriter.WriteLine("[Startup]");
scriptsIniStreamWriter.Flush(); scriptsIniStreamWriter.WriteLine($"0CmdLine={BootstrapperCmdLinePath}");
if (forcedServerUrl == null)
scriptsIniStreamWriter.WriteLine("0Parameters=/AllowUninstall");
else
scriptsIniStreamWriter.WriteLine($"0Parameters=/AllowUninstall {forcedServerUrl}");
} }
} }
cancellationToken.ThrowIfCancellationRequested();
#endregion #endregion
// Backup & Create Group Policy Registry // Backup & Create Group Policy Registry
#region "Group Policy Registry" #region "Group Policy Registry"
Program.Status.UpdateStatus(null, null, "Creating Group Policy Registry Entries"); Program.Status.UpdateStatus(null, null, "Creating Group Policy Registry Entries");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
// Backup Scripts // Backup Scripts
using (var regGroupPolicy = RootRegistryLocation.OpenSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy", true)) using (var regGroupPolicy = rootRegistryLocation.OpenSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy", true))
{ {
if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Scripts") && !regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts")) if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Scripts") && !regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts"))
{ {
RegistryUtilities.RenameSubKey(regGroupPolicy, "Scripts", "Disco_Scripts"); RegistryUtilities.RenameSubKey(regGroupPolicy, "Scripts", "Disco_Scripts");
} }
} }
cancellationToken.ThrowIfCancellationRequested();
// Create Scripts // Create Scripts
RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Shutdown").Dispose(); rootRegistryLocation.CreateSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\Scripts\Shutdown").Dispose();
using (var regScriptsStartup = RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Startup\\0")) using (var regScriptsStartup = rootRegistryLocation.CreateSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\Scripts\Startup\0"))
{ {
regScriptsStartup.SetValue("GPO-ID", "LocalGPO", RegistryValueKind.String); regScriptsStartup.SetValue("GPO-ID", "LocalGPO", RegistryValueKind.String);
regScriptsStartup.SetValue("SOM-ID", "Local", RegistryValueKind.String); regScriptsStartup.SetValue("SOM-ID", "Local", RegistryValueKind.String);
regScriptsStartup.SetValue("FileSysPath", Path.Combine(Environment.SystemDirectory, "GroupPolicy\\Machine"), RegistryValueKind.String); regScriptsStartup.SetValue("FileSysPath", Path.Combine(Environment.SystemDirectory, @"GroupPolicy\Machine"), RegistryValueKind.String);
regScriptsStartup.SetValue("DisplayName", "Local Group Policy", RegistryValueKind.String); regScriptsStartup.SetValue("DisplayName", "Local Group Policy", RegistryValueKind.String);
regScriptsStartup.SetValue("GPOName", "Local Group Policy", RegistryValueKind.String); regScriptsStartup.SetValue("GPOName", "Local Group Policy", RegistryValueKind.String);
regScriptsStartup.SetValue("PSScriptOrder", 1, RegistryValueKind.DWord); regScriptsStartup.SetValue("PSScriptOrder", 1, RegistryValueKind.DWord);
using (var regScriptsStartup0 = regScriptsStartup.CreateSubKey("0")) using (var regScriptsStartup0 = regScriptsStartup.CreateSubKey("0"))
{ {
regScriptsStartup0.SetValue("Script", BootstrapperCmdLinePath, RegistryValueKind.String); regScriptsStartup0.SetValue("Script", BootstrapperCmdLinePath, RegistryValueKind.String);
regScriptsStartup0.SetValue("Parameters", "/AllowUninstall", RegistryValueKind.String); if (forcedServerUrl == null)
regScriptsStartup0.SetValue("Parameters", "/AllowUninstall", RegistryValueKind.String);
else
regScriptsStartup0.SetValue("Parameters", $"/AllowUninstall {forcedServerUrl}", RegistryValueKind.String);
regScriptsStartup0.SetValue("IsPowershell", 0, RegistryValueKind.DWord); regScriptsStartup0.SetValue("IsPowershell", 0, RegistryValueKind.DWord);
regScriptsStartup0.SetValue("ExecTime", new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, RegistryValueKind.Binary); regScriptsStartup0.SetValue("ExecTime", new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, RegistryValueKind.Binary);
} }
} }
RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine\\Scripts\\Shutdown").Dispose(); rootRegistryLocation.CreateSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\Shutdown").Dispose();
cancellationToken.ThrowIfCancellationRequested();
// Backup Scripts State // Backup Scripts State
using (var regGroupPolicy = RootRegistryLocation.OpenSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine", true)) using (var regGroupPolicy = rootRegistryLocation.OpenSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\State\Machine", true))
{ {
if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Scripts") && !regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts")) if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Scripts") && !regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts"))
{ {
RegistryUtilities.RenameSubKey(regGroupPolicy, "Scripts", "Disco_Scripts"); RegistryUtilities.RenameSubKey(regGroupPolicy, "Scripts", "Disco_Scripts");
} }
} }
cancellationToken.ThrowIfCancellationRequested();
// Create Scripts State // Create Scripts State
using (var regStateScriptsStartup = RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine\\Scripts\\Startup\\0")) using (var regStateScriptsStartup = rootRegistryLocation.CreateSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\Startup\0"))
{ {
regStateScriptsStartup.SetValue("GPO-ID", "LocalGPO", RegistryValueKind.String); regStateScriptsStartup.SetValue("GPO-ID", "LocalGPO", RegistryValueKind.String);
regStateScriptsStartup.SetValue("SOM-ID", "Local", RegistryValueKind.String); regStateScriptsStartup.SetValue("SOM-ID", "Local", RegistryValueKind.String);
@@ -165,17 +183,21 @@ namespace Disco.ClientBootstrapper.Interop
using (var regStateScriptsStartup0 = regStateScriptsStartup.CreateSubKey("0")) using (var regStateScriptsStartup0 = regStateScriptsStartup.CreateSubKey("0"))
{ {
regStateScriptsStartup0.SetValue("Script", BootstrapperCmdLinePath, RegistryValueKind.String); regStateScriptsStartup0.SetValue("Script", BootstrapperCmdLinePath, RegistryValueKind.String);
regStateScriptsStartup0.SetValue("Parameters", "/AllowUninstall", RegistryValueKind.String); if (forcedServerUrl == null)
regStateScriptsStartup0.SetValue("Parameters", "/AllowUninstall", RegistryValueKind.String);
else
regStateScriptsStartup0.SetValue("Parameters", $"/AllowUninstall {forcedServerUrl}", RegistryValueKind.String);
regStateScriptsStartup0.SetValue("ExecTime", new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, RegistryValueKind.Binary); regStateScriptsStartup0.SetValue("ExecTime", new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, RegistryValueKind.Binary);
} }
} }
cancellationToken.ThrowIfCancellationRequested();
#endregion #endregion
// Set Registry Startup Environment Policies // Set Registry Startup Environment Policies
#region "Registry Startup Policies" #region "Registry Startup Policies"
Program.Status.UpdateStatus(null, null, "Creating Startup Policy Registry Entries"); Program.Status.UpdateStatus(null, null, "Creating Startup Policy Registry Entries");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
using (var regWinlogon = RootRegistryLocation.OpenSubKey("Microsoft\\Windows NT\\CurrentVersion\\Winlogon", true)) using (var regWinlogon = rootRegistryLocation.OpenSubKey(@"Microsoft\Windows NT\CurrentVersion\Winlogon", true))
{ {
regWinlogon.SetValue("HideStartupScripts", 0, RegistryValueKind.DWord); regWinlogon.SetValue("HideStartupScripts", 0, RegistryValueKind.DWord);
regWinlogon.SetValue("RunStartupScriptSync", 1, RegistryValueKind.DWord); regWinlogon.SetValue("RunStartupScriptSync", 1, RegistryValueKind.DWord);
@@ -183,94 +205,110 @@ namespace Disco.ClientBootstrapper.Interop
#endregion #endregion
} }
public static void Install(string InstallLocation, string WimImageId, string TempPath) public static async Task Install(string installLocation, string wimImageId, string tempPath, Uri forcedServerUrl, CancellationToken cancellationToken)
{ {
Program.Status.UpdateStatus("Installing Bootstrapper", "Starting", "Please wait...", false); Program.Status.UpdateStatus("Installing Bootstrapper", "Starting", "Please wait...", false);
if (string.IsNullOrWhiteSpace(InstallLocation)) if (string.IsNullOrWhiteSpace(installLocation))
InstallLocation = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Disco"); installLocation = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Disco");
if (InstallLocation.EndsWith(".wim", StringComparison.OrdinalIgnoreCase)) cancellationToken.ThrowIfCancellationRequested();
if (installLocation.EndsWith(".wim", StringComparison.OrdinalIgnoreCase))
{ {
// Offline File System (WIM) // Offline File System (WIM)
Program.Status.UpdateStatus("Installing Bootstrapper (Offline)", "Installing", $"Install Location: {InstallLocation}"); Program.Status.UpdateStatus("Installing Bootstrapper (Offline)", "Installing", $"Install Location: {installLocation}");
Program.SleepThread(1000, false); await Program.SleepThread(1000, false, cancellationToken);
// Mount WIM // Mount WIM
int wimImageIndex = 0; int wimImageIndex = 0;
using (var wim = new WIMInterop.WindowsImageContainer(InstallLocation, WIMInterop.WindowsImageContainer.CreateFileMode.OpenExisting, WIMInterop.WindowsImageContainer.CreateFileAccess.Write)) using (var wim = new WIMInterop.WindowsImageContainer(installLocation, WIMInterop.WindowsImageContainer.CreateFileMode.OpenExisting, WIMInterop.WindowsImageContainer.CreateFileAccess.Write))
{ {
if (WimImageId == null) cancellationToken.ThrowIfCancellationRequested();
WimImageId = "1"; if (wimImageId == null)
if (!int.TryParse(WimImageId, out wimImageIndex)) wimImageId = "1";
if (!int.TryParse(wimImageId, out wimImageIndex))
{ {
Program.Status.UpdateStatus(null, "Analysing WIM", $"Looking for Image Name: {WimImageId}"); Program.Status.UpdateStatus(null, "Analysing WIM", $"Looking for Image Name: {wimImageId}");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
for (int i = 0; i < wim.ImageCount; i++) for (int i = 0; i < wim.ImageCount; i++)
{ {
var wimImageInfo = new System.Xml.XmlDocument(); var wimImageInfo = new System.Xml.XmlDocument();
using (var wimImage = wim[i]) using (var wimImage = wim[i])
wimImageInfo.LoadXml(wimImage.ImageInformation); wimImageInfo.LoadXml(wimImage.ImageInformation);
var wimImageInfoName = wimImageInfo.SelectSingleNode("//IMAGE/NAME"); var wimImageInfoName = wimImageInfo.SelectSingleNode("//IMAGE/NAME");
if (wimImageInfoName != null && wimImageInfoName.InnerText.Equals(WimImageId, StringComparison.OrdinalIgnoreCase)) if (wimImageInfoName != null && wimImageInfoName.InnerText.Equals(wimImageId, StringComparison.OrdinalIgnoreCase))
{ {
wimImageIndex = i + 1; wimImageIndex = i + 1;
Program.Status.UpdateStatus(null, "Analysing WIM", $"Found Image Id '{WimImageId}' at Index {wimImageIndex}"); Program.Status.UpdateStatus(null, "Analysing WIM", $"Found Image Id '{wimImageId}' at Index {wimImageIndex}");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
break; break;
} }
} }
} }
} }
cancellationToken.ThrowIfCancellationRequested();
if (wimImageIndex == 0) if (wimImageIndex == 0)
{ {
Program.Status.UpdateStatus(null, "Error", $"Unable to load WIM Image Id: {WimImageId}"); Program.Status.UpdateStatus(null, "Error", $"Unable to load WIM Image Id: {wimImageId}");
Program.SleepThread(5000, false); await Program.SleepThread(5000, false, cancellationToken);
return; return;
} }
// Get Temp Path // Get Temp Path
var wimMountPath = Path.Combine(TempPath ?? Path.GetTempPath(), "DiscoClientBootstrapperWimMount"); var wimMountPath = Path.Combine(tempPath ?? Path.GetTempPath(), "DiscoClientBootstrapperWimMount");
if (Directory.Exists(wimMountPath)) if (Directory.Exists(wimMountPath))
Directory.Delete(wimMountPath, true); Directory.Delete(wimMountPath, true);
Directory.CreateDirectory(wimMountPath); Directory.CreateDirectory(wimMountPath);
var wimTempMountPath = Path.Combine(TempPath ?? Path.GetTempPath(), "DiscoClientBootstrapperWimTempMount"); cancellationToken.ThrowIfCancellationRequested();
var wimTempMountPath = Path.Combine(tempPath ?? Path.GetTempPath(), "DiscoClientBootstrapperWimTempMount");
if (Directory.Exists(wimTempMountPath)) if (Directory.Exists(wimTempMountPath))
Directory.Delete(wimTempMountPath, true); Directory.Delete(wimTempMountPath, true);
Directory.CreateDirectory(wimTempMountPath); Directory.CreateDirectory(wimTempMountPath);
cancellationToken.ThrowIfCancellationRequested();
bool wimCommitChanges = true; bool wimCommitChanges = true;
WIMInterop.WindowsImageContainer.NativeMethods.MessageCallback m_MessageCallback = null; WIMInterop.WindowsImageContainer.NativeMethods.MessageCallback m_MessageCallback = null;
try try
{ {
// Mount WIM // Mount WIM
Program.Status.UpdateStatus(null, "Mounting WIM", $"Mounting WIM Image to '{wimMountPath}'"); Program.Status.UpdateStatus(null, "Mounting WIM", $"Mounting WIM Image to '{wimMountPath}'");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
m_MessageCallback = new WIMInterop.WindowsImageContainer.NativeMethods.MessageCallback(WimImageEventMessagePump); m_MessageCallback = new WIMInterop.WindowsImageContainer.NativeMethods.MessageCallback(WimImageEventMessagePump);
WIMInterop.WindowsImageContainer.NativeMethods.RegisterCallback(m_MessageCallback); WIMInterop.WindowsImageContainer.NativeMethods.RegisterCallback(m_MessageCallback);
WIMInterop.WindowsImageContainer.NativeMethods.MountImage(wimMountPath, InstallLocation, wimImageIndex, wimTempMountPath); WIMInterop.WindowsImageContainer.NativeMethods.MountImage(wimMountPath, installLocation, wimImageIndex, wimTempMountPath);
// Load Local Machine Registry // Load Local Machine Registry
var wimHivePath = Path.Combine(wimMountPath, "Windows\\System32\\config\\SOFTWARE"); var wimHivePath = Path.Combine(wimMountPath, "Windows\\System32\\config\\SOFTWARE");
Program.Status.UpdateStatus(null, "Mounting Offline Registry Hive", $"Mounting Offline Registry Hive at '{wimHivePath}'"); Program.Status.UpdateStatus(null, "Mounting Offline Registry Hive", $"Mounting Offline Registry Hive at '{wimHivePath}'");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
using (var wimReg = new RegistryInterop(RegistryInterop.RegistryHives.HKEY_LOCAL_MACHINE, "DiscoClientBootstrapperWimHive", wimHivePath)) using (var wimReg = new RegistryInterop(RegistryInterop.RegistryHives.HKEY_LOCAL_MACHINE, "DiscoClientBootstrapperWimHive", wimHivePath))
{ {
using (RegistryKey rootRegistryLocation = Registry.LocalMachine.OpenSubKey("DiscoClientBootstrapperWimHive", true)) try
{ {
string rootFileSystemLocation = wimMountPath; cancellationToken.ThrowIfCancellationRequested();
string fileSystemInstallLocation = "Disco"; using (RegistryKey rootRegistryLocation = Registry.LocalMachine.OpenSubKey("DiscoClientBootstrapperWimHive", true))
string virtualRootFileSystemLocation = "C:\\"; {
string rootFileSystemLocation = wimMountPath;
string fileSystemInstallLocation = "Disco";
string virtualRootFileSystemLocation = "C:\\";
Install(rootFileSystemLocation, rootRegistryLocation, fileSystemInstallLocation, virtualRootFileSystemLocation); cancellationToken.ThrowIfCancellationRequested();
await Install(rootFileSystemLocation, rootRegistryLocation, fileSystemInstallLocation, virtualRootFileSystemLocation, forcedServerUrl, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
}
}
finally
{
// Unload Local Machine Registry
Program.Status.UpdateStatus(null, "Unmounting Offline Registry Hive", $"Unmounting Offline Registry Hive at '{wimHivePath}'");
await Program.SleepThread(500, false, cancellationToken);
wimReg.Unload();
} }
// Unload Local Machine Registry
Program.Status.UpdateStatus(null, "Unmounting Offline Registry Hive", $"Unmounting Offline Registry Hive at '{wimHivePath}'");
Program.SleepThread(500, false);
wimReg.Unload();
} }
} }
catch (Exception) catch (Exception)
@@ -282,8 +320,8 @@ namespace Disco.ClientBootstrapper.Interop
{ {
// Unmount WIM // Unmount WIM
Program.Status.UpdateStatus(null, "Unmounting WIM", $"Unmounting WIM Image at '{wimMountPath}'"); Program.Status.UpdateStatus(null, "Unmounting WIM", $"Unmounting WIM Image at '{wimMountPath}'");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
WIMInterop.WindowsImageContainer.NativeMethods.DismountImage(wimMountPath, InstallLocation, wimImageIndex, wimCommitChanges); WIMInterop.WindowsImageContainer.NativeMethods.DismountImage(wimMountPath, installLocation, wimImageIndex, wimCommitChanges);
if (m_MessageCallback != null) if (m_MessageCallback != null)
{ {
@@ -295,23 +333,25 @@ namespace Disco.ClientBootstrapper.Interop
Directory.Delete(wimMountPath, true); Directory.Delete(wimMountPath, true);
if (Directory.Exists(wimTempMountPath)) if (Directory.Exists(wimTempMountPath))
Directory.Delete(wimTempMountPath, true); Directory.Delete(wimTempMountPath, true);
cancellationToken.ThrowIfCancellationRequested();
} }
} }
else else
{ {
// Online File System // Online File System
Program.Status.UpdateStatus("Installing Bootstrapper (Online)", "Installing", $"Install Location: {InstallLocation}", true, -1); Program.Status.UpdateStatus("Installing Bootstrapper (Online)", "Installing", $"Install Location: {installLocation}", true, -1);
Program.SleepThread(1000, false); await Program.SleepThread(1000, false, cancellationToken);
string rootFileSystemLocation = Path.GetPathRoot(InstallLocation); string rootFileSystemLocation = Path.GetPathRoot(installLocation);
RegistryKey rootRegistryLocation = Registry.LocalMachine.OpenSubKey("SOFTWARE", true); RegistryKey rootRegistryLocation = Registry.LocalMachine.OpenSubKey("SOFTWARE", true);
string fileSystemInstallLocation = InstallLocation.Substring(rootFileSystemLocation.Length); string fileSystemInstallLocation = installLocation.Substring(rootFileSystemLocation.Length);
Install(rootFileSystemLocation, rootRegistryLocation, fileSystemInstallLocation, rootFileSystemLocation); await Install(rootFileSystemLocation, rootRegistryLocation, fileSystemInstallLocation, rootFileSystemLocation, forcedServerUrl, cancellationToken);
Program.Status.UpdateStatus(null, "Online File System Installation Complete", string.Empty, true, -1); Program.Status.UpdateStatus(null, "Online File System Installation Complete", string.Empty, true, -1);
Program.SleepThread(1000, false); await Program.SleepThread(1000, false, cancellationToken);
} }
Program.Status.UpdateStatus(null, "Complete", "Finished Installing Bootstrapper"); Program.Status.UpdateStatus(null, "Complete", "Finished Installing Bootstrapper");
Program.SleepThread(1500, false); await Program.SleepThread(1500, false, cancellationToken);
} }
private static uint WimImageEventMessagePump( private static uint WimImageEventMessagePump(
@@ -349,41 +389,28 @@ namespace Disco.ClientBootstrapper.Interop
return status; return status;
} }
public static void Uninstall() public static async Task Uninstall(CancellationToken cancellationToken)
{ {
// Application Directory // Application Directory
var appDirectory = Program.InlinePath.Value; var appDirectory = Path.GetDirectoryName(typeof(Program).Assembly.Location);
if (Program.AllowUninstall && !appDirectory.StartsWith("\\\\")) if (Program.AllowUninstall && !appDirectory.StartsWith(@"\\"))
{ {
Program.Status.UpdateStatus("System Preparation (Bootstrapper)", "Uninstalling Bootstrapper...", string.Empty, false, 0); Program.Status.UpdateStatus("System Preparation (Bootstrapper)", "Uninstalling Bootstrapper...", string.Empty, false, 0);
Program.SleepThread(1000, true); await Program.SleepThread(1000, true, cancellationToken);
//var uninstallScriptLocation = System.IO.Path.Combine(appDirectory, "UninstallBootstrapper.vbs");
//if (System.IO.File.Exists(uninstallScriptLocation))
//{
// var bootstrapperPID = System.Diagnostics.Process.GetCurrentProcess().Id;
// var cscriptPath = System.IO.Path.Combine(Environment.SystemDirectory, "cscript.exe");
// var cscriptArgs = string.Format("\"{0}\" /WaitForProcessID:{1}", uninstallScriptLocation, bootstrapperPID);
// var startProc = new ProcessStartInfo(cscriptPath, cscriptArgs);
// startProc.WorkingDirectory = Environment.SystemDirectory;
// startProc.WindowStyle = ProcessWindowStyle.Hidden;
// Process.Start(startProc);
//}
// Remove Registry Entries // Remove Registry Entries
using (var regWinlogon = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", true)) using (var regWinlogon = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon", true))
{ {
regWinlogon.DeleteValue("HideStartupScripts", false); regWinlogon.DeleteValue("HideStartupScripts", false);
regWinlogon.DeleteValue("RunStartupScriptSync", false); regWinlogon.DeleteValue("RunStartupScriptSync", false);
} }
Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Shutdown", false); Registry.LocalMachine.DeleteSubKeyTree(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\Scripts\Shutdown", false);
Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Startup", false); Registry.LocalMachine.DeleteSubKeyTree(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\Scripts\Startup", false);
Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine\\Scripts\\Shutdown", false); Registry.LocalMachine.DeleteSubKeyTree(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\Shutdown", false);
Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine\\Scripts\\Startup", false); Registry.LocalMachine.DeleteSubKeyTree(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\Startup", false);
// Restore Registry Backups // Restore Registry Backups
using (var regGroupPolicy = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy", true)) using (var regGroupPolicy = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy", true))
{ {
if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts")) if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts"))
{ {
@@ -391,7 +418,7 @@ namespace Disco.ClientBootstrapper.Interop
RegistryUtilities.RenameSubKey(regGroupPolicy, "Disco_Scripts", "Scripts"); RegistryUtilities.RenameSubKey(regGroupPolicy, "Disco_Scripts", "Scripts");
} }
} }
using (var regGroupPolicy = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine", true)) using (var regGroupPolicy = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine", true))
{ {
if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts")) if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts"))
{ {
@@ -401,10 +428,10 @@ namespace Disco.ClientBootstrapper.Interop
} }
// Delete Group Policy Script File // Delete Group Policy Script File
var groupPolicyScriptsPath = Path.Combine(Environment.SystemDirectory, "GroupPolicy\\Machine\\Scripts\\scripts.ini"); var groupPolicyScriptsPath = Path.Combine(Environment.SystemDirectory, @"GroupPolicy\Machine\Scripts\scripts.ini");
if (File.Exists(groupPolicyScriptsPath)) if (File.Exists(groupPolicyScriptsPath))
File.Delete(groupPolicyScriptsPath); File.Delete(groupPolicyScriptsPath);
var groupPolicyScriptsBackupPath = Path.Combine(Environment.SystemDirectory, "GroupPolicy\\Machine\\Scripts\\disco_scripts.ini"); var groupPolicyScriptsBackupPath = Path.Combine(Environment.SystemDirectory, @"GroupPolicy\Machine\Scripts\disco_scripts.ini");
if (File.Exists(groupPolicyScriptsBackupPath)) if (File.Exists(groupPolicyScriptsBackupPath))
File.Move(groupPolicyScriptsBackupPath, groupPolicyScriptsPath); File.Move(groupPolicyScriptsBackupPath, groupPolicyScriptsPath);
@@ -1,14 +1,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Management; using System.Management;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Xml; using System.Xml;
namespace Disco.ClientBootstrapper.Interop namespace Disco.ClientBootstrapper.Interop
{ {
static class NetworkInterop internal static class NetworkInterop
{ {
#region PInvoke #region PInvoke
@@ -164,30 +167,35 @@ namespace Disco.ClientBootstrapper.Interop
} }
} }
public static bool PingDiscoIct(string ServerName) public static bool HasNetworkConnectivity()
{ {
using (Ping p = new Ping()) var nics = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up)
.ToList();
foreach (var nic in nics)
{ {
try if (nic.Supports(NetworkInterfaceComponent.IPv4))
{ {
PingReply pr = p.Send(ServerName, 2000); var ipProps = nic.GetIPProperties();
if (pr.Status == IPStatus.Success) var ipv4Props = ipProps.GetIPv4Properties();
return true; if (ipv4Props.IsAutomaticPrivateAddressingActive)
else continue;
return false;
} return ipProps.UnicastAddresses
catch (Exception) .Where(ua => ua.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{ .Any();
return false;
} }
} }
return false;
} }
public static void ConfigureWireless() public static async Task ConfigureWireless(CancellationToken cancellationToken)
{ {
// Add Certificates // Add Certificates
Program.Status.UpdateStatus(null, null, "Configuring Wireless Certificates"); Program.Status.UpdateStatus(null, null, "Configuring Wireless Certificates");
CertificateInterop.AddTempCerts(); await CertificateInterop.AddTempCerts(cancellationToken);
// Add Wireless Profiles // Add Wireless Profiles
Program.Status.UpdateStatus(null, null, "Configuring Wireless Profiles"); Program.Status.UpdateStatus(null, null, "Configuring Wireless Profiles");
@@ -208,15 +216,16 @@ namespace Disco.ClientBootstrapper.Interop
{ {
foreach (var inlineWirelessProfile in wirelessInlineProfiles) foreach (var inlineWirelessProfile in wirelessInlineProfiles)
{ {
cancellationToken.ThrowIfCancellationRequested();
if (inlineWirelessProfile.AddProfile(wlanHandle, na.Guid)) if (inlineWirelessProfile.AddProfile(wlanHandle, na.Guid))
{ {
Program.Status.UpdateStatus(null, null, $"Added Wireless Profile: {inlineWirelessProfile.ProfileName}"); Program.Status.UpdateStatus(null, null, $"Added Wireless Profile: {inlineWirelessProfile.ProfileName}");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
} }
else else
{ {
Program.Status.UpdateStatus(null, null, $"Unable to add Wireless Profile: {inlineWirelessProfile.ProfileName}"); Program.Status.UpdateStatus(null, null, $"Unable to add Wireless Profile: {inlineWirelessProfile.ProfileName}");
Program.SleepThread(5000, false); await Program.SleepThread(5000, false, cancellationToken);
} }
} }
} }
@@ -246,14 +255,15 @@ namespace Disco.ClientBootstrapper.Interop
private static List<WirelessProfile> GetInlineWirelessProfiles() private static List<WirelessProfile> GetInlineWirelessProfiles()
{ {
var inlineProfileFiles = System.IO.Directory.EnumerateFiles(Program.InlinePath.Value, "WLAN_Profile_*.xml").ToList(); var directoryPath = Path.GetDirectoryName(typeof(Program).Assembly.Location);
var inlineProfileFiles = Directory.EnumerateFiles(directoryPath, "WLAN_Profile_*.xml").ToList();
var inlineProfiles = new List<WirelessProfile>(inlineProfileFiles.Count); var inlineProfiles = new List<WirelessProfile>(inlineProfileFiles.Count);
foreach (var filename in inlineProfileFiles) foreach (var filename in inlineProfileFiles)
{ {
var profile = new WirelessProfile() var profile = new WirelessProfile()
{ {
Filename = filename, Filename = filename,
ProfileXml = System.IO.File.ReadAllText(filename) ProfileXml = File.ReadAllText(filename)
}; };
var profileXml = new XmlDocument(); var profileXml = new XmlDocument();
profileXml.LoadXml(profile.ProfileXml); profileXml.LoadXml(profile.ProfileXml);
+54 -50
View File
@@ -1,36 +1,58 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
namespace Disco.ClientBootstrapper namespace Disco.ClientBootstrapper
{ {
static class Program internal static class Program
{ {
public static IStatus Status { get; set; } private static readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
public static BootstrapperLoop BootstrapperLoop { get; set; } public static IStatus Status { get; private set; }
public static InstallLoop InstallLoop { get; set; }
public static List<string> PostBootstrapperActions { get; set; } public static List<string> PostBootstrapperActions { get; set; }
public static bool AllowUninstall { get; set; } public static bool AllowUninstall { get; private set; }
public static bool ApplicationExiting { get; set; } public static Uri ForcedServerUrl { get; private set; } = null;
public static Lazy<string> InlinePath = new Lazy<string>(() =>
{
return System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
});
/// <summary> /// <summary>
/// The main entry point for the application. /// The main entry point for the application.
/// </summary> /// </summary>
[STAThread] [STAThread]
static void Main(string[] args) private static void Main(string[] args)
{ {
Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException); Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
if (args.Length > 0) if (args.Length > 0)
{ {
#if DEBUG
if (args.Any(a => a.Equals("debug", StringComparison.OrdinalIgnoreCase)))
{
do
{
Console.WriteLine("Waiting for Debugger to Attach");
Thread.Sleep(1000);
} while (!System.Diagnostics.Debugger.IsAttached);
}
#endif
if (args.Any(a => a.StartsWith("http://", StringComparison.OrdinalIgnoreCase)))
throw new ArgumentException("Only HTTPS URLs are supported for a forced server URL.");
var forcedServerArg = args.FirstOrDefault(a => a.StartsWith("https://", StringComparison.OrdinalIgnoreCase));
if (forcedServerArg != null)
{
if (Uri.TryCreate(forcedServerArg, UriKind.Absolute, out var forcedUri))
ForcedServerUrl = forcedUri;
else
throw new ArgumentException("The provided forced server URL is not valid.");
}
switch (args[0].ToLower()) switch (args[0].ToLower())
{ {
case "/install": case "/install":
@@ -46,14 +68,17 @@ namespace Disco.ClientBootstrapper
wimImage = args[2]; wimImage = args[2];
if (args.Length > 3) if (args.Length > 3)
tempPath = args[3]; tempPath = args[3];
InstallLoop = new InstallLoop(installLocation, wimImage, tempPath); var installLoop = new InstallLoop(installLocation, wimImage, tempPath, InstallComplete, ForcedServerUrl);
InstallLoop.Start(new InstallLoop.CompleteCallback(InstallComplete)); installLoop.Start();
Application.Run(); Application.Run();
return; return;
case "/uninstall": case "/uninstall":
AllowUninstall = true; AllowUninstall = true;
Status = new NullStatus(); Status = new NullStatus();
Interop.InstallInterop.Uninstall(); Task.Run(async () =>
{
await Interop.InstallInterop.Uninstall(cancellationTokenSource.Token);
}).Wait(cancellationTokenSource.Token);
return; return;
case "/allowuninstall": case "/allowuninstall":
AllowUninstall = true; AllowUninstall = true;
@@ -71,13 +96,13 @@ namespace Disco.ClientBootstrapper
statusForm.Show(); statusForm.Show();
} }
BootstrapperLoop = new BootstrapperLoop(Status, new BootstrapperLoop.LoopCompleteCallback(LoopComplete)); var bootstrapperLoop = new BootstrapperLoop(Status, ForcedServerUrl, LoopComplete, cancellationTokenSource.Token);
BootstrapperLoop.Start(); bootstrapperLoop.Start();
Application.Run(); Application.Run();
} }
static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{ {
WriteAppError(e.Exception); WriteAppError(e.Exception);
} }
@@ -100,7 +125,7 @@ namespace Disco.ClientBootstrapper
catch (Exception) { } catch (Exception) { }
} }
public static void LoopComplete() public static async Task LoopComplete(CancellationToken cancellationToken)
{ {
// Run Post Actions // Run Post Actions
if (PostBootstrapperActions != null) if (PostBootstrapperActions != null)
@@ -108,32 +133,32 @@ namespace Disco.ClientBootstrapper
// Check Uninstall // Check Uninstall
if (AllowUninstall && PostBootstrapperActions.Contains("UninstallBootstrapper")) if (AllowUninstall && PostBootstrapperActions.Contains("UninstallBootstrapper"))
{ {
Interop.InstallInterop.Uninstall(); await Interop.InstallInterop.Uninstall(cancellationToken);
} }
// Check ShutdownActions // Check ShutdownActions
if (PostBootstrapperActions.Contains("Shutdown")) if (PostBootstrapperActions.Contains("Shutdown"))
{ {
Status.UpdateStatus("System Preparation (Bootstrapper)", "Shutting Down; Finished...", string.Empty, false, 0); Status.UpdateStatus("System Preparation (Bootstrapper)", "Shutting Down; Finished...", string.Empty, false, 0);
SleepThread(4000, true); await SleepThread(4000, true, cancellationToken);
Interop.ShutdownInterop.Shutdown(); Interop.ShutdownInterop.Shutdown();
} }
else if (PostBootstrapperActions.Contains("Reboot")) else if (PostBootstrapperActions.Contains("Reboot"))
{ {
Status.UpdateStatus("System Preparation (Bootstrapper)", "Rebooting; Finished...", string.Empty, false, 0); Status.UpdateStatus("System Preparation (Bootstrapper)", "Rebooting; Finished...", string.Empty, false, 0);
SleepThread(4000, true); await SleepThread(4000, true, cancellationToken);
Interop.ShutdownInterop.Reboot(); Interop.ShutdownInterop.Reboot();
} }
else else
{ {
Status.UpdateStatus("System Preparation (Bootstrapper)", "Starting System; Finished...", string.Empty, false, 0); Status.UpdateStatus("System Preparation (Bootstrapper)", "Starting System; Finished...", string.Empty, false, 0);
SleepThread(2000, true); await SleepThread(2000, true, cancellationToken);
} }
} }
else else
{ {
Status.UpdateStatus("System Preparation (Bootstrapper)", "Starting System; Finished...", string.Empty, false, 0); Status.UpdateStatus("System Preparation (Bootstrapper)", "Starting System; Finished...", string.Empty, false, 0);
SleepThread(2000, true); await SleepThread(2000, true, cancellationToken);
} }
ExitApplication(); ExitApplication();
@@ -146,33 +171,12 @@ namespace Disco.ClientBootstrapper
public static void ExitApplication() public static void ExitApplication()
{ {
if (!ApplicationExiting) if (!cancellationTokenSource.IsCancellationRequested)
{ cancellationTokenSource.Cancel();
ApplicationExiting = true; Application.Exit();
if (BootstrapperLoop != null)
{
if (BootstrapperLoop.LoopThread != null)
{
if (BootstrapperLoop.LoopThread.ThreadState == ThreadState.WaitSleepJoin)
{
BootstrapperLoop.LoopThread.Interrupt();
}
if (BootstrapperLoop.LoopThread.ThreadState == ThreadState.Running)
{
BootstrapperLoop.LoopThread.Abort();
}
}
}
Application.Exit();
}
} }
public static void Trace(string Format, params string[] args) public static async Task SleepThread(int millisecondsTimeout, bool updateUI, CancellationToken cancellationToken)
{
System.Diagnostics.Debug.WriteLine(Format, args);
}
public static void SleepThread(int millisecondsTimeout, bool updateUI)
{ {
if (updateUI) if (updateUI)
{ {
@@ -180,12 +184,12 @@ namespace Disco.ClientBootstrapper
{ {
int progress = Convert.ToInt32(((Convert.ToDouble(i) / Convert.ToDouble(millisecondsTimeout)) * 100)); int progress = Convert.ToInt32(((Convert.ToDouble(i) / Convert.ToDouble(millisecondsTimeout)) * 100));
Status.UpdateStatus(null, null, null, true, progress); Status.UpdateStatus(null, null, null, true, progress);
Thread.Sleep(500); await Task.Delay(500, cancellationToken);
} }
} }
else else
{ {
Thread.Sleep(millisecondsTimeout); await Task.Delay(millisecondsTimeout, cancellationToken);
} }
} }
} }
@@ -14,5 +14,11 @@ namespace Disco.Data.Configuration.Modules
get => Get(DeviceExportOptions.DefaultOptions()); get => Get(DeviceExportOptions.DefaultOptions());
set => Set(value); set => Set(value);
} }
public bool EnrollmentLegacyDiscoveryDisabled
{
get => Get(false);
set => Set(value);
}
} }
} }
+1
View File
@@ -62,6 +62,7 @@
<Compile Include="Repository\FlagType.cs" /> <Compile Include="Repository\FlagType.cs" />
<Compile Include="Repository\User\UserComment.cs" /> <Compile Include="Repository\User\UserComment.cs" />
<Compile Include="Repository\FlagPermission.cs" /> <Compile Include="Repository\FlagPermission.cs" />
<Compile Include="Services\Devices\DeviceEnrolmentServerDiscoveryMethod.cs" />
<Compile Include="Services\Devices\DeviceFlags\DeviceFlagExportOptions.cs" /> <Compile Include="Services\Devices\DeviceFlags\DeviceFlagExportOptions.cs" />
<Compile Include="Services\Devices\DeviceFlags\DeviceFlagExportRecord.cs" /> <Compile Include="Services\Devices\DeviceFlags\DeviceFlagExportRecord.cs" />
<Compile Include="Services\Documents\DocumentExportOptions.cs" /> <Compile Include="Services\Documents\DocumentExportOptions.cs" />
@@ -0,0 +1,13 @@
namespace Disco.Models.Services.Devices
{
public enum DeviceEnrolmentServerDiscoveryMethod
{
Unknown = 0,
Manual = 1,
SRV = 2,
VicSmart = 3,
Legacy = 4,
Mac = 50,
MacSecure = 51,
}
}
@@ -24,6 +24,7 @@ namespace Disco.Models.Services.Interop.DiscoServices
public List<StatisticIntPair> Stat_JobIdentifiers { get; set; } public List<StatisticIntPair> Stat_JobIdentifiers { get; set; }
public List<StatisticJob> Stat_Jobs { get; set; } public List<StatisticJob> Stat_Jobs { get; set; }
public List<StatisticInt> Stat_EnrollmentDiscovery { get; set; }
public class StatisticIntPair public class StatisticIntPair
{ {
@@ -1,8 +1,17 @@
namespace Disco.Models.UI.Config.Enrolment using System;
namespace Disco.Models.UI.Config.Enrolment
{ {
public interface ConfigEnrolmentIndexModel : BaseUIModel public interface ConfigEnrolmentIndexModel : BaseUIModel
{ {
string MacSshUsername { get; set; } string MacSshUsername { get; set; }
int PendingTimeoutMinutes { get; set; } int PendingTimeoutMinutes { get; set; }
Uri MacEnrolUrl { get; set; }
bool HostingPluginInstalled { get; set; }
bool IsVicSmartDeployment { get; set; }
bool IsServicesEducationVicGovAuDomain { get; set; }
string DnsSrvRecordName { get; set; }
string DnsSrvRecordValue { get; set; }
bool LegacyDiscoveryEnabled { get; set; }
} }
} }
@@ -1,6 +1,7 @@
using Disco.Data.Repository; using Disco.Data.Repository;
using Disco.Models.ClientServices; using Disco.Models.ClientServices;
using Disco.Models.Repository; using Disco.Models.Repository;
using Disco.Models.Services.Devices;
using Disco.Services.Authorization; using Disco.Services.Authorization;
using Disco.Services.Interop.ActiveDirectory; using Disco.Services.Interop.ActiveDirectory;
using Disco.Services.Users; using Disco.Services.Users;
@@ -18,6 +19,18 @@ namespace Disco.Services.Devices.Enrolment
private static readonly string pendingIdentifierAlphabet = "23456789ABCDEFGHJKMNPQRSTWXYZ"; private static readonly string pendingIdentifierAlphabet = "23456789ABCDEFGHJKMNPQRSTWXYZ";
private static readonly Random pendingIdentifierRng = new Random(); private static readonly Random pendingIdentifierRng = new Random();
private static readonly ConcurrentDictionary<string, EnrolResponse> pendingEnrolments = new ConcurrentDictionary<string, EnrolResponse>(); private static readonly ConcurrentDictionary<string, EnrolResponse> pendingEnrolments = new ConcurrentDictionary<string, EnrolResponse>();
private static readonly Dictionary<DeviceEnrolmentServerDiscoveryMethod, int> discoveryMethodStatistics = Enum.GetValues(typeof(DeviceEnrolmentServerDiscoveryMethod)).Cast<DeviceEnrolmentServerDiscoveryMethod>().ToDictionary(k => k, k => 0);
public static string GetDnsServiceLocationRecordName()
=> $"_discoict._tcp.{ActiveDirectory.Context.PrimaryDomain.Name}";
public static void IncrementDiscoveryMethod(DeviceEnrolmentServerDiscoveryMethod method)
{
discoveryMethodStatistics[method]++;
}
public static IEnumerable<KeyValuePair<DeviceEnrolmentServerDiscoveryMethod, int>> GetDiscoveryMethodStatistics()
=> discoveryMethodStatistics.AsEnumerable();
private static void CleanupPendingEnrolments() private static void CleanupPendingEnrolments()
{ {
+8
View File
@@ -497,6 +497,14 @@
<Compile Include="Interop\DiscoServices\Upload\UploadOnlineClient.cs" /> <Compile Include="Interop\DiscoServices\Upload\UploadOnlineClient.cs" />
<Compile Include="Interop\DiscoServices\Upload\UploadOnlineService.cs" /> <Compile Include="Interop\DiscoServices\Upload\UploadOnlineService.cs" />
<Compile Include="Interop\DiscoServices\Upload\UploadOnlineSyncTask.cs" /> <Compile Include="Interop\DiscoServices\Upload\UploadOnlineSyncTask.cs" />
<Compile Include="Interop\DNS\ADnsRecord.cs" />
<Compile Include="Interop\DNS\CnameDnsRecord.cs" />
<Compile Include="Interop\DNS\DnsRecord.cs" />
<Compile Include="Interop\DNS\DnsRecordType.cs" />
<Compile Include="Interop\DNS\DnsService.cs" />
<Compile Include="Interop\DNS\NativeDns.cs" />
<Compile Include="Interop\DNS\SrvDnsRecord.cs" />
<Compile Include="Interop\DNS\TxtDnsRecord.cs" />
<Compile Include="Interop\IIS\PreserveIisBindingsTask.cs" /> <Compile Include="Interop\IIS\PreserveIisBindingsTask.cs" />
<Compile Include="Interop\MimeTypes.cs" /> <Compile Include="Interop\MimeTypes.cs" />
<Compile Include="Interop\VicEduDept\VicSmart.cs" /> <Compile Include="Interop\VicEduDept\VicSmart.cs" />
+24
View File
@@ -0,0 +1,24 @@
using System;
using System.Net;
namespace Disco.Services.Interop.DNS
{
public class ADnsRecord : DnsRecord
{
public IPAddress Address { get; }
public ADnsRecord(string name, TimeSpan timeToLive, uint address)
: base(name, DnsRecordType.A, timeToLive, UIntToIPAddress(address).ToString())
{
Address = UIntToIPAddress(address);
}
private static IPAddress UIntToIPAddress(uint address)
{
byte[] bytes = BitConverter.GetBytes(address);
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);
return new IPAddress(bytes);
}
}
}
@@ -0,0 +1,12 @@
using System;
namespace Disco.Services.Interop.DNS
{
public class CnameDnsRecord : DnsRecord
{
public CnameDnsRecord(string name, TimeSpan timeToLive, string canonicalName)
: base(name, DnsRecordType.Cname, timeToLive, canonicalName)
{
}
}
}
+20
View File
@@ -0,0 +1,20 @@
using System;
namespace Disco.Services.Interop.DNS
{
public abstract class DnsRecord
{
public string Name { get; }
public DnsRecordType Type { get; }
public TimeSpan TimeToLive { get; }
public string Content { get; }
protected DnsRecord(string name, DnsRecordType type, TimeSpan timeToLive, string content)
{
Name = name;
Type = type;
TimeToLive = timeToLive;
Content = content;
}
}
}
@@ -0,0 +1,10 @@
namespace Disco.Services.Interop.DNS
{
public enum DnsRecordType
{
A = 0x01,
Cname = 0x05,
Txt = 0x10,
Srv = 0x21
}
}
+30
View File
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
namespace Disco.Services.Interop.DNS
{
public class DnsService
{
public DnsService()
{
}
public static List<T> Query<T>(string name, bool bypassCache = false) where T : DnsRecord
{
DnsRecordType recordType;
if (typeof(T) == typeof(ADnsRecord))
recordType = DnsRecordType.A;
else if (typeof(T) == typeof(CnameDnsRecord))
recordType = DnsRecordType.Cname;
else if (typeof(T) == typeof(TxtDnsRecord))
recordType = DnsRecordType.Txt;
else if (typeof(T) == typeof(SrvDnsRecord))
recordType = DnsRecordType.Srv;
else
throw new NotSupportedException($"Unsupported DNS record type: {typeof(T).Name}");
var records = NativeDns.QueryRecords(recordType, name, bypassCache);
return records.ConvertAll(r => (T)r);
}
}
}
+202
View File
@@ -0,0 +1,202 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;
namespace Disco.Services.Interop.DNS
{
internal static class NativeDns
{
[DllImport("dnsapi", EntryPoint = "DnsQuery_W", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
private static extern int DnsQuery([MarshalAs(UnmanagedType.VBByRefStr)] ref string pszName, NativeDnsQueryTypes wType, NativeDnsQueryOptions options, int aipServers, ref IntPtr ppQueryResults, int pReserved);
[DllImport("dnsapi", CharSet = CharSet.Auto, SetLastError = true)]
private static extern void DnsRecordListFree(IntPtr pRecordList, int FreeType);
private const int DNS_ERROR_RCODE_NAME_ERROR = 0x232B;
private const int DNS_ERROR_BAD_PACKET = 0x251E;
public static List<DnsRecord> QueryRecords(DnsRecordType type, string name, bool bypassCache)
{
NativeDnsQueryTypes queryType;
Func<IntPtr, Tuple<DnsRecord, IntPtr>> marshaller;
switch (type)
{
case DnsRecordType.A:
queryType = NativeDnsQueryTypes.DNS_TYPE_A;
marshaller = MarshalARecord;
break;
case DnsRecordType.Cname:
queryType = NativeDnsQueryTypes.DNS_TYPE_CNAME;
marshaller = MarshalCnameRecord;
break;
case DnsRecordType.Txt:
queryType = NativeDnsQueryTypes.DNS_TYPE_TEXT;
marshaller = MarshalTxtRecord;
break;
case DnsRecordType.Srv:
queryType = NativeDnsQueryTypes.DNS_TYPE_SRV;
marshaller = MarshalSrvRecord;
break;
default:
throw new NotSupportedException($"Unsupported DNS record type: {type}");
}
IntPtr rrPointers = IntPtr.Zero;
var records = new List<DnsRecord>();
var retry = 5;
retry:
try
{
int queryResult = DnsQuery(ref name, queryType, bypassCache ? NativeDnsQueryOptions.DNS_QUERY_BYPASS_CACHE : NativeDnsQueryOptions.DNS_QUERY_STANDARD, 0, ref rrPointers, 0);
if (queryResult != 0)
{
if (queryResult == DNS_ERROR_RCODE_NAME_ERROR)
return records;
else if (queryResult == DNS_ERROR_BAD_PACKET && retry > 0)
{
// Sometimes a BAD_PACKET error is returned, retry a few times
Thread.Sleep(100);
retry--;
goto retry;
}
else
throw new Win32Exception(queryResult);
}
for (var rrPointer = rrPointers; !rrPointer.Equals(IntPtr.Zero);)
{
var (record, rrPointerNext) = marshaller(rrPointer);
records.Add(record);
rrPointer = rrPointerNext;
}
}
finally
{
if (rrPointers != IntPtr.Zero)
DnsRecordListFree(rrPointers, 0);
}
return records;
}
private static Tuple<DnsRecord, IntPtr> MarshalARecord(IntPtr pointer)
{
var native = Marshal.PtrToStructure<NativeDnsAData>(pointer);
var record = new ADnsRecord(native.pName, TimeSpan.FromSeconds(native.dwTtl), native.IpAddress);
return Tuple.Create((DnsRecord)record, native.pNext);
}
private static Tuple<DnsRecord, IntPtr> MarshalCnameRecord(IntPtr pointer)
{
var native = Marshal.PtrToStructure<NativeDnsPtrData>(pointer);
var record = new CnameDnsRecord(native.pName, TimeSpan.FromSeconds(native.dwTtl), native.pNameHost);
return Tuple.Create((DnsRecord)record, native.pNext);
}
private static Tuple<DnsRecord, IntPtr> MarshalTxtRecord(IntPtr pointer)
{
var native = Marshal.PtrToStructure<NativeDnsTxtData>(pointer);
var record = new TxtDnsRecord(native.pName, TimeSpan.FromSeconds(native.dwTtl), native.pStringArray);
return Tuple.Create((DnsRecord)record, native.pNext);
}
private static Tuple<DnsRecord, IntPtr> MarshalSrvRecord(IntPtr pointer)
{
var native = Marshal.PtrToStructure<NativeDnsSrvData>(pointer);
var record = new SrvDnsRecord(native.pName, TimeSpan.FromSeconds(native.dwTtl), native.pNameTarget, native.wPriority, native.wWeight, native.wPort);
return Tuple.Create((DnsRecord)record, native.pNext);
}
private enum NativeDnsQueryOptions
{
DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE = 1,
DNS_QUERY_BYPASS_CACHE = 8,
DNS_QUERY_DONT_RESET_TTL_VALUES = 0x100000,
DNS_QUERY_NO_HOSTS_FILE = 0x40,
DNS_QUERY_NO_LOCAL_NAME = 0x20,
DNS_QUERY_NO_NETBT = 0x80,
DNS_QUERY_NO_RECURSION = 4,
DNS_QUERY_NO_WIRE_QUERY = 0x10,
DNS_QUERY_RESERVED = -16777216,
DNS_QUERY_RETURN_MESSAGE = 0x200,
DNS_QUERY_STANDARD = 0,
DNS_QUERY_TREAT_AS_FQDN = 0x1000,
DNS_QUERY_USE_TCP_ONLY = 2,
DNS_QUERY_WIRE_ONLY = 0x100
}
private enum NativeDnsQueryTypes
{
DNS_TYPE_A = 0x0001,
DNS_TYPE_CNAME = 0x0005,
DNS_TYPE_TEXT = 0x0010,
DNS_TYPE_SRV = 0x0021
}
[StructLayout(LayoutKind.Sequential)]
private struct NativeDnsSrvData
{
public IntPtr pNext;
[MarshalAs(UnmanagedType.LPWStr)]
public string pName;
public ushort wType;
public ushort wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
[MarshalAs(UnmanagedType.LPWStr)]
public string pNameTarget;
public ushort wPriority;
public ushort wWeight;
public ushort wPort;
public ushort Pad;
}
[StructLayout(LayoutKind.Sequential)]
private struct NativeDnsTxtData
{
public IntPtr pNext;
[MarshalAs(UnmanagedType.LPWStr)]
public string pName;
public ushort wType;
public ushort wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
public uint dwStringLength;
[MarshalAs(UnmanagedType.LPWStr)]
public string pStringArray;
}
[StructLayout(LayoutKind.Sequential)]
private struct NativeDnsPtrData
{
public IntPtr pNext;
[MarshalAs(UnmanagedType.LPWStr)]
public string pName;
public ushort wType;
public ushort wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
[MarshalAs(UnmanagedType.LPWStr)]
public string pNameHost;
}
[StructLayout(LayoutKind.Sequential)]
private struct NativeDnsAData
{
public IntPtr pNext;
[MarshalAs(UnmanagedType.LPWStr)]
public string pName;
public ushort wType;
public ushort wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
public uint IpAddress;
}
}
}
@@ -0,0 +1,21 @@
using System;
namespace Disco.Services.Interop.DNS
{
public class SrvDnsRecord : DnsRecord
{
public string Target { get; }
public ushort Priority { get; }
public ushort Weight { get; }
public ushort Port { get; }
public SrvDnsRecord(string name, TimeSpan timeToLive, string target, ushort priority, ushort weight, ushort port)
: base(name, DnsRecordType.Srv, timeToLive, $"{priority} {weight} {port} {target}")
{
Target = target;
Priority = priority;
Weight = weight;
Port = port;
}
}
}
@@ -0,0 +1,12 @@
using System;
namespace Disco.Services.Interop.DNS
{
public class TxtDnsRecord : DnsRecord
{
public TxtDnsRecord(string name, TimeSpan timeToLive, string text)
: base(name, DnsRecordType.Txt, timeToLive, text)
{
}
}
}
@@ -1,6 +1,7 @@
using Disco.Data.Repository; using Disco.Data.Repository;
using Disco.Models.Repository; using Disco.Models.Repository;
using Disco.Models.Services.Interop.DiscoServices; using Disco.Models.Services.Interop.DiscoServices;
using Disco.Services.Devices.Enrolment;
using Disco.Services.Tasks; using Disco.Services.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
@@ -221,6 +222,10 @@ namespace Disco.Services.Interop.DiscoServices
RepairerLogged = j.JobType == JobType.JobTypeIds.HWar ? j.WarrantyRepairerLoggedDate : j.RepairerLoggedDate, RepairerLogged = j.JobType == JobType.JobTypeIds.HWar ? j.WarrantyRepairerLoggedDate : j.RepairerLoggedDate,
RepairerCompleted = j.JobType == JobType.JobTypeIds.HWar ? j.WarrantyRepairerCompletedDate : j.RepairerCompletedDate RepairerCompleted = j.JobType == JobType.JobTypeIds.HWar ? j.WarrantyRepairerCompletedDate : j.RepairerCompletedDate
}).ToList(); }).ToList();
m.Stat_EnrollmentDiscovery = WindowsDeviceEnrolment.GetDiscoveryMethodStatistics()
.Where(s => s.Value != 0)
.Select(s => new StatisticInt() { Key = s.Key.ToString(), Value = s.Value }).ToList();
} }
m.InstalledPlugins = Plugins.Plugins.GetPlugins().Select(manifest => new StatisticString() { Key = manifest.Id, Value = manifest.VersionFormatted }).ToList(); m.InstalledPlugins = Plugins.Plugins.GetPlugins().Select(manifest => new StatisticString() { Key = manifest.Id, Value = manifest.VersionFormatted }).ToList();
@@ -1,13 +1,51 @@
using Disco.Services.Interop.DiscoServices; using Disco.Services.Interop.DiscoServices;
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Net.NetworkInformation;
using System.Xml.Linq; using System.Xml.Linq;
namespace Disco.Services.Interop.VicEduDept namespace Disco.Services.Interop.VicEduDept
{ {
public class VicSmart public class VicSmart
{ {
public static bool IsVicSmartDeployment()
{
var nics = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up)
.ToList();
bool found10Net = false;
foreach (var nic in nics)
{
if (nic.Supports(NetworkInterfaceComponent.IPv4))
{
var ipProps = nic.GetIPProperties();
var ipv4Props = ipProps.GetIPv4Properties();
if (ipv4Props.IsAutomaticPrivateAddressingActive)
continue;
found10Net = ipProps.UnicastAddresses
.Where(ua =>
ua.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork &&
ua.Address.GetAddressBytes()[0] == 10)
.Any();
if (found10Net)
break;
}
}
if (!found10Net)
return false;
try
{
var entry = Dns.GetHostEntry("broadband.doe.wan");
return entry.AddressList.Length > 0;
}
catch (Exception)
{ return false; } // Fail on error
}
/// <summary> /// <summary>
/// Queries DoE VicSmart Service to detect the current site. /// Queries DoE VicSmart Service to detect the current site.
@@ -88,5 +88,21 @@ namespace Disco.Web.Areas.API.Controllers
return BadRequest(ex.Message); return BadRequest(ex.Message);
} }
} }
[DiscoAuthorize(Claims.Config.Enrolment.Configure)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult LegacyDiscovery(bool enabled)
{
try
{
Database.DiscoConfiguration.Devices.EnrollmentLegacyDiscoveryDisabled = !enabled;
Database.SaveChanges();
return Ok();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
} }
} }
@@ -1,7 +1,13 @@
using Disco.Models.UI.Config.Enrolment; using Disco.Models.UI.Config.Enrolment;
using Disco.Services.Authorization; using Disco.Services.Authorization;
using Disco.Services.Devices.Enrolment;
using Disco.Services.Interop.ActiveDirectory;
using Disco.Services.Interop.DNS;
using Disco.Services.Interop.VicEduDept;
using Disco.Services.Plugins;
using Disco.Services.Plugins.Features.UIExtension; using Disco.Services.Plugins.Features.UIExtension;
using Disco.Services.Web; using Disco.Services.Web;
using System;
using System.Linq; using System.Linq;
using System.Web.Mvc; using System.Web.Mvc;
@@ -12,10 +18,30 @@ namespace Disco.Web.Areas.Config.Controllers
[DiscoAuthorize(Claims.Config.Enrolment.Show)] [DiscoAuthorize(Claims.Config.Enrolment.Show)]
public virtual ActionResult Index() public virtual ActionResult Index()
{ {
var serverUrl = Request.Url;
if ((serverUrl.HostNameType == UriHostNameType.Dns && serverUrl.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase)) ||
serverUrl.HostNameType == UriHostNameType.IPv4 || serverUrl.HostNameType == UriHostNameType.IPv6)
{
serverUrl = new UriBuilder(serverUrl)
{
Host = Environment.MachineName
}.Uri;
}
var srvRecord = DnsService.Query<SrvDnsRecord>(WindowsDeviceEnrolment.GetDnsServiceLocationRecordName(), true).FirstOrDefault();
var srvValue = srvRecord == null ? null : (srvRecord.Port == 443 ? srvRecord.Target : $"{srvRecord.Target}:{srvRecord.Port}");
var m = new Models.Enrolment.IndexModel() var m = new Models.Enrolment.IndexModel()
{ {
MacSshUsername = Database.DiscoConfiguration.Bootstrapper.MacSshUsername, MacSshUsername = Database.DiscoConfiguration.Bootstrapper.MacSshUsername,
PendingTimeoutMinutes = (int)Database.DiscoConfiguration.Bootstrapper.PendingTimeout.TotalMinutes, PendingTimeoutMinutes = (int)Database.DiscoConfiguration.Bootstrapper.PendingTimeout.TotalMinutes,
MacEnrolUrl = new Uri(serverUrl, Url.Action(MVC.Services.Client.Unauthenticated("MacSecureEnrol"))),
HostingPluginInstalled = Plugins.PluginInstalled("Hosting"),
IsServicesEducationVicGovAuDomain = ActiveDirectory.Context.PrimaryDomain.Name.Equals("services.education.vic.gov.au", StringComparison.OrdinalIgnoreCase),
IsVicSmartDeployment = VicSmart.IsVicSmartDeployment(),
DnsSrvRecordName = WindowsDeviceEnrolment.GetDnsServiceLocationRecordName(),
DnsSrvRecordValue = srvValue,
LegacyDiscoveryEnabled = !Database.DiscoConfiguration.Devices.EnrollmentLegacyDiscoveryDisabled,
}; };
// UI Extensions // UI Extensions
@@ -1,4 +1,5 @@
using Disco.Models.UI.Config.Enrolment; using Disco.Models.UI.Config.Enrolment;
using System;
namespace Disco.Web.Areas.Config.Models.Enrolment namespace Disco.Web.Areas.Config.Models.Enrolment
{ {
@@ -6,5 +7,12 @@ namespace Disco.Web.Areas.Config.Models.Enrolment
{ {
public string MacSshUsername { get; set; } public string MacSshUsername { get; set; }
public int PendingTimeoutMinutes { get; set; } public int PendingTimeoutMinutes { get; set; }
public Uri MacEnrolUrl { get; set; }
public bool HostingPluginInstalled { get; set; }
public bool IsVicSmartDeployment { get; set; }
public bool IsServicesEducationVicGovAuDomain { get; set; }
public string DnsSrvRecordName { get; set; }
public string DnsSrvRecordValue { get; set; }
public bool LegacyDiscoveryEnabled { get; set; }
} }
} }
@@ -121,7 +121,7 @@
able to connect to the requesting Apple Mac client via <a target="_blank" href="http://en.wikipedia.org/wiki/Secure_Shell">SSH</a>. Enter/Script the following command: able to connect to the requesting Apple Mac client via <a target="_blank" href="http://en.wikipedia.org/wiki/Secure_Shell">SSH</a>. Enter/Script the following command:
</span> </span>
<div class="code"> <div class="code">
curl&nbsp;<a target="_blank" href="http://disco:9292/Services/Client/Unauthenticated/MacSecureEnrol">http://disco:9292/Services/Client/Unauthenticated/MacSecureEnrol</a> curl&nbsp;<a target="_blank" href="@Model.MacEnrolUrl">@Model.MacEnrolUrl</a>
</div> </div>
<span class="smallText">This url will return a <a target="_blank" href="http://json.org/">JSON</a> response containing basic information about the enrolment.</span><br /> <span class="smallText">This url will return a <a target="_blank" href="http://json.org/">JSON</a> response containing basic information about the enrolment.</span><br />
<span class="smallMessage"> <span class="smallMessage">
@@ -133,6 +133,167 @@
</tr> </tr>
</table> </table>
</div> </div>
<div class="form" style="width: 530px; margin-top: 15px">
<h2>Bootstrapper Server Discovery</h2>
<table>
<tr>
<td>
<div>
The Disco ICT
@if (Authorization.Has(Claims.Config.Enrolment.DownloadBootstrapper))
{
@Html.ActionLink("Bootstrapper", MVC.Services.Client.Bootstrapper())
}
else
{
<text>Bootstrapper</text>
}
is used to enrol devices. It is strongly recommended that HTTPS be used for all communication.
the
The @Html.ActionLink("Hosting", Model.HostingPluginInstalled ? MVC.Config.Plugins.Configure("Hosting") : MVC.Config.Plugins.Install())
plugin can be used to automate deployment of HTTPS certificates.
</div>
<div>
The Bootstrapper discovers the server using the first successful method (in order):
</div>
<ol>
<li>
<h5>Manually Specified</h5>
<div>
The server url can be specified at the command line. The url must use HTTPS. For example:
</div>
<div class="code">Disco.ClientBootstrapper.exe https://@Request.Url.Authority</div>
</li>
<li>
<h5>DNS Service Location (SRV) Record</h5>
Expected Record Name: <strong><code>@Model.DnsSrvRecordName</code></strong>
@if (Model.IsServicesEducationVicGovAuDomain)
{
<div class="smallText">
This mechanism is not supported in the shared education.vic.gov.au domain and can be ignored.
</div>
}
else
{
if (Model.DnsSrvRecordValue == null)
{
<div class="info-box">
<span class="error">
No Service Location (SRV) record found.
</span>
@if (Request.IsSecureConnection)
{
<span>
Please create a DNS Service Location (SRV) record:
</span>
<table class="none">
<tr>
<th>Service:</th>
<td><code>_discoict</code></td>
</tr>
<tr>
<th>Protocol:</th>
<td><code>_tcp</code></td>
</tr>
<tr>
<th>Priority:</th>
<td><code>0</code></td>
</tr>
<tr>
<th>Weight:</th>
<td><code>0</code></td>
</tr>
<tr>
<th>Port:</th>
<td><code>@Request.Url.Port</code></td>
</tr>
<tr>
<th>Host offering this service:</th>
<td><code>@Request.Url.Host</code></td>
</tr>
</table>
}
else
{
<div>
Please configure and connect with HTTPS.
<span>
You can enable HTTPS automation using the
@Html.ActionLink("Hosting", Model.HostingPluginInstalled ? MVC.Config.Plugins.Configure("Hosting") : MVC.Config.Plugins.Install())
plugin.
</span>
</div>
}
</div>
}
else
{
<div>
Value: <strong><code>https://@Model.DnsSrvRecordValue</code></strong>
@if (Request.IsSecureConnection && !string.Equals(Model.DnsSrvRecordValue, Request.Url.Authority, StringComparison.OrdinalIgnoreCase))
{
<div class="info-box error">
<i class="fa fa-exclamation"></i> The Service Location (SRV) record does not match the way you are currently accessing the server: <code>@Request.Url.Authority</code>.
</div>
}
</div>
}
}
</li>
@if (Model.IsVicSmartDeployment)
{
<li>
<h5>Victorian Government Schools VicSmart Discovery</h5>
If the Bootstrapper detects it is running inside the VicSmart network, it will query Online Services for the Disco ICT server address based on the subnets assigned to each school.
This is configured in the @Html.ActionLink("Hosting", Model.HostingPluginInstalled ? MVC.Config.Plugins.Configure("Hosting") : MVC.Config.Plugins.Install())
plugin.
</li>
}
<li>
<h5>Legacy Discovery</h5>
<div>
The Bootstrapper will attempt to send an ICMP ping to &quot;<code>disco</code>&quot;. If the ping is successful, it will attempt to connect to <code>http://disco:9292/</code>.
</div>
<div>
@if (canConfig)
{
<input id="Enrolment_LegacyDiscovery" type="checkbox" @(Model.LegacyDiscoveryEnabled ? "checked" : null) />
<script type="text/javascript">
$(function () {
document.DiscoFunctions.PropertyChangeHelper(
$('#Enrolment_LegacyDiscovery'),
null,
'@Url.Action(MVC.API.Enrolment.LegacyDiscovery())',
'enabled'
);
});
</script>
}
else
{
<input id="Enrolment_LegacyDiscovery" type="checkbox" @(Model.LegacyDiscoveryEnabled ? "checked" : null) disabled="disabled" />
}
<label for="Enrolment_LegacyDiscovery">
Legacy Discovery Enabled
</label>
@AjaxHelpers.AjaxLoader()
</div>
@if ((Model.IsServicesEducationVicGovAuDomain || Model.DnsSrvRecordValue != null) && Model.LegacyDiscoveryEnabled)
{
<div class="info-box error">
<i class="fa fa-exclamation-triangle"></i>
It is not recommended to have Legacy Discovery enabled. Please use the latest Bootstrapper and disable this option.
</div>
}
<div>
This method is not secure and is only provided for backwards compatibility. In time this method will be removed.
</div>
</li>
</ol>
</td>
</tr>
</table>
</div>
@if (canShowStatus && Authorization.Has(Claims.Config.Logging.Show)) @if (canShowStatus && Authorization.Has(Claims.Config.Logging.Show))
{ {
<h2>Live Enrolment Logging</h2> <h2>Live Enrolment Logging</h2>
@@ -451,10 +451,26 @@ WriteLiteral(">\r\n curl&nbsp;<a");
WriteLiteral(" target=\"_blank\""); WriteLiteral(" target=\"_blank\"");
WriteLiteral(" href=\"http://disco:9292/Services/Client/Unauthenticated/MacSecureEnrol\""); WriteAttribute("href", Tuple.Create(" href=\"", 4881), Tuple.Create("\"", 4906)
WriteLiteral(">http://disco:9292/Services/Client/Unauthenticated/MacSecureEnrol</a>\r\n " + #line 124 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
" </div>\r\n <span"); , Tuple.Create(Tuple.Create("", 4888), Tuple.Create<System.Object, System.Int32>(Model.MacEnrolUrl
#line default
#line hidden
, 4888), false)
);
WriteLiteral(">");
#line 124 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Model.MacEnrolUrl);
#line default
#line hidden
WriteLiteral("</a>\r\n </div>\r\n <span");
WriteLiteral(" class=\"smallText\""); WriteLiteral(" class=\"smallText\"");
@@ -486,10 +502,521 @@ WriteLiteral(" class=\"code\"");
WriteLiteral(">&lt;script&gt;</span>\r\n tag embedded on the organisation\'s in" + WriteLiteral(">&lt;script&gt;</span>\r\n tag embedded on the organisation\'s in" +
"tranet.\r\n </span>\r\n </td>\r\n </tr>\r\n </table>" + "tranet.\r\n </span>\r\n </td>\r\n </tr>\r\n </table>" +
"\r\n</div>\r\n"); "\r\n</div>\r\n<div");
WriteLiteral(" class=\"form\"");
WriteLiteral(" style=\"width: 530px; margin-top: 15px\"");
WriteLiteral(">\r\n <h2>Bootstrapper Server Discovery</h2>\r\n <table>\r\n <tr>\r\n " +
" <td>\r\n <div>\r\n The Disco ICT\r\n");
#line 136 "..\..\Areas\Config\Views\Enrolment\Index.cshtml" #line 143 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default
#line hidden
#line 143 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (Authorization.Has(Claims.Config.Enrolment.DownloadBootstrapper))
{
#line default
#line hidden
#line 145 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Html.ActionLink("Bootstrapper", MVC.Services.Client.Bootstrapper()));
#line default
#line hidden
#line 145 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
else
{
#line default
#line hidden
WriteLiteral(" ");
WriteLiteral("Bootstrapper");
WriteLiteral("\r\n");
#line 150 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
#line default
#line hidden
WriteLiteral(" is used to enrol devices. It is strongly recommended that HTT" +
"PS be used for all communication.\r\n the\r\n " +
"The ");
#line 153 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Html.ActionLink("Hosting", Model.HostingPluginInstalled ? MVC.Config.Plugins.Configure("Hosting") : MVC.Config.Plugins.Install()));
#line default
#line hidden
WriteLiteral(@"
plugin can be used to automate deployment of HTTPS certificates.
</div>
<div>
The Bootstrapper discovers the server using the first successful method (in order):
</div>
<ol>
<li>
<h5>Manually Specified</h5>
<div>
The server url can be specified at the command line. The url must use HTTPS. For example:
</div>
<div");
WriteLiteral(" class=\"code\"");
WriteLiteral(">Disco.ClientBootstrapper.exe https://");
#line 165 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Request.Url.Authority);
#line default
#line hidden
WriteLiteral("</div>\r\n </li>\r\n <li>\r\n " +
" <h5>DNS Service Location (SRV) Record</h5>\r\n Expected" +
" Record Name: <strong><code>");
#line 169 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Model.DnsSrvRecordName);
#line default
#line hidden
WriteLiteral("</code></strong>\r\n");
#line 170 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default
#line hidden
#line 170 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (Model.IsServicesEducationVicGovAuDomain)
{
#line default
#line hidden
WriteLiteral(" <div");
WriteLiteral(" class=\"smallText\"");
WriteLiteral(">\r\n This mechanism is not supported in the shared " +
"education.vic.gov.au domain and can be ignored.\r\n </d" +
"iv>\r\n");
#line 175 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
else
{
if (Model.DnsSrvRecordValue == null)
{
#line default
#line hidden
WriteLiteral(" <div");
WriteLiteral(" class=\"info-box\"");
WriteLiteral(">\r\n <span");
WriteLiteral(" class=\"error\"");
WriteLiteral(">\r\n No Service Location (SRV) record found" +
".\r\n </span>\r\n");
#line 184 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default
#line hidden
#line 184 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (Request.IsSecureConnection)
{
#line default
#line hidden
WriteLiteral(" <span>\r\n " +
" Please create a DNS Service Location (SRV) record:\r\n " +
" </span>\r\n");
WriteLiteral(" <table");
WriteLiteral(" class=\"none\"");
WriteLiteral(@">
<tr>
<th>Service:</th>
<td><code>_discoict</code></td>
</tr>
<tr>
<th>Protocol:</th>
<td><code>_tcp</code></td>
</tr>
<tr>
<th>Priority:</th>
<td><code>0</code></td>
</tr>
<tr>
<th>Weight:</th>
<td><code>0</code></td>
</tr>
<tr>
<th>Port:</th>
<td><code>");
#line 208 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Request.Url.Port);
#line default
#line hidden
WriteLiteral(@"</code></td>
</tr>
<tr>
<th>Host offering this service:</th>
<td><code>");
#line 212 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Request.Url.Host);
#line default
#line hidden
WriteLiteral("</code></td>\r\n </tr>\r\n " +
" </table>\r\n");
#line 215 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
else
{
#line default
#line hidden
WriteLiteral(@" <div>
Please configure and connect with HTTPS.
<span>
You can enable HTTPS automation using the
");
WriteLiteral(" ");
#line 222 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Html.ActionLink("Hosting", Model.HostingPluginInstalled ? MVC.Config.Plugins.Configure("Hosting") : MVC.Config.Plugins.Install()));
#line default
#line hidden
WriteLiteral("\r\n plugin.\r\n " +
" </span>\r\n </div>\r\n");
#line 226 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
#line default
#line hidden
WriteLiteral(" </div>\r\n");
#line 228 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
else
{
#line default
#line hidden
WriteLiteral(" <div>\r\n Value:" +
" <strong><code>https://");
#line 232 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Model.DnsSrvRecordValue);
#line default
#line hidden
WriteLiteral("</code></strong>\r\n");
#line 233 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default
#line hidden
#line 233 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (Request.IsSecureConnection && !string.Equals(Model.DnsSrvRecordValue, Request.Url.Authority, StringComparison.OrdinalIgnoreCase))
{
#line default
#line hidden
WriteLiteral(" <div");
WriteLiteral(" class=\"info-box error\"");
WriteLiteral(">\r\n <i");
WriteLiteral(" class=\"fa fa-exclamation\"");
WriteLiteral("></i> The Service Location (SRV) record does not match the way you are currently " +
"accessing the server: <code>");
#line 236 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Request.Url.Authority);
#line default
#line hidden
WriteLiteral("</code>.\r\n </div>\r\n");
#line 238 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
#line default
#line hidden
WriteLiteral(" </div>\r\n");
#line 240 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
}
#line default
#line hidden
WriteLiteral(" </li>\r\n");
#line 243 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default
#line hidden
#line 243 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (Model.IsVicSmartDeployment)
{
#line default
#line hidden
WriteLiteral(@" <li>
<h5>Victorian Government Schools VicSmart Discovery</h5>
If the Bootstrapper detects it is running inside the VicSmart network, it will query Online Services for the Disco ICT server address based on the subnets assigned to each school.
This is configured in the ");
#line 248 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Html.ActionLink("Hosting", Model.HostingPluginInstalled ? MVC.Config.Plugins.Configure("Hosting") : MVC.Config.Plugins.Install()));
#line default
#line hidden
WriteLiteral("\r\n plugin.\r\n </li>\r\n");
#line 251 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
#line default
#line hidden
WriteLiteral(@" <li>
<h5>Legacy Discovery</h5>
<div>
The Bootstrapper will attempt to send an ICMP ping to &quot;<code>disco</code>&quot;. If the ping is successful, it will attempt to connect to <code>http://disco:9292/</code>.
</div>
<div>
");
#line 258 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default
#line hidden
#line 258 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (canConfig)
{
#line default
#line hidden
WriteLiteral(" <input");
WriteLiteral(" id=\"Enrolment_LegacyDiscovery\"");
WriteLiteral(" type=\"checkbox\"");
WriteLiteral(" ");
#line 260 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Model.LegacyDiscoveryEnabled ? "checked" : null);
#line default
#line hidden
WriteLiteral(" />\r\n");
WriteLiteral(" <script");
WriteLiteral(" type=\"text/javascript\"");
WriteLiteral(@">
$(function () {
document.DiscoFunctions.PropertyChangeHelper(
$('#Enrolment_LegacyDiscovery'),
null,
'");
#line 266 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Url.Action(MVC.API.Enrolment.LegacyDiscovery()));
#line default
#line hidden
WriteLiteral("\',\r\n \'enabled\'\r\n " +
" );\r\n });\r\n " +
" </script>\r\n");
#line 271 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
else
{
#line default
#line hidden
WriteLiteral(" <input");
WriteLiteral(" id=\"Enrolment_LegacyDiscovery\"");
WriteLiteral(" type=\"checkbox\"");
WriteLiteral(" ");
#line 274 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Model.LegacyDiscoveryEnabled ? "checked" : null);
#line default
#line hidden
WriteLiteral(" disabled=\"disabled\" />\r\n");
#line 275 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
#line default
#line hidden
WriteLiteral(" <label");
WriteLiteral(" for=\"Enrolment_LegacyDiscovery\"");
WriteLiteral(">\r\n Legacy Discovery Enabled\r\n " +
" </label>\r\n");
WriteLiteral(" ");
#line 279 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(AjaxHelpers.AjaxLoader());
#line default
#line hidden
WriteLiteral("\r\n </div>\r\n");
#line 281 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default
#line hidden
#line 281 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if ((Model.IsServicesEducationVicGovAuDomain || Model.DnsSrvRecordValue != null) && Model.LegacyDiscoveryEnabled)
{
#line default
#line hidden
WriteLiteral(" <div");
WriteLiteral(" class=\"info-box error\"");
WriteLiteral(">\r\n <i");
WriteLiteral(" class=\"fa fa-exclamation-triangle\"");
WriteLiteral("></i>\r\n It is not recommended to have Legacy Disco" +
"very enabled. Please use the latest Bootstrapper and disable this option.\r\n " +
" </div>\r\n");
#line 287 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
#line default
#line hidden
WriteLiteral(@" <div>
This method is not secure and is only provided for backwards compatibility. In time this method will be removed.
</div>
</li>
</ol>
</td>
</tr>
</table>
</div>
");
#line 297 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (canShowStatus && Authorization.Has(Claims.Config.Logging.Show)) if (canShowStatus && Authorization.Has(Claims.Config.Logging.Show))
{ {
@@ -499,13 +1026,13 @@ WriteLiteral(">&lt;script&gt;</span>\r\n tag embedded on the
WriteLiteral(" <h2>Live Enrolment Logging</h2>\r\n"); WriteLiteral(" <h2>Live Enrolment Logging</h2>\r\n");
#line 139 "..\..\Areas\Config\Views\Enrolment\Index.cshtml" #line 300 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default #line default
#line hidden #line hidden
#line 139 "..\..\Areas\Config\Views\Enrolment\Index.cshtml" #line 300 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Html.Partial(MVC.Config.Shared.Views.LogEvents, new Disco.Web.Areas.Config.Models.Shared.LogEventsModel() Write(Html.Partial(MVC.Config.Shared.Views.LogEvents, new Disco.Web.Areas.Config.Models.Shared.LogEventsModel()
{ {
IsLive = true, IsLive = true,
@@ -519,7 +1046,7 @@ Write(Html.Partial(MVC.Config.Shared.Views.LogEvents, new Disco.Web.Areas.Config
#line default #line default
#line hidden #line hidden
#line 146 "..\..\Areas\Config\Views\Enrolment\Index.cshtml" #line 307 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
} }
@@ -533,13 +1060,13 @@ WriteLiteral(" class=\"actionBar\"");
WriteLiteral(">\r\n"); WriteLiteral(">\r\n");
#line 149 "..\..\Areas\Config\Views\Enrolment\Index.cshtml" #line 310 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default #line default
#line hidden #line hidden
#line 149 "..\..\Areas\Config\Views\Enrolment\Index.cshtml" #line 310 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (Authorization.Has(Claims.Config.Enrolment.DownloadBootstrapper)) if (Authorization.Has(Claims.Config.Enrolment.DownloadBootstrapper))
{ {
@@ -547,14 +1074,14 @@ WriteLiteral(">\r\n");
#line default #line default
#line hidden #line hidden
#line 151 "..\..\Areas\Config\Views\Enrolment\Index.cshtml" #line 312 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Html.ActionLinkButton("Download Bootstrapper", MVC.Services.Client.Bootstrapper())); Write(Html.ActionLinkButton("Download Bootstrapper", MVC.Services.Client.Bootstrapper()));
#line default #line default
#line hidden #line hidden
#line 151 "..\..\Areas\Config\Views\Enrolment\Index.cshtml" #line 312 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
} }
@@ -564,7 +1091,7 @@ WriteLiteral(">\r\n");
WriteLiteral(" "); WriteLiteral(" ");
#line 153 "..\..\Areas\Config\Views\Enrolment\Index.cshtml" #line 314 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (canShowStatus) if (canShowStatus)
{ {
@@ -572,14 +1099,14 @@ WriteLiteral(" ");
#line default #line default
#line hidden #line hidden
#line 155 "..\..\Areas\Config\Views\Enrolment\Index.cshtml" #line 316 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Html.ActionLinkButton("Enrolment Status", MVC.Config.Enrolment.Status())); Write(Html.ActionLinkButton("Enrolment Status", MVC.Config.Enrolment.Status()));
#line default #line default
#line hidden #line hidden
#line 155 "..\..\Areas\Config\Views\Enrolment\Index.cshtml" #line 316 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
} }
@@ -1,5 +1,6 @@
using Disco.Data.Repository; using Disco.Data.Repository;
using Disco.Models.ClientServices; using Disco.Models.ClientServices;
using Disco.Models.Services.Devices;
using Disco.Services; using Disco.Services;
using Disco.Services.Authorization; using Disco.Services.Authorization;
using Disco.Services.Devices.Enrolment; using Disco.Services.Devices.Enrolment;
@@ -22,11 +23,21 @@ namespace Disco.Web.Areas.Services.Controllers
public virtual ActionResult PreparationClient() public virtual ActionResult PreparationClient()
{ {
var discoveryMethodHeader = Request.Headers["X-DiscoICT-Discovery"];
if (!string.IsNullOrEmpty(discoveryMethodHeader) && Enum.TryParse<DeviceEnrolmentServerDiscoveryMethod>(discoveryMethodHeader, out var discoveryMethod))
WindowsDeviceEnrolment.IncrementDiscoveryMethod(discoveryMethod);
if (!CheckLegacyEnrollmentDiscovery())
return BadRequest("Enrollment Legacy Discovery is disabled. Please use secure connection (HTTPS) for device enrollment.");
return File(Links.ClientBin.PreparationClient_zip, "application/x-msdownload", "PreparationClient.zip"); return File(Links.ClientBin.PreparationClient_zip, "application/x-msdownload", "PreparationClient.zip");
} }
public virtual ActionResult Unauthenticated(string feature) public virtual ActionResult Unauthenticated(string feature)
{ {
if (!CheckLegacyEnrollmentDiscovery())
return BadRequest("Enrollment Legacy Discovery is disabled. Please use secure connection (HTTPS) for device enrollment.");
if (string.IsNullOrEmpty(feature)) if (string.IsNullOrEmpty(feature))
{ {
return Json(null); return Json(null);
@@ -64,6 +75,7 @@ namespace Disco.Web.Areas.Services.Controllers
} }
case "macenrol": case "macenrol":
{ {
WindowsDeviceEnrolment.IncrementDiscoveryMethod(DeviceEnrolmentServerDiscoveryMethod.Mac);
var Binder = ModelBinders.Binders.GetBinder(typeof(MacEnrol)); var Binder = ModelBinders.Binders.GetBinder(typeof(MacEnrol));
var BinderContext = new ModelBindingContext() var BinderContext = new ModelBindingContext()
{ {
@@ -78,6 +90,7 @@ namespace Disco.Web.Areas.Services.Controllers
} }
case "macsecureenrol": case "macsecureenrol":
{ {
WindowsDeviceEnrolment.IncrementDiscoveryMethod(DeviceEnrolmentServerDiscoveryMethod.MacSecure);
using (var database = new DiscoDataContext()) using (var database = new DiscoDataContext())
{ {
var host = HttpContext.Request.UserHostAddress; var host = HttpContext.Request.UserHostAddress;
@@ -93,6 +106,9 @@ namespace Disco.Web.Areas.Services.Controllers
[Authorize] [Authorize]
public virtual ActionResult Authenticated(string feature) public virtual ActionResult Authenticated(string feature)
{ {
if (!CheckLegacyEnrollmentDiscovery())
return BadRequest("Enrollment Legacy Discovery is disabled. Please use secure connection (HTTPS) for device enrollment.");
if (string.IsNullOrEmpty(feature)) if (string.IsNullOrEmpty(feature))
{ {
WhoAmIResponse whoAmIResponse = new WhoAmI().BuildResponse(); WhoAmIResponse whoAmIResponse = new WhoAmI().BuildResponse();
@@ -171,5 +187,21 @@ namespace Disco.Web.Areas.Services.Controllers
return Content("Error Message Logged"); return Content("Error Message Logged");
} }
private bool CheckLegacyEnrollmentDiscovery()
{
if (!Request.IsSecureConnection)
{
using (DiscoDataContext database = new DiscoDataContext())
{
if (database.DiscoConfiguration.Devices.EnrollmentLegacyDiscoveryDisabled)
{
EnrolmentLog.LogClientError(Request.UserHostAddress, Request.UserHostName, string.Empty, "Enrollment Legacy Discovery is disabled. Please use secure connection (HTTPS) for device enrollment.", string.Empty);
return false;
}
}
}
return true;
}
} }
} }
@@ -83,6 +83,12 @@ namespace Disco.Web.Areas.API.Controllers
{ {
return new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.MacSshPassword); return new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.MacSshPassword);
} }
[NonAction]
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public virtual System.Web.Mvc.ActionResult LegacyDiscovery()
{
return new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.LegacyDiscovery);
}
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public EnrolmentController Actions { get { return MVC.API.Enrolment; } } public EnrolmentController Actions { get { return MVC.API.Enrolment; } }
@@ -103,6 +109,7 @@ namespace Disco.Web.Areas.API.Controllers
public readonly string PendingTimeoutMinutes = "PendingTimeoutMinutes"; public readonly string PendingTimeoutMinutes = "PendingTimeoutMinutes";
public readonly string MacSshUsername = "MacSshUsername"; public readonly string MacSshUsername = "MacSshUsername";
public readonly string MacSshPassword = "MacSshPassword"; public readonly string MacSshPassword = "MacSshPassword";
public readonly string LegacyDiscovery = "LegacyDiscovery";
} }
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
@@ -112,6 +119,7 @@ namespace Disco.Web.Areas.API.Controllers
public const string PendingTimeoutMinutes = "PendingTimeoutMinutes"; public const string PendingTimeoutMinutes = "PendingTimeoutMinutes";
public const string MacSshUsername = "MacSshUsername"; public const string MacSshUsername = "MacSshUsername";
public const string MacSshPassword = "MacSshPassword"; public const string MacSshPassword = "MacSshPassword";
public const string LegacyDiscovery = "LegacyDiscovery";
} }
@@ -151,6 +159,14 @@ namespace Disco.Web.Areas.API.Controllers
{ {
public readonly string MacSshPassword = "MacSshPassword"; public readonly string MacSshPassword = "MacSshPassword";
} }
static readonly ActionParamsClass_LegacyDiscovery s_params_LegacyDiscovery = new ActionParamsClass_LegacyDiscovery();
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public ActionParamsClass_LegacyDiscovery LegacyDiscoveryParams { get { return s_params_LegacyDiscovery; } }
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public class ActionParamsClass_LegacyDiscovery
{
public readonly string enabled = "enabled";
}
static readonly ViewsClass s_views = new ViewsClass(); static readonly ViewsClass s_views = new ViewsClass();
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public ViewsClass Views { get { return s_views; } } public ViewsClass Views { get { return s_views; } }
@@ -222,6 +238,18 @@ namespace Disco.Web.Areas.API.Controllers
return callInfo; return callInfo;
} }
[NonAction]
partial void LegacyDiscoveryOverride(T4MVC_System_Web_Mvc_ActionResult callInfo, bool enabled);
[NonAction]
public override System.Web.Mvc.ActionResult LegacyDiscovery(bool enabled)
{
var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.LegacyDiscovery);
ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "enabled", enabled);
LegacyDiscoveryOverride(callInfo, enabled);
return callInfo;
}
} }
} }
+3 -2
View File
@@ -145,8 +145,9 @@ Global
UpdateAssemblyVersion = True UpdateAssemblyVersion = True
UpdateAssemblyFileVersion = True UpdateAssemblyFileVersion = True
UpdateAssemblyInfoVersion = False UpdateAssemblyInfoVersion = False
AssemblyVersionSettings = None.None.DateStamp.TimeStamp ShouldCreateLogs = True
AssemblyFileVersionSettings = None.None.DateStamp.TimeStamp AssemblyVersionSettings = None.None.DateStamp.None
AssemblyFileVersionSettings = None.None.DateStamp.None
UpdatePackageVersion = False UpdatePackageVersion = False
AssemblyInfoVersionType = SettingsVersion AssemblyInfoVersionType = SettingsVersion
InheritWinAppVersionFrom = None InheritWinAppVersionFrom = None