48512fa9d1
Replaces old behaviour of deleting and creating new accounts. Now when a device has a new name, its existing account is renamed and reused.
101 lines
5.2 KiB
C#
101 lines
5.2 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace Disco.Services.Interop.ActiveDirectory
|
|
{
|
|
public static class ADDeviceOfflineDomainJoining
|
|
{
|
|
[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 NetProvisionComputerAccount(
|
|
[In, MarshalAs(UnmanagedType.LPWStr)] string lpDomain,
|
|
[In, MarshalAs(UnmanagedType.LPWStr)] string lpMachineName,
|
|
[In, Optional, MarshalAs(UnmanagedType.LPWStr)] string lpMachineAccountOU,
|
|
[In, Optional, MarshalAs(UnmanagedType.LPWStr)] string lpDcName,
|
|
[In, MarshalAs(UnmanagedType.I4)] NETSETUP_PROVISION_FLAGS dwOptions,
|
|
out IntPtr pProvisionBinData,
|
|
out int pdwProvisionBinDataSize,
|
|
[Optional] IntPtr pProvisionTextData
|
|
);
|
|
|
|
[DllImport("Netapi32.dll", SetLastError = true)]
|
|
private static extern int NetApiBufferFree(IntPtr Buffer);
|
|
|
|
public static string OfflineDomainJoinProvision(this ADDomainController dc, string computerSamAccountName, string organisationalUnit, ref ADMachineAccount machineAccount)
|
|
{
|
|
if (machineAccount != null && machineAccount.IsCriticalSystemObject)
|
|
throw new InvalidOperationException($"This account {machineAccount.DistinguishedName} is a Critical System Active Directory Object and Disco ICT refuses to modify it");
|
|
|
|
if (!dc.IsWritable)
|
|
throw new InvalidOperationException($"The domain controller [{dc.Name}] is not writable. This action (Offline Domain Join Provision) requires a writable domain controller.");
|
|
|
|
if (!string.IsNullOrWhiteSpace(computerSamAccountName))
|
|
computerSamAccountName = computerSamAccountName.TrimEnd('$');
|
|
if (!string.IsNullOrWhiteSpace(computerSamAccountName) && computerSamAccountName.Contains('\\'))
|
|
computerSamAccountName = computerSamAccountName.Substring(computerSamAccountName.IndexOf('\\') + 1);
|
|
|
|
// NetBIOS Limit (16 characters; "{ComputerName}$"; 15 characters allowed)
|
|
if (string.IsNullOrWhiteSpace(computerSamAccountName) || computerSamAccountName.Length > 15)
|
|
throw new ArgumentException("Invalid Computer Name; > 0 and <= 15", "ComputerName");
|
|
|
|
// Ensure Specified OU Exists
|
|
if (!string.IsNullOrEmpty(organisationalUnit))
|
|
{
|
|
try
|
|
{
|
|
using (var deOU = dc.RetrieveDirectoryEntry(organisationalUnit, new string[] { "distinguishedName" }))
|
|
{
|
|
if (deOU == null)
|
|
throw new Exception($"OU's Directory Entry couldn't be found at [{organisationalUnit}]");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new ArgumentException($"An error occurred while trying to locate the specified OU: {organisationalUnit}", "OrganisationalUnit", ex);
|
|
}
|
|
}
|
|
|
|
if (machineAccount != null && !string.Equals(machineAccount.Name, computerSamAccountName, StringComparison.Ordinal))
|
|
{
|
|
// rename the account
|
|
machineAccount.RenameAccount(dc, computerSamAccountName);
|
|
}
|
|
|
|
var result = NetProvisionComputerAccount(dc.Domain.Name, computerSamAccountName, string.IsNullOrWhiteSpace(organisationalUnit) ? null : organisationalUnit, dc.Name, NETSETUP_PROVISION_FLAGS.NETSETUP_PROVISION_REUSE_ACCOUNT, out var provisionDataPointer, out var provisionDataLength);
|
|
|
|
if (result != 0)
|
|
{
|
|
var win32Exception = new System.ComponentModel.Win32Exception(result);
|
|
throw new InvalidOperationException($"NetProvisionComputerAccount failed with error code {result}: {win32Exception.Message}");
|
|
}
|
|
|
|
if (provisionDataPointer == IntPtr.Zero || provisionDataLength == 0)
|
|
throw new InvalidOperationException("NetProvisionComputerAccount did not return valid provisioning data.");
|
|
|
|
var buffer = new byte[provisionDataLength];
|
|
Marshal.Copy(provisionDataPointer, buffer, 0, provisionDataLength);
|
|
var freeResult = NetApiBufferFree(provisionDataPointer);
|
|
var encodedResult = Convert.ToBase64String(buffer);
|
|
|
|
// Reload Machine Account
|
|
machineAccount = dc.RetrieveADMachineAccount($@"{dc.Domain.NetBiosName}\{computerSamAccountName}", (machineAccount == null ? null : machineAccount.LoadedProperties.Keys.ToArray()));
|
|
|
|
return encodedResult;
|
|
}
|
|
}
|
|
}
|