Files
Gary Sharp 48512fa9d1 qol: offline domain join to reuse AD computer accounts
Replaces old behaviour of deleting and creating new accounts. Now when a device has a new name, its existing account is renamed and reused.
2026-02-25 14:34:34 +11:00

196 lines
8.8 KiB
C#

using Disco.Client.Interop;
using Disco.Models.ClientServices;
using Microsoft.Win32;
using System;
using System.Runtime.InteropServices;
namespace Disco.Client.Extensions
{
internal static class EnrolExtensions
{
public static void Build(this Enrol enrol)
{
enrol.ComputerName = Environment.MachineName;
enrol.RunningUserDomain = Environment.UserDomainName;
enrol.RunningUserName = Environment.UserName;
enrol.RunningInteractively = Environment.UserInteractive;
// Hardware Audit
enrol.Hardware = Hardware.Information;
enrol.SerialNumber = enrol.Hardware.SerialNumber;
// Apply System Information
enrol.ApplySystemInformation();
// Certificates
enrol.Certificates = Certificates.GetAllCertificates();
// Wireless Profiles
enrol.WirelessProfiles = WirelessNetwork.GetWirelessProfiles();
}
public static void Process(this EnrolResponse enrolResponse)
{
if (enrolResponse == null)
throw new ClientServiceException("Enrolment", "Server denied enrolment (Empty Response)");
ErrorReporting.EnrolmentSessionId = enrolResponse.SessionId;
if (!string.IsNullOrEmpty(enrolResponse.ErrorMessage))
throw new ClientServiceException("Enrolment", enrolResponse.ErrorMessage);
if (enrolResponse.IsPending)
return;
// Offline Domain Join
bool requireReboot = enrolResponse.ApplyOfflineDomainJoin();
// Device Owner
enrolResponse.ApplyDeviceAssignedUser();
// Certificates
enrolResponse.ApplyDeviceCertificates();
// Wireless Profiles
enrolResponse.ApplyWirelessProfiles();
Presentation.UpdateStatus("Enrolling Device", "Device Enrolment Successfully Completed", false, 0, 1500);
Program.RebootRequired = requireReboot;
Program.AllowUninstall = enrolResponse.AllowBootstrapperUninstall;
}
[Flags]
private enum NETSETUP_PROVISION_FLAGS : int
{
NETSETUP_PROVISION_DOWNLEVEL_PRIV_SUPPORT = 0x00000001,
NETSETUP_PROVISION_REUSE_ACCOUNT = 0x00000002,
NETSETUP_PROVISION_USE_DEFAULT_PASSWORD = 0x00000004,
NETSETUP_PROVISION_SKIP_ACCOUNT_SEARCH = 0x00000008,
NETSETUP_PROVISION_ROOT_CA_CERTS = 0x00000010,
NETSETUP_PROVISION_PERSISTENTSITE = 0x00000020,
NETSETUP_PROVISION_ONLINE_CALLER = 0x40000000,
NETSETUP_PROVISION_CHECK_PWD_ONLY = unchecked((int)0x80000000),
}
[DllImport("Netapi32.dll", CallingConvention = CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.I4)]
private static extern int NetRequestOfflineDomainJoin(
[In] IntPtr pProvisionBinData,
[In, MarshalAs(UnmanagedType.I4)] int cbProvisionBinDataSize,
[In, MarshalAs(UnmanagedType.I4)] NETSETUP_PROVISION_FLAGS dwOptions,
[In, MarshalAs(UnmanagedType.LPWStr)] string lpWindowsPath
);
/// <summary>
/// Processes a Client Service Enrol Response for Offline Domain Join Actions
/// </summary>
/// <param name="enrolResponse"></param>
/// <returns>Boolean indicating whether a reboot is required.</returns>
private static bool ApplyOfflineDomainJoin(this EnrolResponse enrolResponse)
{
if (!string.IsNullOrWhiteSpace(enrolResponse.OfflineDomainJoinManifest))
{
Presentation.UpdateStatus("Enrolling Device", $"Performing Offline Domain Join:\r\nRenaming Computer: {Environment.MachineName} -> {enrolResponse.ComputerName}", true, -1, 1500);
var provisionData = Convert.FromBase64String(enrolResponse.OfflineDomainJoinManifest);
string systemRoot = Environment.GetEnvironmentVariable("SystemRoot");
var provisionDataPointer = Marshal.AllocCoTaskMem(provisionData.Length);
Marshal.Copy(provisionData, 0, provisionDataPointer, provisionData.Length);
var joinResult = default(int);
try
{
joinResult = NetRequestOfflineDomainJoin(provisionDataPointer, provisionData.Length, NETSETUP_PROVISION_FLAGS.NETSETUP_PROVISION_ONLINE_CALLER, systemRoot);
}
finally
{
Marshal.FreeCoTaskMem(provisionDataPointer);
}
if (joinResult != 0)
{
var win32Exception = new System.ComponentModel.Win32Exception(joinResult);
Presentation.UpdateStatus("Enrolling Device", $"Offline Domain Join Failed:\r\n{win32Exception.Message} [{joinResult}]", true, -1, 3000);
throw new InvalidOperationException($"Offline Domain Join Failed:\r\n{win32Exception.Message} [{joinResult}]");
}
else
{
Presentation.UpdateStatus("Enrolling Device", $"Offline Domain Join Succeeded", true, -1, 2000);
}
// Flush Logged-On History
if (enrolResponse.SetAssignedUserForLogon && !string.IsNullOrEmpty(enrolResponse.DomainName))
{
using (RegistryKey regWinlogon = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon", true))
{
regWinlogon.SetValue("DefaultDomainName", enrolResponse.DomainName, RegistryValueKind.String);
regWinlogon.SetValue("DefaultUserName", string.Empty, RegistryValueKind.String);
}
using (RegistryKey regLogonUI = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI", true))
{
regLogonUI.DeleteValue("LastLoggedOnUser", false);
}
}
return true; // Reboot required
}
else
{
// No Domain Join
return false; // Reboot not required
}
}
/// <summary>
/// Processes a Client Service Enrol Response for Device Assigned User Actions
/// </summary>
/// <param name="enrolResponse"></param>
private static void ApplyDeviceAssignedUser(this EnrolResponse enrolResponse)
{
// Only run task if Assigned User was specified
if (!string.IsNullOrWhiteSpace(enrolResponse.AssignedUserSID))
{
Presentation.UpdateStatus("Enrolling Device", $"Configuring the device owner:\r\n{enrolResponse.AssignedUserDescription} ({enrolResponse.AssignedUserDomain}\\{enrolResponse.AssignedUserUsername})", true, -1, 3000);
if (enrolResponse.AssignedUserIsLocalAdmin)
LocalAuthentication.AddLocalGroupMembership("Administrators", enrolResponse.AssignedUserSID, enrolResponse.AssignedUserUsername, enrolResponse.AssignedUserDomain);
}
if (enrolResponse.SetAssignedUserForLogon && !string.IsNullOrEmpty(enrolResponse.AssignedUserDomain) && !string.IsNullOrEmpty(enrolResponse.AssignedUserUsername))
{
// Make Windows think this user was the last to logon
using (RegistryKey regWinlogon = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon", true))
{
regWinlogon.SetValue("DefaultDomainName", enrolResponse.AssignedUserDomain, RegistryValueKind.String);
regWinlogon.SetValue("DefaultUserName", enrolResponse.AssignedUserUsername, RegistryValueKind.String);
}
using (RegistryKey regLogonUI = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI", true))
{
regLogonUI.SetValue("LastLoggedOnUser", $@"{enrolResponse.AssignedUserDomain}\{enrolResponse.AssignedUserUsername}", RegistryValueKind.String);
}
}
}
private static void ApplyDeviceCertificates(this EnrolResponse enrolResponse)
{
if (enrolResponse.Certificates != null)
{
Presentation.UpdateStatus("Enrolling Device", "Configuring Certificates", true, -1, 1000);
enrolResponse.Certificates.Apply();
}
}
private static void ApplyWirelessProfiles(this EnrolResponse enrolResponse)
{
if (enrolResponse.WirelessProfiles != null)
{
Presentation.UpdateStatus("Enrolling Device", "Configuring Wireless Profiles", true, -1, 1000);
enrolResponse.WirelessProfiles.Apply();
}
}
}
}