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.
This commit is contained in:
@@ -2,8 +2,7 @@
|
|||||||
using Disco.Models.ClientServices;
|
using Disco.Models.ClientServices;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Runtime.InteropServices;
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace Disco.Client.Extensions
|
namespace Disco.Client.Extensions
|
||||||
{
|
{
|
||||||
@@ -61,6 +60,28 @@ namespace Disco.Client.Extensions
|
|||||||
Program.AllowUninstall = enrolResponse.AllowBootstrapperUninstall;
|
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>
|
/// <summary>
|
||||||
/// Processes a Client Service Enrol Response for Offline Domain Join Actions
|
/// Processes a Client Service Enrol Response for Offline Domain Join Actions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -72,30 +93,31 @@ namespace Disco.Client.Extensions
|
|||||||
{
|
{
|
||||||
Presentation.UpdateStatus("Enrolling Device", $"Performing Offline Domain Join:\r\nRenaming Computer: {Environment.MachineName} -> {enrolResponse.ComputerName}", true, -1, 1500);
|
Presentation.UpdateStatus("Enrolling Device", $"Performing Offline Domain Join:\r\nRenaming Computer: {Environment.MachineName} -> {enrolResponse.ComputerName}", true, -1, 1500);
|
||||||
|
|
||||||
string odjFile = Path.GetTempFileName();
|
var provisionData = Convert.FromBase64String(enrolResponse.OfflineDomainJoinManifest);
|
||||||
File.WriteAllBytes(odjFile, Convert.FromBase64String(enrolResponse.OfflineDomainJoinManifest));
|
string systemRoot = Environment.GetEnvironmentVariable("SystemRoot");
|
||||||
|
|
||||||
string odjWindowsPath = Environment.GetEnvironmentVariable("SystemRoot");
|
var provisionDataPointer = Marshal.AllocCoTaskMem(provisionData.Length);
|
||||||
string odjProcessArguments = $"/REQUESTODJ /LOADFILE \"{odjFile}\" /WINDOWSPATH \"{odjWindowsPath}\" /LOCALOS";
|
Marshal.Copy(provisionData, 0, provisionDataPointer, provisionData.Length);
|
||||||
|
var joinResult = default(int);
|
||||||
ProcessStartInfo odjProcessStartInfo = new ProcessStartInfo("DJOIN.EXE", odjProcessArguments)
|
try
|
||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
joinResult = NetRequestOfflineDomainJoin(provisionDataPointer, provisionData.Length, NETSETUP_PROVISION_FLAGS.NETSETUP_PROVISION_ONLINE_CALLER, systemRoot);
|
||||||
ErrorDialog = false,
|
}
|
||||||
LoadUserProfile = true,
|
finally
|
||||||
RedirectStandardOutput = true,
|
{
|
||||||
UseShellExecute = false
|
Marshal.FreeCoTaskMem(provisionDataPointer);
|
||||||
};
|
|
||||||
string odjResult;
|
|
||||||
using (Process odjProcess = System.Diagnostics.Process.Start(odjProcessStartInfo))
|
|
||||||
{
|
|
||||||
odjResult = odjProcess.StandardOutput.ReadToEnd();
|
|
||||||
odjProcess.WaitForExit(20000); // 20 Seconds
|
|
||||||
}
|
}
|
||||||
Presentation.UpdateStatus("Enrolling Device", $"Offline Domain Join Result:\r\n{odjResult}", true, -1, 3000);
|
|
||||||
|
|
||||||
if (File.Exists(odjFile))
|
if (joinResult != 0)
|
||||||
File.Delete(odjFile);
|
{
|
||||||
|
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
|
// Flush Logged-On History
|
||||||
if (enrolResponse.SetAssignedUserForLogon && !string.IsNullOrEmpty(enrolResponse.DomainName))
|
if (enrolResponse.SetAssignedUserForLogon && !string.IsNullOrEmpty(enrolResponse.DomainName))
|
||||||
|
|||||||
@@ -188,9 +188,11 @@ namespace Disco.Services.Devices.Enrolment
|
|||||||
{
|
{
|
||||||
if (!authenticatedToken.Has(Claims.ComputerAccount))
|
if (!authenticatedToken.Has(Claims.ComputerAccount))
|
||||||
throw new EnrolmentSafeException($"Connection not correctly authenticated (SN: {Request.SerialNumber}; Auth User: {authenticatedToken.User.UserId})");
|
throw new EnrolmentSafeException($"Connection not correctly authenticated (SN: {Request.SerialNumber}; Auth User: {authenticatedToken.User.UserId})");
|
||||||
|
else if (!string.Equals($"{Request.ComputerName}$", authenticatedToken.User.UserId, StringComparison.OrdinalIgnoreCase))
|
||||||
|
throw new InvalidOperationException($"Connection not correctly authenticated (SN: {Request.SerialNumber}; Computer Name: {Request.ComputerName}; Auth User: {authenticatedToken.User.UserId})");
|
||||||
|
|
||||||
if (domain == null)
|
if (domain == null && !ActiveDirectory.Context.TryGetDomainByName(Request.DNSDomainName, out domain))
|
||||||
domain = ActiveDirectory.Context.GetDomainByName(Request.DNSDomainName);
|
throw new EnrolmentSafeException($"The specified domain name '{Request.DNSDomainName}' is not recognized or reachable.");
|
||||||
|
|
||||||
if (!authenticatedToken.User.UserId.Equals($@"{domain.NetBiosName}\{Request.ComputerName}$", StringComparison.OrdinalIgnoreCase))
|
if (!authenticatedToken.User.UserId.Equals($@"{domain.NetBiosName}\{Request.ComputerName}$", StringComparison.OrdinalIgnoreCase))
|
||||||
throw new EnrolmentSafeException($"Connection not correctly authenticated (SN: {Request.SerialNumber}; Auth User: {authenticatedToken.User.UserId})");
|
throw new EnrolmentSafeException($"Connection not correctly authenticated (SN: {Request.SerialNumber}; Auth User: {authenticatedToken.User.UserId})");
|
||||||
@@ -392,9 +394,7 @@ namespace Disco.Services.Devices.Enrolment
|
|||||||
EnrolmentLog.LogSessionTaskProvisioningADAccount(sessionId, device.SerialNumber, device.DeviceDomainId);
|
EnrolmentLog.LogSessionTaskProvisioningADAccount(sessionId, device.SerialNumber, device.DeviceDomainId);
|
||||||
adMachineAccount = domainController.Value.RetrieveADMachineAccount(device.DeviceDomainId);
|
adMachineAccount = domainController.Value.RetrieveADMachineAccount(device.DeviceDomainId);
|
||||||
|
|
||||||
response.OfflineDomainJoinManifest = domainController.Value.OfflineDomainJoinProvision(device.DeviceDomainId, device.DeviceProfile.OrganisationalUnit, ref adMachineAccount, out var offlineProvisionDiagnosicInfo);
|
response.OfflineDomainJoinManifest = domainController.Value.OfflineDomainJoinProvision(device.DeviceDomainId, device.DeviceProfile.OrganisationalUnit, ref adMachineAccount);
|
||||||
|
|
||||||
EnrolmentLog.LogSessionDiagnosticInformation(sessionId, offlineProvisionDiagnosicInfo);
|
|
||||||
|
|
||||||
response.RequireReboot = true;
|
response.RequireReboot = true;
|
||||||
}
|
}
|
||||||
@@ -441,10 +441,7 @@ namespace Disco.Services.Devices.Enrolment
|
|||||||
response.ComputerName = calculatedAccountUsername;
|
response.ComputerName = calculatedAccountUsername;
|
||||||
|
|
||||||
// Create New Account
|
// Create New Account
|
||||||
|
response.OfflineDomainJoinManifest = domainController.Value.OfflineDomainJoinProvision(device.DeviceDomainId, device.DeviceProfile.OrganisationalUnit, ref adMachineAccount);
|
||||||
response.OfflineDomainJoinManifest = domainController.Value.OfflineDomainJoinProvision(device.DeviceDomainId, device.DeviceProfile.OrganisationalUnit, ref adMachineAccount, out var offlineProvisionDiagnosicInfo);
|
|
||||||
|
|
||||||
EnrolmentLog.LogSessionDiagnosticInformation(sessionId, offlineProvisionDiagnosicInfo);
|
|
||||||
|
|
||||||
response.RequireReboot = true;
|
response.RequireReboot = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -464,6 +464,7 @@
|
|||||||
<Compile Include="Interop\ActiveDirectory\ActiveDirectoryGroupCache.cs" />
|
<Compile Include="Interop\ActiveDirectory\ActiveDirectoryGroupCache.cs" />
|
||||||
<Compile Include="Interop\ActiveDirectory\ActiveDirectoryManagedGroups.cs" />
|
<Compile Include="Interop\ActiveDirectory\ActiveDirectoryManagedGroups.cs" />
|
||||||
<Compile Include="Interop\ActiveDirectory\ADDeviceDescriptionUpdateTask.cs" />
|
<Compile Include="Interop\ActiveDirectory\ADDeviceDescriptionUpdateTask.cs" />
|
||||||
|
<Compile Include="Interop\ActiveDirectory\ADDeviceOfflineDomainJoining.cs" />
|
||||||
<Compile Include="Interop\ActiveDirectory\ADDirectoryEntry.cs" />
|
<Compile Include="Interop\ActiveDirectory\ADDirectoryEntry.cs" />
|
||||||
<Compile Include="Interop\ActiveDirectory\ADDiscoverServers.cs" />
|
<Compile Include="Interop\ActiveDirectory\ADDiscoverServers.cs" />
|
||||||
<Compile Include="Interop\ActiveDirectory\ADDomain.cs" />
|
<Compile Include="Interop\ActiveDirectory\ADDomain.cs" />
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.DirectoryServices;
|
using System.DirectoryServices;
|
||||||
using System.DirectoryServices.ActiveDirectory;
|
using System.DirectoryServices.ActiveDirectory;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Disco.Services.Interop.ActiveDirectory
|
namespace Disco.Services.Interop.ActiveDirectory
|
||||||
{
|
{
|
||||||
@@ -353,93 +351,6 @@ namespace Disco.Services.Interop.ActiveDirectory
|
|||||||
return (pr.Status == IPStatus.Success);
|
return (pr.Status == IPStatus.Success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string OfflineDomainJoinProvision(string ComputerSamAccountName, string OrganisationalUnit, ref ADMachineAccount MachineAccount, out string DiagnosticInformation)
|
|
||||||
{
|
|
||||||
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 (!IsWritable)
|
|
||||||
throw new InvalidOperationException($"The domain controller [{Name}] is not writable. This action (Offline Domain Join Provision) requires a writable domain controller.");
|
|
||||||
|
|
||||||
StringBuilder diagnosticInfo = new StringBuilder();
|
|
||||||
string DJoinResult = null;
|
|
||||||
|
|
||||||
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 = 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)
|
|
||||||
MachineAccount.DeleteAccount(this);
|
|
||||||
|
|
||||||
string tempFileName = System.IO.Path.GetTempFileName();
|
|
||||||
string argumentOU = (!string.IsNullOrWhiteSpace(OrganisationalUnit)) ? $" /MACHINEOU \"{OrganisationalUnit}\"" : string.Empty;
|
|
||||||
string arguments = $"/PROVISION /DOMAIN \"{Domain.Name}\" /DCNAME \"{Name}\" /MACHINE \"{ComputerSamAccountName}\"{argumentOU} /REUSE /SAVEFILE \"{tempFileName}\"";
|
|
||||||
ProcessStartInfo commandStarter = new ProcessStartInfo("DJOIN.EXE", arguments)
|
|
||||||
{
|
|
||||||
CreateNoWindow = true,
|
|
||||||
ErrorDialog = false,
|
|
||||||
LoadUserProfile = false,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
UseShellExecute = false
|
|
||||||
};
|
|
||||||
diagnosticInfo.AppendFormat($"DJOIN.EXE {arguments}");
|
|
||||||
diagnosticInfo.AppendLine();
|
|
||||||
|
|
||||||
string stdOutput;
|
|
||||||
string stdError;
|
|
||||||
using (Process commandProc = Process.Start(commandStarter))
|
|
||||||
{
|
|
||||||
commandProc.WaitForExit(20000);
|
|
||||||
stdOutput = commandProc.StandardOutput.ReadToEnd();
|
|
||||||
stdError = commandProc.StandardError.ReadToEnd();
|
|
||||||
}
|
|
||||||
if (!string.IsNullOrWhiteSpace(stdOutput))
|
|
||||||
diagnosticInfo.AppendLine(stdOutput);
|
|
||||||
if (!string.IsNullOrWhiteSpace(stdError))
|
|
||||||
diagnosticInfo.AppendLine(stdError);
|
|
||||||
|
|
||||||
if (System.IO.File.Exists(tempFileName))
|
|
||||||
{
|
|
||||||
DJoinResult = Convert.ToBase64String(System.IO.File.ReadAllBytes(tempFileName));
|
|
||||||
System.IO.File.Delete(tempFileName);
|
|
||||||
}
|
|
||||||
if (string.IsNullOrWhiteSpace(DJoinResult))
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$@"Domain Join Unsuccessful
|
|
||||||
Error: {stdError}
|
|
||||||
Output: {stdOutput}");
|
|
||||||
|
|
||||||
DiagnosticInformation = diagnosticInfo.ToString();
|
|
||||||
|
|
||||||
// Reload Machine Account
|
|
||||||
MachineAccount = RetrieveADMachineAccount($@"{Domain.NetBiosName}\{ComputerSamAccountName}", (MachineAccount == null ? null : MachineAccount.LoadedProperties.Keys.ToArray()));
|
|
||||||
|
|
||||||
return DJoinResult;
|
|
||||||
}
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|||||||
@@ -204,6 +204,29 @@ namespace Disco.Services.Interop.ActiveDirectory
|
|||||||
|
|
||||||
#region Actions
|
#region Actions
|
||||||
|
|
||||||
|
public void RenameAccount(ADDomainController writableDomainController, string newName)
|
||||||
|
{
|
||||||
|
if (IsCriticalSystemObject)
|
||||||
|
throw new InvalidOperationException($"This account [{DistinguishedName}] is a Critical System Active Directory Object and Disco ICT refuses to modify it");
|
||||||
|
|
||||||
|
if (!writableDomainController.IsWritable)
|
||||||
|
throw new InvalidOperationException($"The domain controller [{Name}] is not writable. This action (Delete Account) requires a writable domain controller.");
|
||||||
|
|
||||||
|
using (ADDirectoryEntry adEntry = writableDomainController.RetrieveDirectoryEntry(DistinguishedName))
|
||||||
|
{
|
||||||
|
var entry = adEntry.Entry;
|
||||||
|
entry.Properties["dNSHostName"][0] = $"{newName}.{Domain.Name}";
|
||||||
|
entry.Properties["sAMAccountName"][0] = $"{newName}$";
|
||||||
|
entry.CommitChanges();
|
||||||
|
entry.Rename($"CN={newName}");
|
||||||
|
entry.CommitChanges();
|
||||||
|
|
||||||
|
// Update Distinguished Name
|
||||||
|
Name = newName;
|
||||||
|
DistinguishedName = entry.Properties["distinguishedName"][0].ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void DeleteAccount(ADDomainController WritableDomainController)
|
public void DeleteAccount(ADDomainController WritableDomainController)
|
||||||
{
|
{
|
||||||
if (IsCriticalSystemObject)
|
if (IsCriticalSystemObject)
|
||||||
|
|||||||
@@ -179,18 +179,6 @@ namespace Disco.Services.Interop.ActiveDirectory
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Actions
|
|
||||||
|
|
||||||
public static string OfflineDomainJoinProvision(string ComputerSamAccountName, string OrganisationalUnit, ref ADMachineAccount MachineAccount, out string DiagnosticInformation)
|
|
||||||
{
|
|
||||||
var domain = Context.GetDomainFromDistinguishedName(OrganisationalUnit);
|
|
||||||
var writableDomainController = domain.GetAvailableDomainController(RequireWritable: true);
|
|
||||||
|
|
||||||
return writableDomainController.OfflineDomainJoinProvision(ComputerSamAccountName, OrganisationalUnit, ref MachineAccount, out DiagnosticInformation);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Helpers
|
#region Helpers
|
||||||
|
|
||||||
public static string ParseDomainAccountId(string AccountId)
|
public static string ParseDomainAccountId(string AccountId)
|
||||||
|
|||||||
Reference in New Issue
Block a user