Update #42: AD Migration

Refactor to target specific Domain Controllers, with failover.
This commit is contained in:
Gary Sharp
2014-04-21 21:43:13 +10:00
parent 43fc622121
commit 09c2a24222
98 changed files with 3808 additions and 3271 deletions
@@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Disco.Services.Interop.ActiveDirectory;
namespace Disco.Services.Authorization
{
@@ -18,12 +19,12 @@ namespace Disco.Services.Authorization
#region Token Builders
public static AuthorizationToken BuildToken(User User, List<string> GroupMembership)
public static AuthorizationToken BuildToken(User User, IEnumerable<string> GroupMembership)
{
return new AuthorizationToken()
{
User = User,
GroupMembership = GroupMembership,
GroupMembership = GroupMembership.ToList(),
RoleTokens = RoleCache.GetRoleTokens(GroupMembership, User)
};
}
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Disco.Services.Interop.ActiveDirectory;
namespace Disco.Services.Authorization.Roles
{
@@ -49,19 +50,19 @@ namespace Disco.Services.Authorization.Roles
private static IEnumerable<string> GenerateAdministratorSubjectIds(DiscoDataContext Database)
{
var domainNetBiosName = Interop.ActiveDirectory.ActiveDirectory.PrimaryDomain.NetBiosName;
var domainNetBiosName = ActiveDirectory.Context.PrimaryDomain.NetBiosName;
var configuredSubjectIds = Database.DiscoConfiguration.Administrators.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Contains(@"\") ? s : string.Format(@"{0}\{1}", domainNetBiosName, s));
return RequiredAdministratorSubjectIds
.Concat(configuredSubjectIds)
.Distinct(StringComparer.InvariantCultureIgnoreCase)
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(s => s);
}
public static IEnumerable<string> RequiredAdministratorSubjectIds
{
get
{
var domainNetBiosName = Interop.ActiveDirectory.ActiveDirectory.PrimaryDomain.NetBiosName;
var domainNetBiosName = ActiveDirectory.Context.PrimaryDomain.NetBiosName;
return _RequiredAdministratorSubjectIds.Select(s => string.Format(@"{0}\{1}", domainNetBiosName, s));
}
}
@@ -79,7 +80,7 @@ namespace Disco.Services.Authorization.Roles
SubjectIds = SubjectIds
.Where(s => !string.IsNullOrWhiteSpace(s))
.Concat(RequiredAdministratorSubjectIds)
.Distinct(StringComparer.InvariantCultureIgnoreCase)
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(s => s);
var subjectIdsString = string.Join(",", SubjectIds);
@@ -136,17 +137,15 @@ namespace Disco.Services.Authorization.Roles
}
internal static RoleToken GetRoleToken(string SecurityGroup)
{
return _Cache.FirstOrDefault(t => t.SubjectIdHashes.Contains(SecurityGroup.ToLower()));
return _Cache.FirstOrDefault(t => t.SubjectIdHashes.Contains(SecurityGroup));
}
internal static List<IRoleToken> GetRoleTokens(IEnumerable<string> SecurityGroup)
{
var securityGroups = SecurityGroup.Select(sg => sg.ToLower());
return _Cache.Where(t => securityGroups.Any(sg => t.SubjectIdHashes.Contains(sg))).Cast<IRoleToken>().ToList();
return _Cache.Where(t => SecurityGroup.Any(sg => t.SubjectIdHashes.Contains(sg))).Cast<IRoleToken>().ToList();
}
internal static List<IRoleToken> GetRoleTokens(IEnumerable<string> SecurityGroup, User User)
{
var subjectIds = (new string[] { User.UserId }).Concat(SecurityGroup).Select(sg => sg.ToLower());
var subjectIds = SecurityGroup.Concat(new string[] { User.UserId });
return _Cache.Where(t => subjectIds.Any(sg => t.SubjectIdHashes.Contains(sg))).Cast<IRoleToken>().ToList();
}
@@ -30,7 +30,7 @@ namespace Disco.Services.Authorization.Roles
return new RoleToken()
{
Role = Role,
SubjectIdHashes = new HashSet<string>(sg.Select(i => i.ToLower())),
SubjectIdHashes = new HashSet<string>(sg.Select(i => i.ToLower()), StringComparer.OrdinalIgnoreCase),
SubjectIds = sg.ToList(),
Claims = Claims
};
+22 -5
View File
@@ -174,12 +174,29 @@
<Compile Include="Authorization\Roles\RoleToken.cs" />
<Compile Include="Extensions\DateTimeExtensions.cs" />
<Compile Include="Extensions\StringExtensions.cs" />
<None Include="Interop\_ActiveDirectory\ActiveDirectory.cs" />
<None Include="Interop\_ActiveDirectory\ActiveDirectoryExtensions.cs" />
<None Include="Interop\_ActiveDirectory\Internal\ADDiscoverForestServers.cs" />
<None Include="Interop\_ActiveDirectory\Internal\ADGroupCache.cs" />
<None Include="Interop\_ActiveDirectory\Internal\ADInterop.cs" />
<None Include="Interop\_ActiveDirectory\Internal\ADUpdateLastNetworkLogonDateJob.cs" />
<Compile Include="Interop\ActiveDirectory\ActiveDirectory.cs" />
<Compile Include="Interop\ActiveDirectory\ActiveDirectoryContext.cs" />
<Compile Include="Interop\ActiveDirectory\ActiveDirectoryExtensions.cs" />
<Compile Include="Interop\ActiveDirectory\Internal\ADDiscoverForestServers.cs" />
<Compile Include="Interop\ActiveDirectory\Internal\ADGroupCache.cs" />
<Compile Include="Interop\ActiveDirectory\Internal\ADInterop.cs" />
<Compile Include="Interop\ActiveDirectory\Internal\ADUpdateLastNetworkLogonDateJob.cs" />
<Compile Include="Interop\ActiveDirectory\ActiveDirectoryGroupCache.cs" />
<Compile Include="Interop\ActiveDirectory\ADDirectoryEntry.cs" />
<Compile Include="Interop\ActiveDirectory\ADDiscoverForestServers.cs" />
<Compile Include="Interop\ActiveDirectory\ADDomain.cs" />
<Compile Include="Interop\ActiveDirectory\ADDomainController.cs" />
<Compile Include="Interop\ActiveDirectory\ADGroup.cs" />
<Compile Include="Interop\ActiveDirectory\ADHelpers.cs" />
<Compile Include="Interop\ActiveDirectory\ADMachineAccount.cs" />
<Compile Include="Interop\ActiveDirectory\ADOrganisationalUnit.cs" />
<Compile Include="Interop\ActiveDirectory\ADSearchResult.cs" />
<Compile Include="Interop\ActiveDirectory\ADSite.cs" />
<Compile Include="Interop\ActiveDirectory\ADTaskUpdateNetworkLogonDates.cs" />
<Compile Include="Interop\ActiveDirectory\ADUserAccount.cs" />
<Compile Include="Interop\ActiveDirectory\IADObject.cs" />
<Compile Include="Jobs\JobExtensions.cs" />
<Compile Include="Jobs\JobLists\JobTableExtensions.cs" />
<Compile Include="Jobs\JobQueues\Cache.cs" />
@@ -286,7 +303,7 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<ProjectExtensions>
<VisualStudio>
<UserProperties BuildVersion_UseGlobalSettings="False" BuildVersion_DetectChanges="False" BuildVersion_BuildAction="Both" BuildVersion_StartDate="2011/7/1" BuildVersion_BuildVersioningStyle="None.DeltaBaseYear.MonthAndDayStamp.TimeStamp" BuildVersion_UpdateAssemblyVersion="True" BuildVersion_UpdateFileVersion="True" />
<UserProperties BuildVersion_UpdateFileVersion="True" BuildVersion_UpdateAssemblyVersion="True" BuildVersion_BuildVersioningStyle="None.DeltaBaseYear.MonthAndDayStamp.TimeStamp" BuildVersion_StartDate="2011/7/1" BuildVersion_BuildAction="Both" BuildVersion_DetectChanges="False" BuildVersion_UseGlobalSettings="False" />
</VisualStudio>
</ProjectExtensions>
<PropertyGroup>
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Disco.Services.Interop.ActiveDirectory
{
public class ADDirectoryEntry : IDisposable
{
public ADDomain Domain { get; private set; }
public ADDomainController DomainController { get; private set; }
public DirectoryEntry Entry { get; private set; }
internal ADDirectoryEntry(ADDomain Domain, ADDomainController DomainController, DirectoryEntry Entry)
{
if (Domain == null)
throw new ArgumentNullException("Domain");
if (DomainController == null)
throw new ArgumentNullException("DomainController");
if (Entry == null)
throw new ArgumentNullException("Entry");
this.Domain = Domain;
this.DomainController = DomainController;
this.Entry = Entry;
}
public void Dispose()
{
if (Entry != null)
{
Entry.Dispose();
Entry = null;
}
}
public override string ToString()
{
if (this.Entry != null)
return this.Entry.Path;
else
return base.ToString();
}
}
}
@@ -7,23 +7,25 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Disco.Services.Interop.ActiveDirectory.Internal
namespace Disco.Services.Interop.ActiveDirectory
{
public class ADDiscoverForestServers : ScheduledTask
{
public override string TaskName { get { return "Active Directory - Discover Forest Servers"; } }
public override bool SingleInstanceTask { get { return true; } }
public override bool CancelInitiallySupported { get { return false; } }
internal static List<string> ForestServers { get; set; }
private static object _scheduleLock = new object();
protected override void ExecuteTask()
{
var forestServers = DiscoverForestServers();
ADInterop._ForestServers = forestServers;
ADDiscoverForestServers.ForestServers = forestServers;
// Restrict Searching Entire Forest if to many servers
using (DiscoDataContext Database = new DiscoDataContext())
{
var searchEntireForest = Database.DiscoConfiguration.ActiveDirectory.SearchEntireForest;
var searchEntireForest = Database.DiscoConfiguration.ActiveDirectory.SearchAllForestServers;
// Check explicitly configured: No
if (!searchEntireForest.HasValue || searchEntireForest.Value)
@@ -32,12 +34,12 @@ namespace Disco.Services.Interop.ActiveDirectory.Internal
if (forestServers.Count > ActiveDirectory.MaxForestServerSearch)
{
// Update Database
Database.DiscoConfiguration.ActiveDirectory.SearchEntireForest = false;
Database.DiscoConfiguration.ActiveDirectory.SearchAllForestServers = false;
}
else
{
// Default
Database.DiscoConfiguration.ActiveDirectory.SearchEntireForest = true;
Database.DiscoConfiguration.ActiveDirectory.SearchAllForestServers = true;
}
Database.SaveChanges();
@@ -52,12 +54,59 @@ namespace Disco.Services.Interop.ActiveDirectory.Internal
return taskStatus;
else
{
var t = new ADDiscoverForestServers();
return t.ScheduleTask();
lock (_scheduleLock)
{
taskStatus = ScheduledTasks.GetTaskStatuses(typeof(ADDiscoverForestServers)).Where(ts => ts.IsRunning).FirstOrDefault();
if (taskStatus != null)
return taskStatus;
else
{
var t = new ADDiscoverForestServers();
return t.ScheduleTask();
}
}
}
}
internal static List<string> DiscoverForestServers()
public static List<string> LoadForestServersBlocking()
{
if (ADDiscoverForestServers.ForestServers != null)
return ADDiscoverForestServers.ForestServers;
ScheduledTaskStatus status;
lock (_scheduleLock)
{
if (ADDiscoverForestServers.ForestServers != null)
return ADDiscoverForestServers.ForestServers;
status = ADDiscoverForestServers.ScheduleNow();
}
status.CompletionTask.Wait();
return ForestServers;
}
public static Task<List<string>> LoadForestServersAsync()
{
if (ADDiscoverForestServers.ForestServers != null)
return Task.FromResult(ADDiscoverForestServers.ForestServers);
ScheduledTaskStatus status;
lock (_scheduleLock)
{
if (ADDiscoverForestServers.ForestServers != null)
return Task.FromResult(ADDiscoverForestServers.ForestServers);
status = ADDiscoverForestServers.ScheduleNow();
}
return status.CompletionTask.ContinueWith(t =>
{
return ADDiscoverForestServers.ForestServers;
});
}
private static List<string> DiscoverForestServers()
{
using (var computerDomain = Domain.GetComputerDomain())
{
@@ -0,0 +1,383 @@
using Disco.Services.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Disco.Services.Interop.ActiveDirectory
{
public class ADDomain
{
private const int DomainMaintanceIntervalMinutes = 15;
private const int SearchExceptionRetryMax = 4;
private ActiveDirectoryContext context;
private ConcurrentStack<ADDomainController> domainControllers;
private int domainControllerRoundRobin = -1;
private object domainMaintainLock = new object();
private DateTime domainMaintenanceNext;
public Domain Domain { get; private set; }
public IEnumerable<ADDomainController> DomainControllers
{
get
{
return domainControllers.ToArray();
}
}
public string Name { get; private set; }
public string NetBiosName { get; private set; }
public string DistinguishedName { get; private set; }
public string ConfigurationNamingContext { get; private set; }
public SecurityIdentifier SecurityIdentifier { get; private set; }
public List<string> SearchContainers { get; private set; }
public ADDomain(ActiveDirectoryContext Context, Domain Domain)
{
this.context = Context;
this.Domain = Domain;
this.SearchContainers = null;
this.domainControllers = null;
this.domainMaintenanceNext = DateTime.Now.AddMinutes(DomainMaintanceIntervalMinutes);
this.Initialize();
}
private void Initialize()
{
this.Name = Domain.Name;
var dc = Domain.FindDomainController();
string ldapPath = string.Format("LDAP://{0}/", dc.Name);
using (var adRootDSE = new DirectoryEntry(ldapPath + "RootDSE"))
{
this.DistinguishedName = adRootDSE.Properties["defaultNamingContext"][0].ToString();
this.ConfigurationNamingContext = adRootDSE.Properties["configurationNamingContext"][0].ToString();
}
using (var adDomainRoot = new DirectoryEntry(ldapPath + this.DistinguishedName))
{
this.SecurityIdentifier = new SecurityIdentifier((byte[])(adDomainRoot.Properties["objectSid"][0]), 0);
}
using (var configSearchRoot = new DirectoryEntry(ldapPath + "CN=Partitions," + this.ConfigurationNamingContext))
{
var configSearchFilter = string.Format("(&(objectcategory=Crossref)(dnsRoot={0})(netBIOSName=*))", this.Name);
using (var configSearcher = new DirectorySearcher(configSearchRoot, configSearchFilter, new string[] { "NetBIOSName" }, System.DirectoryServices.SearchScope.OneLevel))
{
SearchResult configResult = configSearcher.FindOne();
if (configResult != null)
this.NetBiosName = configResult.Properties["NetBIOSName"][0].ToString();
else
this.NetBiosName = null;
}
}
}
#region Domain Controllers
public IEnumerable<ADDomainController> GetAllReachableDomainControllers()
{
return this.Domain.FindAllDomainControllers().WhereReachable().Select(dc => new ADDomainController(this.context, dc, this, dc.SiteName == this.context.Site.Name, false));
}
public IEnumerable<ADDomainController> GetReachableSiteDomainControllers()
{
return this.DomainControllers.Where(dc => dc.IsSiteServer && dc.DomainController.IsReachable());
}
public ADDomainController GetAvailableDomainController(bool RequireWritable = false)
{
if (this.domainMaintenanceNext < DateTime.Now)
MaintainDomainControllers();
IEnumerable<ADDomainController> availableServers;
// Try Site Servers first
availableServers = AvilableDomainControllers(RequireSiteServer: true, RequireWritable: RequireWritable);
if (!availableServers.Any())
{
// No Site Servers available - try all
availableServers = AvilableDomainControllers(RequireSiteServer: false, RequireWritable: RequireWritable);
if (!availableServers.Any())
{
lock (domainMaintainLock)
{
availableServers = AvilableDomainControllers(RequireSiteServer: false, RequireWritable: RequireWritable);
if (!availableServers.Any())
return DiscoverAvailableDomainController(RequireWritable);
}
}
}
switch (availableServers.Count())
{
case 1:
// 1 Available DC
return availableServers.First();
default:
// Multiple DCs Available - Round Robin
int drr = Interlocked.Increment(ref domainControllerRoundRobin);
int dcrrValue = drr % availableServers.Count();
if (drr > availableServers.Count())
domainControllerRoundRobin = -1;
return availableServers.ElementAt(dcrrValue);
}
}
private IEnumerable<ADDomainController> AvilableDomainControllers(bool RequireSiteServer, bool RequireWritable)
{
IEnumerable<ADDomainController> query = this.DomainControllers.Where(dc => dc.IsAvailable);
if (RequireSiteServer)
query = query.Where(dc => dc.IsSiteServer);
if (RequireWritable)
query = query.Where(dc => dc.IsWritable);
return query;
}
private ADDomainController DiscoverAvailableDomainController(bool RequireWritable)
{
LocatorOptions locatorOptions;
if (RequireWritable)
locatorOptions = LocatorOptions.ForceRediscovery | LocatorOptions.WriteableRequired;
else
locatorOptions = LocatorOptions.ForceRediscovery;
var dc = this.Domain.FindDomainController(locatorOptions);
var dcName = dc.Name;
var existingDC = this.DomainControllers.FirstOrDefault(edc => edc.Name == dcName);
if (existingDC != null)
{
// DC already in scope
// Native API indicates writable
if (RequireWritable)
existingDC.IsWritable = true;
// Native API indicates it is available
existingDC.IsAvailable = true;
return existingDC;
}
else
{
// New DC discovered
var adDC = new ADDomainController(this.context, dc, this, dc.SiteName == this.context.Site.Name, RequireWritable);
// Add DC to Available Servers
this.domainControllers.Push(adDC);
return adDC;
}
}
private void MaintainDomainControllers()
{
lock (domainMaintainLock)
{
var servers = this.domainControllers.ToList();
var nonSiteServersPresent = servers.Any(s => !s.IsSiteServer);
if (nonSiteServersPresent)
{
var siteServersAvailable = servers.Any(s => s.IsSiteServer && s.IsAvailable);
var nonSiteServersUnavailable = servers.Any(s => !s.IsSiteServer && !s.IsAvailable);
if (siteServersAvailable)
{
// Remove non-site servers
UpdateDomainControllers(servers.Where(s => s.IsSiteServer));
}
else if (nonSiteServersUnavailable)
{
// Remove unavailable non-site servers
UpdateDomainControllers(servers.Where(s => s.IsSiteServer || s.IsAvailable));
}
}
this.domainMaintenanceNext = DateTime.Now.AddMinutes(DomainMaintanceIntervalMinutes);
}
}
internal void UpdateDomainControllers(IEnumerable<ADDomainController> DomainControllers)
{
this.domainControllers = new ConcurrentStack<ADDomainController>(DomainControllers);
}
#endregion
public ADDirectoryEntry RetrieveDirectoryEntry(string DistinguishedName, string[] LoadProperties = null)
{
if (string.IsNullOrWhiteSpace(DistinguishedName))
throw new ArgumentNullException("DistinguishedName");
if (!DistinguishedName.EndsWith(this.DistinguishedName, StringComparison.OrdinalIgnoreCase))
throw new ArgumentException(string.Format("The Distinguished Name ({0}) isn't a member of this domain [{1}]", DistinguishedName, this.Name), "DistinguishedName");
var dc = GetAvailableDomainController();
return dc.RetrieveDirectoryEntry(DistinguishedName, LoadProperties);
}
#region Searching
public IEnumerable<ADSearchResult> SearchEntireDomain(string LdapFilter, string[] LoadProperties, int? ResultLimit = null)
{
return SearchInternal(this.DistinguishedName, LdapFilter, LoadProperties, ResultLimit);
}
public IEnumerable<ADSearchResult> SearchScope(string LdapFilter, string[] LoadProperties, int? ResultLimit = null)
{
var searchScope = this.SearchContainers;
// No scope set, search entire domain
if (searchScope == null)
return SearchEntireDomain(LdapFilter, LoadProperties, ResultLimit);
switch (searchScope.Count)
{
case 0: // Ignore domain
return Enumerable.Empty<ADSearchResult>();
case 1: // Single-search
return SearchInternal(searchScope[0], LdapFilter, LoadProperties, ResultLimit);
default: // Multi-search - Parallelize
var queryTasks = searchScope.Select(scope =>
Task<IEnumerable<ADSearchResult>>.Factory.StartNew(() =>
SearchInternal(scope, LdapFilter, LoadProperties, ResultLimit))).ToArray();
// Block
Task.WaitAll(queryTasks);
var results = queryTasks.SelectMany(t => t.Result);
if (ResultLimit.HasValue)
results = results.Take(ResultLimit.Value);
return results;
}
}
internal IEnumerable<ADSearchResult> SearchInternal(string SearchRoot, string LdapFilter, string[] LoadProperties, int? ResultLimit)
{
if (string.IsNullOrEmpty(SearchRoot))
throw new ArgumentNullException("SearchRoot");
if (string.IsNullOrEmpty(LdapFilter))
throw new ArgumentNullException("LdapFilter");
if (ResultLimit.HasValue && ResultLimit.Value < 1)
throw new ArgumentOutOfRangeException("ResultLimit", "The ResultLimit must be 1 or greater");
// Search with recovery
var exceptionCount = 0;
Queue<Exception> exceptions = null;
do
{
var domainController = GetAvailableDomainController();
try
{
return domainController.SearchInternal(SearchRoot, LdapFilter, LoadProperties, ResultLimit);
}
catch (Exception ex)
{
if (exceptions == null)
exceptions = new Queue<Exception>(SearchExceptionRetryMax);
exceptions.Enqueue(ex);
exceptionCount++;
// Set offline for DomainControllerUnavailableMinutes
domainController.IsAvailable = false;
SystemLog.LogWarning(string.Format("A domain controller [{0}] is offline. It will be retried after {1}. Error: {2} [{3}]", domainController.Name, domainController.AvailableWhen.Value.ToShortTimeString(), ex.Message, ex.GetType().Name));
}
} while (exceptionCount < SearchExceptionRetryMax);
throw new AggregateException(
new Exception[] { new Exception(string.Format("Unable to perform Active Directory Search after {0} attempts", exceptionCount)) }
.Concat(exceptions));
}
#endregion
internal void UpdateSearchContainers(List<string> Containers)
{
this.SearchContainers = Containers ?? new List<string>();
}
internal void UpdateSearchEntireDomain()
{
this.SearchContainers = null;
}
#region Helpers
public string DefaultComputerContainer
{
get
{
return string.Format("CN=Computers,{0}", this.DistinguishedName);
}
}
public string FriendlyDistinguishedNamePath(string DistinguishedName)
{
if (!DistinguishedName.EndsWith(this.DistinguishedName, StringComparison.OrdinalIgnoreCase))
throw new ArgumentException(string.Format("The Distinguished Name [{0}] doesn't exist within this domain [{1}]", DistinguishedName, this.DistinguishedName));
StringBuilder name = new StringBuilder();
name.Append('[').Append(this.NetBiosName).Append(']');
var subDN = DistinguishedName.Substring(0, DistinguishedName.Length - this.DistinguishedName.Length);
var subDNComponents = subDN.Split(',');
subDNComponents
.Where(c => !string.IsNullOrWhiteSpace(c))
.Reverse()
.Select(c => c.Substring(c.IndexOf('=') + 1))
.ToList()
.ForEach(c => name.Append(" > ").Append(c));
return name.ToString();
}
#endregion
public override string ToString()
{
return string.Format("{0} [{1}]", this.Name, this.NetBiosName);
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is ADDomain))
return false;
else
return this.DistinguishedName == ((ADDomain)obj).DistinguishedName;
}
public override int GetHashCode()
{
return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this.DistinguishedName);
}
}
}
@@ -0,0 +1,424 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
using System.Linq;
using System.Net.NetworkInformation;
using System.Security.Principal;
using System.Text;
namespace Disco.Services.Interop.ActiveDirectory
{
public class ADDomainController
{
private const string LdapPathTemplate = @"LDAP://{0}/{1}";
private ActiveDirectoryContext context;
public DomainController DomainController { get; private set; }
public ADDomain Domain { get; private set; }
public string Name { get; private set; }
public string SiteName { get; private set; }
public bool IsSiteServer { get; private set; }
public bool IsWritable { get; internal set; }
public DateTime? AvailableWhen { get; private set; }
public bool IsAvailable
{
get
{
var aw = AvailableWhen;
if (aw.HasValue && aw.Value < DateTime.Now)
AvailableWhen = null;
return !aw.HasValue;
}
internal set
{
if (value)
AvailableWhen = null;
else
AvailableWhen = DateTime.Now.AddMinutes(ActiveDirectory.DomainControllerUnavailableMinutes);
}
}
public ADDomainController(ActiveDirectoryContext Context, DomainController DomainController, ADDomain Domain, bool IsSiteServer, bool IsWritable)
{
this.context = Context;
this.Domain = Domain;
this.DomainController = DomainController;
this.Name = DomainController.Name;
this.SiteName = DomainController.SiteName;
this.IsSiteServer = IsSiteServer;
this.IsWritable = IsWritable;
this.AvailableWhen = null;
}
public ADDirectoryEntry RetrieveDirectoryEntry(string DistinguishedName, string[] LoadProperties = null)
{
if (string.IsNullOrWhiteSpace(DistinguishedName))
throw new ArgumentNullException("DistinguishedName");
if (!DistinguishedName.EndsWith(this.Domain.DistinguishedName, StringComparison.OrdinalIgnoreCase))
throw new ArgumentException(string.Format("The Distinguished Name ({0}) isn't a member of this domain [{1}]", DistinguishedName, this.Domain.Name), "DistinguishedName");
var entry = new DirectoryEntry(string.Format(LdapPathTemplate, this.Name, DistinguishedName));
if (LoadProperties != null)
entry.RefreshCache(LoadProperties);
return new ADDirectoryEntry(this.Domain, this, entry);
}
#region Searching
public IEnumerable<ADSearchResult> SearchEntireDomain(string LdapFilter, string[] LoadProperties, int? ResultLimit = null)
{
return SearchInternal(Domain.DistinguishedName, LdapFilter, LoadProperties, ResultLimit);
}
public IEnumerable<ADSearchResult> SearchScope(string LdapFilter, string[] LoadProperties, int? ResultLimit = null)
{
var searchScope = this.Domain.SearchContainers;
// No scope set, search entire domain
if (searchScope == null)
return SearchEntireDomain(LdapFilter, LoadProperties, ResultLimit);
// Ignore domain
if (searchScope.Count == 0)
return Enumerable.Empty<ADSearchResult>();
// Multi-search
var results = searchScope.SelectMany(scope => SearchInternal(scope, LdapFilter, LoadProperties, ResultLimit));
if (ResultLimit.HasValue)
results = results.Take(ResultLimit.Value);
return results;
}
internal IEnumerable<ADSearchResult> SearchInternal(string SearchRoot, string LdapFilter, string[] LoadProperties, int? ResultLimit)
{
if (string.IsNullOrEmpty(SearchRoot))
throw new ArgumentNullException("SearchRoot");
if (string.IsNullOrEmpty(LdapFilter))
throw new ArgumentNullException("LdapFilter");
if (ResultLimit.HasValue && ResultLimit.Value < 1)
throw new ArgumentOutOfRangeException("ResultLimit", "The ResultLimit must be 1 or greater");
using (ADDirectoryEntry rootEntry = this.RetrieveDirectoryEntry(SearchRoot))
{
using (DirectorySearcher searcher = new DirectorySearcher(rootEntry.Entry, LdapFilter, LoadProperties, System.DirectoryServices.SearchScope.Subtree))
{
searcher.PageSize = 500;
if (ResultLimit.HasValue)
searcher.SizeLimit = ResultLimit.Value;
return searcher.FindAll().Cast<SearchResult>().Select(result => new ADSearchResult(Domain, this, SearchRoot, LdapFilter, result));
}
}
}
#endregion
#region AD Objects
#region User Accounts
public ADUserAccount RetrieveADUserAccount(string Id, string[] AdditionalProperties = null)
{
string[] loadProperites = (AdditionalProperties != null && AdditionalProperties.Length > 0)
? ADUserAccount.LoadProperties.Concat(AdditionalProperties).ToArray()
: ADUserAccount.LoadProperties;
var result = RetrieveBySamAccountName(Id, ADUserAccount.LdapSamAccountNameFilterTemplate, loadProperites);
if (result == null)
return null;
else
return result.AsADUserAccount(false, AdditionalProperties);
}
#endregion
#region Machine Accounts
public ADMachineAccount RetrieveADMachineAccount(string Id, System.Guid? UUIDNetbootGUID, System.Guid? MacAddressNetbootGUID, string[] AdditionalProperties = null)
{
if (string.IsNullOrWhiteSpace(Id))
throw new ArgumentNullException("Id");
// Add $ identifier for machine accounts
if (!Id.EndsWith("$"))
Id += "$";
string[] loadProperites = (AdditionalProperties != null && AdditionalProperties.Length > 0)
? ADMachineAccount.LoadProperties.Concat(AdditionalProperties).ToArray()
: ADMachineAccount.LoadProperties;
ADSearchResult adResult;
adResult = RetrieveBySamAccountName(Id, ADMachineAccount.LdapSamAccountNameFilterTemplate, loadProperites);
if (adResult == null && (UUIDNetbootGUID.HasValue || MacAddressNetbootGUID.HasValue))
{
string ldapFilter;
if (UUIDNetbootGUID.HasValue && MacAddressNetbootGUID.HasValue)
{
ldapFilter = string.Format(ADMachineAccount.LdapNetbootGuidDoubleFilterTemplate, UUIDNetbootGUID.Value.ToLdapQueryFormat(), MacAddressNetbootGUID.Value.ToLdapQueryFormat());
}
else if (UUIDNetbootGUID.HasValue)
{
ldapFilter = string.Format(ADMachineAccount.LdapNetbootGuidSingleFilterTemplate, UUIDNetbootGUID.Value.ToLdapQueryFormat());
}
else // MacAddressNetbootGUID.HasValue
{
ldapFilter = string.Format(ADMachineAccount.LdapNetbootGuidSingleFilterTemplate, MacAddressNetbootGUID.Value.ToLdapQueryFormat());
}
adResult = this.SearchEntireDomain(ldapFilter, loadProperites, ActiveDirectory.SingleSearchResult).FirstOrDefault();
}
if (adResult != null)
return adResult.AsADMachineAccount(AdditionalProperties);
else
return null; // Not Found
}
public ADMachineAccount RetrieveADMachineAccount(string Id, string[] AdditionalProperties = null)
{
return RetrieveADMachineAccount(Id, null, null, AdditionalProperties);
}
public ADMachineAccount RetrieveADMachineAccount(string Id, System.Guid? NetbootGUID, string[] AdditionalProperties = null)
{
return RetrieveADMachineAccount(Id, NetbootGUID, null, AdditionalProperties);
}
#endregion
#region Groups
public ADGroup RetrieveADGroup(string Id)
{
var result = RetrieveBySamAccountName(Id, ADGroup.LdapSamAccountNameFilterTemplate, ADGroup.LoadProperties);
if (result == null)
return null;
else
return result.AsADGroup();
}
public ADGroup RetrieveADGroupByDistinguishedName(string DistinguishedName)
{
using (var groupEntry = this.RetrieveDirectoryEntry(DistinguishedName, ADGroup.LoadProperties))
{
if (groupEntry == null)
return null;
return groupEntry.AsADGroup();
}
}
public ADGroup RetrieveADGroupWithSecurityIdentifier(SecurityIdentifier SecurityIdentifier)
{
if (SecurityIdentifier == null)
throw new ArgumentNullException("SecurityIdentifier");
if (!SecurityIdentifier.IsEqualDomainSid(this.Domain.SecurityIdentifier))
throw new ArgumentException(string.Format("The specified Security Identifier [{0}] does not belong to this domain [{1}]", SecurityIdentifier.ToString(), this.Domain.Name), "SecurityIdentifier");
var sidBinaryString = SecurityIdentifier.ToBinaryString();
string ldapFilter = string.Format(ADGroup.LdapSecurityIdentifierFilterTemplate, sidBinaryString);
var result = this.SearchEntireDomain(ldapFilter, ADGroup.LoadProperties, ActiveDirectory.SingleSearchResult).FirstOrDefault();
if (result == null)
return null;
else
return result.AsADGroup();
}
#endregion
#region Object
private const string ObjectLdapSamAccountNameFilter = "(&(|(objectCategory=Person)(objectCategory=Computer)(objectCategory=Group))(sAMAccountName={0}))";
private static readonly string[] ObjectLoadProperties = { "objectCategory" };
private static readonly string[] ObjectLoadPropertiesAll = ObjectLoadProperties.Concat(ADUserAccount.LoadProperties).Concat(ADMachineAccount.LoadProperties).Concat(ADGroup.LoadProperties).Distinct().ToArray();
public IADObject RetrieveADObject(string Id, bool Quick)
{
var result = RetrieveBySamAccountName(Id, ObjectLdapSamAccountNameFilter, ObjectLoadPropertiesAll);
if (result == null)
return null;
else
{
var objectCategory = result.Value<string>("objectCategory");
objectCategory = objectCategory.Substring(0, objectCategory.IndexOf(',')).ToLower();
switch (objectCategory)
{
case "cn=person":
return result.AsADUserAccount(Quick, null);
case "cn=computer":
return result.AsADMachineAccount(null);
case "cn=group":
return result.AsADGroup();
default:
throw new InvalidOperationException("Unexpected objectCategory");
}
}
}
#endregion
#region Organisational Units
private const string OrganisationalUnitsLdapFilter = "(objectCategory=organizationalUnit)";
private static readonly string[] OrganisationalUnitsLoadProperties = { "name", "distinguishedName" };
public List<ADOrganisationalUnit> RetrieveADOrganisationalUnitStructure()
{
Dictionary<string, List<ADOrganisationalUnit>> resultTree = new Dictionary<string, List<ADOrganisationalUnit>>();
var unsortedOrganisationalUnits = this.SearchEntireDomain(OrganisationalUnitsLdapFilter, OrganisationalUnitsLoadProperties)
.Select(r => r.AsADOrganisationalUnit()).ToList();
var indexedOrganisationalUnits = unsortedOrganisationalUnits.ToDictionary(k => k.DistinguishedName);
var indexedChildren = unsortedOrganisationalUnits
.GroupBy(ou => ou.DistinguishedName.Substring(ou.DistinguishedName.IndexOf(',') + 1))
.ToDictionary(g => g.Key, g => g.ToList());
// Link Children
foreach (var ouChildren in indexedChildren)
{
ADOrganisationalUnit ouParent;
if (indexedOrganisationalUnits.TryGetValue(ouChildren.Key, out ouParent))
{
ouParent.Children = ouChildren.Value;
}
}
return indexedChildren[Domain.DistinguishedName];
}
#endregion
private ADSearchResult RetrieveBySamAccountName(string Id, string LdapFilterTemplate, string[] LoadProperties)
{
var splitId = UserExtensions.SplitUserId(Id);
if (!this.Domain.NetBiosName.Equals(splitId.Item1, StringComparison.OrdinalIgnoreCase))
throw new ArgumentException(string.Format("The Id [{0}] is invalid for this domain [{1}]", Id, this.Domain.Name), "Id");
var ldapFilter = string.Format(LdapFilterTemplate, splitId.Item2);
return this.SearchEntireDomain(ldapFilter, LoadProperties, ActiveDirectory.SingleSearchResult).FirstOrDefault();
}
#endregion
#region Actions
public bool IsReachable()
{
using (Ping p = new Ping())
{
var pr = p.Send(this.Name, 1000);
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(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", MachineAccount.DistinguishedName));
if (!this.IsWritable)
throw new InvalidOperationException(string.Format("The domain controller [{0}] is not writable. This action (Offline Domain Join Provision) requires a writable domain controller.", this.Name));
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);
if (string.IsNullOrWhiteSpace(ComputerSamAccountName) || ComputerSamAccountName.Length > 24)
throw new System.ArgumentException("Invalid Computer Name; > 0 and <= 24", "ComputerName");
// Ensure Specified OU Exists
if (!string.IsNullOrEmpty(OrganisationalUnit))
{
try
{
using (var deOU = this.RetrieveDirectoryEntry(OrganisationalUnit, new string[] { "distinguishedName" }))
{
if (deOU == null)
throw new Exception(string.Format("OU's Directory Entry couldn't be found at [{0}]", OrganisationalUnit));
}
}
catch (Exception ex)
{
throw new ArgumentException(string.Format("An error occurred while trying to locate the specified OU: {0}", OrganisationalUnit), "OrganisationalUnit", ex);
}
}
if (MachineAccount != null)
MachineAccount.DeleteAccount(this);
string tempFileName = System.IO.Path.GetTempFileName();
string argumentOU = (!string.IsNullOrWhiteSpace(OrganisationalUnit)) ? string.Format(" /MACHINEOU \"{0}\"", OrganisationalUnit) : string.Empty;
string arguments = string.Format("/PROVISION /DOMAIN \"{0}\" /DCNAME \"{1}\" /MACHINE \"{2}\"{3} /REUSE /SAVEFILE \"{4}\"",
this.Domain.Name,
this.Name,
ComputerSamAccountName,
argumentOU,
tempFileName
);
ProcessStartInfo commandStarter = new ProcessStartInfo("DJOIN.EXE", arguments)
{
CreateNoWindow = true,
ErrorDialog = false,
LoadUserProfile = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
diagnosticInfo.AppendFormat("{0} {1}", "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 = System.Convert.ToBase64String(System.IO.File.ReadAllBytes(tempFileName));
System.IO.File.Delete(tempFileName);
}
if (string.IsNullOrWhiteSpace(DJoinResult))
throw new System.InvalidOperationException(string.Format("Domain Join Unsuccessful{0}Error: {1}{0}Output: {2}", System.Environment.NewLine, stdError, stdOutput));
DiagnosticInformation = diagnosticInfo.ToString();
// Reload Machine Account
MachineAccount = this.RetrieveADMachineAccount(string.Format(@"{0}\{1}", this.Domain.NetBiosName, ComputerSamAccountName), (MachineAccount == null ? null : MachineAccount.LoadedProperties.Keys.ToArray()));
return DJoinResult;
}
#endregion
public override string ToString()
{
return this.Name;
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is ADDomainController))
return false;
else
return this.Name == ((ADDomainController)obj).Name;
}
public override int GetHashCode()
{
return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this.Name);
}
}
}
@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Linq;
using System.Security.Principal;
namespace Disco.Services.Interop.ActiveDirectory
{
public class ADGroup : IADObject
{
internal static readonly string[] LoadProperties = { "name", "distinguishedName", "sAMAccountName", "objectSid", "memberOf" };
internal const string LdapSearchFilterTemplate = "(&(objectCategory=Group)(|(sAMAccountName=*{0}*)(name=*{0}*)(cn=*{0}*)))";
internal const string LdapSamAccountNameFilterTemplate = "(&(objectCategory=Group)(sAMAccountName={0}))";
internal const string LdapSecurityIdentifierFilterTemplate = "(&(objectCategory=Group)(objectSid={0}))";
public ADDomain Domain { get; private set; }
public string DistinguishedName { get; private set; }
public SecurityIdentifier SecurityIdentifier { get; private set; }
public string Id { get { return string.Format(@"{0}\{1}", Domain.NetBiosName, SamAccountName); } }
public string SamAccountName { get; private set; }
public string Name { get; private set; }
public string DisplayName { get { return this.Name; } }
public List<string> MemberOf { get; private set; }
private ADGroup(ADDomain Domain, string DistinguishedName, SecurityIdentifier SecurityIdentifier, string SamAccountName, string Name, List<string> MemberOf)
{
this.Domain = Domain;
this.DistinguishedName = DistinguishedName;
this.SecurityIdentifier = SecurityIdentifier;
this.SamAccountName = SamAccountName;
this.Name = Name;
this.MemberOf = MemberOf;
}
public static ADGroup FromSearchResult(ADSearchResult SearchResult)
{
if (SearchResult == null)
throw new ArgumentNullException("SearchResult");
var name = SearchResult.Value<string>("name");
var distinguishedName = SearchResult.Value<string>("distinguishedName");
var sAMAccountName = SearchResult.Value<string>("sAMAccountName");
var objectSid = new SecurityIdentifier(SearchResult.Value<byte[]>("objectSid"), 0);
var memberOf = SearchResult.Values<string>("memberOf").ToList();
return new ADGroup(SearchResult.Domain, distinguishedName, objectSid, sAMAccountName, name, memberOf);
}
public static ADGroup FromDirectoryEntry(ADDirectoryEntry DirectoryEntry)
{
if (DirectoryEntry == null)
throw new ArgumentNullException("DirectoryEntry");
var properties = DirectoryEntry.Entry.Properties;
var name = properties.Value<string>("name");
var distinguishedName = properties.Value<string>("distinguishedName");
var sAMAccountName = properties.Value<string>("sAMAccountName");
var objectSid = new SecurityIdentifier(properties.Value<byte[]>("objectSid"), 0);
var memberOf = properties.Values<string>("memberOf").ToList();
return new ADGroup(DirectoryEntry.Domain, distinguishedName, objectSid, sAMAccountName, name, memberOf);
}
public override string ToString()
{
return this.Id;
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is ADGroup))
return false;
else
return this.DistinguishedName == ((ADGroup)obj).DistinguishedName;
}
public override int GetHashCode()
{
return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this.DistinguishedName);
}
}
}
@@ -0,0 +1,66 @@
using System.Security.Principal;
using System.Text;
namespace Disco.Services.Interop.ActiveDirectory
{
internal static class ADHelpers
{
internal static byte[] ToBytes(this SecurityIdentifier Sid)
{
var sidBytes = new byte[Sid.BinaryLength];
Sid.GetBinaryForm(sidBytes, 0);
return sidBytes;
}
internal static SecurityIdentifier BuildPrimaryGroupSid(SecurityIdentifier UserSid, int PrimaryGroupId)
{
var groupSid = UserSid.ToBytes();
int ridOffset = groupSid.Length - 4;
int groupId = PrimaryGroupId;
for (int i = 0; i < 4; i++)
{
groupSid[ridOffset + i] = (byte)(groupId & 0xFF);
groupId >>= 8;
}
return new SecurityIdentifier(groupSid, 0);
}
internal static string ToBinaryString(this SecurityIdentifier Sid)
{
StringBuilder escapedSid = new StringBuilder();
foreach (var sidByte in Sid.ToBytes())
{
escapedSid.Append('\\');
escapedSid.Append(sidByte.ToString("x2"));
}
return escapedSid.ToString();
}
internal static string EscapeLdapQuery(string query)
{
return query.Replace("*", "\\2a").Replace("(", "\\28").Replace(")", "\\29").Replace("\\", "\\5c").Replace("NUL", "\\00").Replace("/", "\\2f");
}
internal static string ToLdapQueryFormat(this System.Guid g)
{
checked
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
byte[] array = g.ToByteArray();
for (int i = 0; i < array.Length; i++)
{
byte b = array[i];
sb.Append("\\");
sb.Append(b.ToString("X2"));
}
return sb.ToString();
}
}
}
}
@@ -0,0 +1,361 @@
using Disco.Models.Repository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
namespace Disco.Services.Interop.ActiveDirectory
{
public class ADMachineAccount : IADObject
{
internal static readonly string[] LoadProperties = { "name", "distinguishedName", "sAMAccountName", "objectSid", "dNSHostName", "netbootGUID", "isCriticalSystemObject" };
internal const string LdapSamAccountNameFilterTemplate = "(&(objectCategory=computer)(sAMAccountName={0}))";
internal const string LdapNetbootGuidSingleFilterTemplate = "(&(objectCategory=computer)(netbootGUID={0}))";
internal const string LdapNetbootGuidDoubleFilterTemplate = "(&(objectCategory=computer)(|(netbootGUID={0})(netbootGUID={1})))";
public ADDomain Domain { get; private set; }
public string DistinguishedName { get; private set; }
public SecurityIdentifier SecurityIdentifier { get; private set; }
public string Id { get { return string.Format(@"{0}\{1}", Domain.NetBiosName, SamAccountName); } }
public string SamAccountName { get; private set; }
public string Name { get; private set; }
public string DisplayName { get { return this.Name; } }
public string DnsName { get; private set; }
public Guid NetbootGUID { get; private set; }
public bool IsCriticalSystemObject { get; private set; }
public Dictionary<string, object[]> LoadedProperties { get; private set; }
public string ParentDistinguishedName
{
get
{
return DistinguishedName.Substring(DistinguishedName.IndexOf(',') + 1);
}
}
private ADMachineAccount(ADDomain Domain, string DistinguishedName, SecurityIdentifier SecurityIdentifier, string SamAccountName, string Name, string DnsName, Guid NetbootGUID, bool IsCriticalSystemObject, Dictionary<string, object[]> LoadedProperties)
{
this.Domain = Domain;
this.DistinguishedName = DistinguishedName;
this.SecurityIdentifier = SecurityIdentifier;
this.SamAccountName = SamAccountName;
this.Name = Name;
this.DnsName = DnsName;
this.NetbootGUID = NetbootGUID;
this.IsCriticalSystemObject = IsCriticalSystemObject;
this.LoadedProperties = LoadedProperties;
}
public static ADMachineAccount FromSearchResult(ADSearchResult SearchResult, string[] AdditionalProperties)
{
if (SearchResult == null)
throw new ArgumentNullException("SearchResult");
string name = SearchResult.Value<string>("name");
string sAMAccountName = SearchResult.Value<string>("sAMAccountName");
string distinguishedName = SearchResult.Value<string>("distinguishedName");
var objectSid = new SecurityIdentifier(SearchResult.Value<byte[]>("objectSid"), 0);
var dNSName = SearchResult.Value<string>("dNSHostName");
if (dNSName == null)
dNSName = string.Format("{0}.{1}", sAMAccountName.TrimEnd('$'), SearchResult.Domain.Name);
bool isCriticalSystemObject = SearchResult.Value<bool>("isCriticalSystemObject");
var netbootGUID = default(Guid);
byte[] netbootGuidBytes = SearchResult.Value<byte[]>("netbootGUID");
if (netbootGuidBytes != null)
netbootGUID = new Guid(netbootGuidBytes);
// Additional Properties
Dictionary<string, object[]> additionalProperties;
if (AdditionalProperties != null)
additionalProperties = AdditionalProperties
.Select(p => Tuple.Create(p, SearchResult.Values<object>(p).ToArray()))
.ToDictionary(t => t.Item1, t => t.Item2);
else
{
additionalProperties = new Dictionary<string, object[]>();
}
return new ADMachineAccount(
SearchResult.Domain,
distinguishedName,
objectSid,
sAMAccountName,
name,
dNSName,
netbootGUID,
isCriticalSystemObject,
additionalProperties);
}
public User ToRepositoryUser()
{
return new User
{
UserId = this.Id,
DisplayName = this.Name
};
}
public object GetPropertyValue(string PropertyName, int Index = 0)
{
switch (PropertyName.ToLower())
{
case "name":
return this.Name;
case "samaccountname":
return this.SamAccountName;
case "distinguishedname":
return this.DistinguishedName;
case "objectsid":
return this.SecurityIdentifier.ToString();
case "netbootguid":
return this.NetbootGUID;
default:
object[] adProperty;
if (this.LoadedProperties.TryGetValue(PropertyName, out adProperty) && Index <= adProperty.Length)
return adProperty[Index];
else
return null;
}
}
#region Actions
public void DeleteAccount(ADDomainController WritableDomainController)
{
if (this.IsCriticalSystemObject)
throw new InvalidOperationException(string.Format("This account [{0}] is a Critical System Active Directory Object and Disco refuses to modify it", this.DistinguishedName));
if (!WritableDomainController.IsWritable)
throw new InvalidOperationException(string.Format("The domain controller [{0}] is not writable. This action (Offline Domain Join Provision) requires a writable domain controller.", this.Name));
using (ADDirectoryEntry entry = WritableDomainController.RetrieveDirectoryEntry(this.DistinguishedName))
{
entry.Entry.DeleteTree();
}
}
public void DeleteAccount()
{
this.DeleteAccount(Domain.GetAvailableDomainController(RequireWritable: true));
}
private void SetNetbootGUID(ADDomainController WritableDomainController, System.Guid updatedNetbootGUID)
{
if (this.IsCriticalSystemObject)
throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", this.DistinguishedName));
using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(this.DistinguishedName))
{
var netbootGUIDProp = deAccount.Entry.Properties["netbootGUID"];
bool flag = netbootGUIDProp.Count > 0;
if (flag)
{
netbootGUIDProp.Clear();
}
netbootGUIDProp.Add(updatedNetbootGUID.ToByteArray());
deAccount.Entry.CommitChanges();
}
}
public void SetDescription(ADDomainController WritableDomainController, string Description)
{
using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(this.DistinguishedName))
{
var descriptionProp = deAccount.Entry.Properties["description"];
if (descriptionProp.Count > 0)
{
descriptionProp.Clear();
}
if (!string.IsNullOrEmpty(Description))
{
descriptionProp.Add(Description);
}
deAccount.Entry.CommitChanges();
}
}
public void SetDescription(string Description)
{
this.SetDescription(Domain.GetAvailableDomainController(RequireWritable: true), Description);
}
public void SetDescription(ADDomainController WritableDomainController, Device Device)
{
System.Text.StringBuilder descriptionBuilder = new System.Text.StringBuilder();
if (Device.AssignedUserId != null)
{
descriptionBuilder.Append(Device.AssignedUser.UserId).Append(" (").Append(Device.AssignedUser.DisplayName).Append("); ");
}
if (Device.DeviceModelId.HasValue)
{
descriptionBuilder.Append(Device.DeviceModel.Description).Append("; ");
}
descriptionBuilder.Append(Device.DeviceProfile.Description).Append(";");
string description = descriptionBuilder.ToString().Trim();
if (description.Length > 1024)
description = description.Substring(0, 1024);
this.SetDescription(WritableDomainController, description);
}
public void SetDescription(Device Device)
{
this.SetDescription(Domain.GetAvailableDomainController(RequireWritable: true), Device);
}
public void DisableAccount(ADDomainController WritableDomainController)
{
if (this.IsCriticalSystemObject)
throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", this.DistinguishedName));
using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(this.DistinguishedName))
{
int accountControl = (int)deAccount.Entry.Properties["userAccountControl"][0];
int updatedAccountControl = (accountControl | 2);
if (accountControl != updatedAccountControl)
{
deAccount.Entry.Properties["userAccountControl"][0] = updatedAccountControl;
deAccount.Entry.CommitChanges();
}
}
}
public void DisableAccount()
{
this.DisableAccount(Domain.GetAvailableDomainController(RequireWritable: true));
}
public void EnableAccount(ADDomainController WritableDomainController)
{
if (this.IsCriticalSystemObject)
throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", this.DistinguishedName));
using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(this.DistinguishedName))
{
int accountControl = (int)deAccount.Entry.Properties["userAccountControl"][0];
if ((accountControl & 2) == 2)
{
int updatedAccountControl = (accountControl ^ 2);
deAccount.Entry.Properties["userAccountControl"][0] = updatedAccountControl;
deAccount.Entry.CommitChanges();
}
}
}
public void EnableAccount()
{
this.EnableAccount(Domain.GetAvailableDomainController(RequireWritable: true));
}
public bool UpdateNetbootGUID(ADDomainController WritableDomainController, string UUID, string MACAddress)
{
if (this.IsCriticalSystemObject)
throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", this.DistinguishedName));
Guid netbootGUID = Guid.Empty;
if (!string.IsNullOrWhiteSpace(UUID))
{
netbootGUID = NetbootGUIDFromUUID(UUID);
}
else if (!string.IsNullOrWhiteSpace(MACAddress))
{
netbootGUID = NetbootGUIDFromMACAddress(MACAddress);
}
if (netbootGUID != System.Guid.Empty && netbootGUID != this.NetbootGUID)
{
this.SetNetbootGUID(WritableDomainController, netbootGUID);
return true;
}
else
{
return false;
}
}
public void UpdateNetbootGUID(string UUID, string MACAddress)
{
this.UpdateNetbootGUID(Domain.GetAvailableDomainController(RequireWritable: true), UUID, MACAddress);
}
public static System.Guid NetbootGUIDFromUUID(string UUID)
{
return new Guid(UUID);
}
public static System.Guid NetbootGUIDFromMACAddress(string MACAddress)
{
string strippedMACAddress = MACAddress.Trim().Replace(":", string.Empty).Replace("-", string.Empty);
bool flag = strippedMACAddress.Length == 12;
System.Guid NetbootGUIDFromMACAddress;
if (flag)
{
System.Guid guid = new System.Guid(string.Format("00000000-0000-0000-0000-{0}", strippedMACAddress));
NetbootGUIDFromMACAddress = guid;
}
else
{
NetbootGUIDFromMACAddress = System.Guid.Empty;
}
return NetbootGUIDFromMACAddress;
}
public void MoveOrganisationalUnit(ADDomainController WritableDomainController, string NewOrganisationUnit)
{
if (this.IsCriticalSystemObject)
throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", this.DistinguishedName));
var parentDistinguishedName = this.ParentDistinguishedName;
if (parentDistinguishedName != null && !parentDistinguishedName.Equals(NewOrganisationUnit, StringComparison.OrdinalIgnoreCase))
{
// If no OU provided, place in default Computers container
if (string.IsNullOrWhiteSpace(NewOrganisationUnit))
NewOrganisationUnit = Domain.DefaultComputerContainer;
if (!NewOrganisationUnit.EndsWith(Domain.DistinguishedName, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException(string.Format("Unable to move AD Account from one domain [{0}] to another [{1}].", this.DistinguishedName, NewOrganisationUnit));
using (ADDirectoryEntry ou = WritableDomainController.RetrieveDirectoryEntry(NewOrganisationUnit))
{
using (ADDirectoryEntry i = WritableDomainController.RetrieveDirectoryEntry(this.DistinguishedName))
{
i.Entry.UsePropertyCache = false;
i.Entry.MoveTo(ou.Entry);
// Update Distinguished Name
this.DistinguishedName = i.Entry.Properties["distinguishedName"][0].ToString();
}
}
}
}
public void MoveOrganisationalUnit(string NewOrganisationUnit)
{
this.MoveOrganisationalUnit(Domain.GetAvailableDomainController(RequireWritable: true), NewOrganisationUnit);
}
#endregion
public override string ToString()
{
return this.Id;
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is ADMachineAccount))
return false;
else
return this.DistinguishedName == ((ADMachineAccount)obj).DistinguishedName;
}
public override int GetHashCode()
{
return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this.DistinguishedName);
}
}
}
@@ -0,0 +1,46 @@
using System.Collections.Generic;
namespace Disco.Services.Interop.ActiveDirectory
{
public class ADOrganisationalUnit
{
public ADDomain Domain { get; private set; }
public string DistinguishedName { get; private set; }
public string Name { get; private set; }
public List<ADOrganisationalUnit> Children { get; internal set; }
private ADOrganisationalUnit(ADDomain Domain, string DistinguishedName, string Name, List<ADOrganisationalUnit> Children)
{
this.Domain = Domain;
this.DistinguishedName = DistinguishedName;
this.Name = Name;
this.Children = Children;
}
public static ADOrganisationalUnit FromSearchResult(ADSearchResult SearchResult)
{
string distinguishedName = SearchResult.Value<string>("distinguishedName");
string name = SearchResult.Value<string>("name");
return new ADOrganisationalUnit(SearchResult.Domain, distinguishedName, name, null);
}
public override string ToString()
{
return this.Name;
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is ADOrganisationalUnit))
return false;
else
return this.DistinguishedName == ((ADOrganisationalUnit)obj).DistinguishedName;
}
public override int GetHashCode()
{
return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this.DistinguishedName);
}
}
}
@@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.DirectoryServices;
using System.Linq;
namespace Disco.Services.Interop.ActiveDirectory
{
public class ADSearchResult
{
private SearchResult _result;
public ADDomain Domain { get; private set; }
public ADDomainController DomainController { get; private set; }
public string SearchPath { get; private set; }
public string LdapFilter { get; private set; }
public string LdapPath { get; private set; }
public string DistinguishedName { get; private set; }
public ADSearchResult(ADDomain Domain, ADDomainController DomainController, string SearchPath, string LdapFilter, SearchResult Result)
{
this._result = Result;
this.Domain = Domain;
this.DomainController = DomainController;
this.SearchPath = SearchPath;
this.LdapFilter = LdapFilter;
this.LdapPath = _result.Path;
this.DistinguishedName = Value<string>("dn");
}
public bool Contains(string PropertyName)
{
return _result.Properties.Contains(PropertyName);
}
public T Value<T>(string PropertyName)
{
var p = Values<T>(PropertyName);
return p.FirstOrDefault();
}
public IEnumerable<T> Values<T>(string PropertyName)
{
var p = _result.Properties[PropertyName];
return p.OfType<T>();
}
public override string ToString()
{
return this.LdapPath;
}
}
}
@@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.DirectoryServices.ActiveDirectory;
using System.Linq;
namespace Disco.Services.Interop.ActiveDirectory
{
public class ADSite
{
private ActiveDirectoryContext context;
public ActiveDirectorySite Site { get; private set; }
public string Name { get; private set; }
public List<ADDomainController> DomainControllers { get; private set; }
public ADSite(ActiveDirectoryContext Context, ActiveDirectorySite Site)
{
this.context = Context;
this.Site = Site;
this.Name = Site.Name;
this.DomainControllers = null;
}
internal void UpdateDomainControllers(IEnumerable<ADDomainController> DomainControllers)
{
this.DomainControllers = DomainControllers.ToList();
}
public override string ToString()
{
return this.Name;
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is ADSite))
return false;
else
return this.Name == ((ADSite)obj).Name;
}
public override int GetHashCode()
{
return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this.Name);
}
}
}
@@ -0,0 +1,205 @@
using Disco.Data.Repository;
using Disco.Models.Repository;
using Disco.Services.Logging;
using Disco.Services.Tasks;
using Quartz;
using System;
using System.Collections;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
using System.Linq;
namespace Disco.Services.Interop.ActiveDirectory
{
public class ADTaskUpdateNetworkLogonDates : ScheduledTask
{
public override string TaskName { get { return "Active Directory - Update Last Network Logon Dates Task"; } }
public override bool SingleInstanceTask { get { return true; } }
public override bool CancelInitiallySupported { get { return false; } }
public override void InitalizeScheduledTask(DiscoDataContext Database)
{
// ActiveDirectoryUpdateLastNetworkLogonDateJob @ 11:30pm
TriggerBuilder triggerBuilder = TriggerBuilder.Create().
WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(23, 30));
this.ScheduleTask(triggerBuilder);
}
protected override void ExecuteTask()
{
int changeCount;
this.Status.UpdateStatus(1, "Starting", "Connecting to the Database and initializing the environment");
using (DiscoDataContext database = new DiscoDataContext())
{
UpdateLastNetworkLogonDates(database, this.Status);
this.Status.UpdateStatus(95, "Updating Database", "Writing last network logon dates to the Database");
changeCount = database.SaveChanges();
this.Status.Finished(string.Format("{0} Device last network logon dates updated", changeCount), "/Config/SystemConfig");
}
SystemLog.LogInformation(new string[]
{
"Updated LastNetworkLogon Device Property for Device/s",
changeCount.ToString()
});
}
public static ScheduledTaskStatus ScheduleImmediately()
{
var existingTask = ScheduledTasks.GetTaskStatuses(typeof(ADTaskUpdateNetworkLogonDates)).Where(s => s.IsRunning).FirstOrDefault();
if (existingTask != null)
return existingTask;
var instance = new ADTaskUpdateNetworkLogonDates();
return instance.ScheduleTask();
}
public static bool UpdateLastNetworkLogonDate(Device Device)
{
const string ldapFilterTemplate = "(&(objectCategory=Computer)(sAMAccountName={0}))";
string[] ldapProperties = new string[] { "lastLogon", "lastLogonTimestamp" };
System.DateTime? lastLogon = null;
if (!string.IsNullOrEmpty(Device.DeviceDomainId))
{
var context = ActiveDirectory.Context;
var deviceSamAccountName = UserExtensions.SplitUserId(Device.DeviceDomainId).Item2 + "$";
var ldapFilter = string.Format(ldapFilterTemplate, ADHelpers.EscapeLdapQuery(deviceSamAccountName));
var domain = context.GetDomainFromId(Device.DeviceDomainId);
IEnumerable<ADDomainController> domainControllers;
if (context.SearchAllForestServers)
domainControllers = domain.GetAllReachableDomainControllers();
else
domainControllers = domain.GetReachableSiteDomainControllers();
lastLogon = domainControllers.Select(dc =>
{
var result = dc.SearchEntireDomain(ldapFilter, ldapProperties, ActiveDirectory.SingleSearchResult).FirstOrDefault();
if (result != null)
{
long lastLogonValue = default(long);
long lastLogonTimestampValue = default(long);
lastLogonValue = result.Value<long>("lastLogon");
lastLogonTimestampValue = result.Value<long>("lastLogonTimestamp");
long highedValue = Math.Max(lastLogonValue, lastLogonTimestampValue);
if (highedValue > 0)
return (DateTime?)new DateTime((DateTime.FromFileTime(highedValue).Ticks / 10000000L) * 10000000L);
else
return null;
}
return null;
}).Where(dt => dt.HasValue).Max();
}
if (lastLogon.HasValue &&
(
!Device.LastNetworkLogonDate.HasValue
|| Device.LastNetworkLogonDate.Value < lastLogon
))
{
Device.LastNetworkLogonDate = lastLogon;
return true;
}
return false;
}
private static void UpdateLastNetworkLogonDates(DiscoDataContext Database, ScheduledTaskStatus status)
{
var context = ActiveDirectory.Context;
const string ldapFilter = "(objectCategory=Computer)";
string[] ldapProperties = new string[] { "sAMAccountName", "lastLogon" };
status.UpdateStatus(2, "Initializing", "Determining Domains and Available Domain Controllers");
// Determine Domain Scopes to Query
var domainQueries = context.Domains
.Select(d => Tuple.Create(d, d.SearchContainers ?? new List<string>() { d.DistinguishedName }))
.Where(d => d.Item2.Count > 0);
// Determine Domain Controllers to Query
IEnumerable<Tuple<ADDomain, ADDomainController, List<string>>> serverQueries;
if (context.SearchAllForestServers)
serverQueries = domainQueries.SelectMany(q => q.Item1.GetAllReachableDomainControllers(), (q, dc) => Tuple.Create(q.Item1, dc, q.Item2));
else
serverQueries = domainQueries.SelectMany(q => q.Item1.GetReachableSiteDomainControllers(), (q, dc) => Tuple.Create(q.Item1, dc, q.Item2));
var scopedQueries = serverQueries.SelectMany(q => q.Item3, (q, scope) => Tuple.Create(q.Item1, q.Item2, scope)).ToList();
var queries = Enumerable.Range(0, scopedQueries.Count).Select(i =>
{
var q = scopedQueries[i];
return Tuple.Create(i, q.Item1, q.Item2, q.Item3);
});
var queryResults = queries.SelectMany(q =>
{
var queryIndex = q.Item1;
var domain = q.Item2;
var domainController = q.Item3;
var searchRoot = q.Item4;
// Update Status
double progress = 5 + (queryIndex * (90 / scopedQueries.Count));
status.UpdateStatus(progress, string.Format("Querying Domain [{0}] using controller [{1}]", domain.NetBiosName, domainController.Name), string.Format("Searching: {0}", searchRoot));
// Perform Query
var directoryResults = domainController.SearchInternal(searchRoot, ldapFilter, ldapProperties, null);
return directoryResults.Select(result =>
{
var samAccountName = result.Value<string>("sAMAccountName");
long lastLogonValue = default(long);
long lastLogonTimestampValue = default(long);
lastLogonValue = result.Value<long>("lastLogon");
lastLogonTimestampValue = result.Value<long>("lastLogonTimestamp");
long highedValue = Math.Max(lastLogonValue, lastLogonTimestampValue);
if (highedValue > 0)
{
var computerName = string.Format(@"{0}\{1}", domain.NetBiosName, samAccountName.TrimEnd('$'));
var lastLogon = new DateTime((DateTime.FromFileTime(highedValue).Ticks / 10000000L) * 10000000L);
return Tuple.Create(computerName, lastLogon);
}
else
return null;
}).Where(i => i != null).ToList();
}).GroupBy(r => r.Item1, StringComparer.OrdinalIgnoreCase).ToDictionary(g => g.Key.ToUpper(), g => g.Max(i => i.Item2));
status.UpdateStatus(90, "Processing Results", "Processing last network logon dates and looking for updates");
foreach (Device device in Database.Devices.Where(device => device.DeviceDomainId != null))
{
DateTime lastLogonDate;
if (queryResults.TryGetValue(device.DeviceDomainId.ToUpper(), out lastLogonDate))
{
if (!device.LastNetworkLogonDate.HasValue)
device.LastNetworkLogonDate = lastLogonDate;
else
{
// Change accuracy to the second
lastLogonDate = new DateTime((lastLogonDate.Ticks / 10000000L) * 10000000L);
if (device.LastNetworkLogonDate.Value < lastLogonDate)
device.LastNetworkLogonDate = lastLogonDate;
}
}
}
}
}
}
@@ -0,0 +1,173 @@
using Disco.Models.Repository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
namespace Disco.Services.Interop.ActiveDirectory
{
public class ADUserAccount : IADObject
{
internal const string LdapSamAccountNameFilterTemplate = "(&(objectCategory=Person)(sAMAccountName={0}))";
internal const string LdapSearchFilterTemplate = "(&(objectCategory=Person)(objectClass=user)(|(sAMAccountName=*{0}*)(displayName=*{0}*)))";
internal static readonly string[] LoadProperties = { "name", "distinguishedName", "sAMAccountName", "objectSid", "displayName", "sn", "givenName", "memberOf", "primaryGroupID", "mail", "telephoneNumber" };
internal static readonly string[] QuickLoadProperties = { "name", "distinguishedName", "sAMAccountName", "objectSid", "displayName", "sn", "givenName", "mail", "telephoneNumber" };
public ADDomain Domain { get; private set; }
public string DistinguishedName { get; private set; }
public SecurityIdentifier SecurityIdentifier { get; private set; }
public string Id { get { return string.Format(@"{0}\{1}", Domain.NetBiosName, SamAccountName); } }
public string SamAccountName { get; private set; }
public string Name { get; private set; }
public string DisplayName { get; private set; }
public string Surname { get; private set; }
public string GivenName { get; private set; }
public string Email { get; private set; }
public string Phone { get; private set; }
public List<ADGroup> Groups { get; private set; }
public Dictionary<string, object[]> LoadedProperties { get; private set; }
private ADUserAccount(ADDomain Domain, string DistinguishedName, SecurityIdentifier SecurityIdentifier, string SamAccountName,
string Name, string DisplayName, string Surname, string GivenName, string Email, string Phone,
List<ADGroup> Groups, Dictionary<string, object[]> LoadedProperties)
{
this.Domain = Domain;
this.DistinguishedName = DistinguishedName;
this.SecurityIdentifier = SecurityIdentifier;
this.SamAccountName = SamAccountName;
this.Name = Name;
this.DisplayName = DisplayName;
this.Surname = Surname;
this.GivenName = GivenName;
this.Email = Email;
this.Phone = Phone;
this.Groups = Groups;
this.LoadedProperties = LoadedProperties;
}
public static ADUserAccount FromSearchResult(ADSearchResult SearchResult, bool Quick, string[] AdditionalProperties)
{
if (SearchResult == null)
throw new ArgumentNullException("SearchResult");
string name = SearchResult.Value<string>("name");
string sAMAccountName = SearchResult.Value<string>("sAMAccountName");
string distinguishedName = SearchResult.Value<string>("distinguishedName");
var objectSid = new SecurityIdentifier(SearchResult.Value<byte[]>("objectSid"), 0);
var displayName = SearchResult.Value<string>("displayName") ?? sAMAccountName;
var surname = SearchResult.Value<string>("sn");
string givenName = SearchResult.Value<string>("givenName");
string email = SearchResult.Value<string>("mail");
string phone = SearchResult.Value<string>("telephoneNumber");
List<ADGroup> groups = null;
// Don't load Groups when doing a quick search
if (!Quick)
{
int primaryGroupID = (int)SearchResult.Value<int>("primaryGroupID");
var primaryGroupSid = ADHelpers.BuildPrimaryGroupSid(objectSid, primaryGroupID);
var memberGroups = SearchResult.Values<string>("memberOf");
var primaryGroup = ActiveDirectory.GroupCache.GetGroup(primaryGroupSid);
var groupDistinguishedNames =
new string[] { primaryGroup.DistinguishedName }
.Concat(memberGroups);
groups = ActiveDirectory.GroupCache.GetRecursiveGroups(groupDistinguishedNames).ToList();
}
// Additional Properties
Dictionary<string, object[]> additionalProperties;
if (AdditionalProperties != null)
additionalProperties = AdditionalProperties
.Select(p => Tuple.Create(p, SearchResult.Values<object>(p).ToArray()))
.ToDictionary(t => t.Item1, t => t.Item2);
else
{
additionalProperties = new Dictionary<string, object[]>();
}
return new ADUserAccount(
SearchResult.Domain,
distinguishedName,
objectSid,
sAMAccountName,
name,
displayName,
surname,
givenName,
email,
phone,
groups,
additionalProperties);
}
public object GetPropertyValue(string PropertyName, int Index = 0)
{
switch (PropertyName.ToLower())
{
case "name":
return this.Name;
case "samaccountname":
return this.SamAccountName;
case "distinguishedname":
return this.DistinguishedName;
case "objectsid":
return this.SecurityIdentifier.ToString();
case "sn":
return this.Surname;
case "givenname":
return this.GivenName;
case "mail":
return this.Email;
case "telephonenumber":
return this.Phone;
default:
object[] adProperty;
if (this.LoadedProperties.TryGetValue(PropertyName, out adProperty) && Index <= adProperty.Length)
return adProperty[Index];
else
return null;
}
}
public User ToRepositoryUser()
{
return new User
{
UserId = this.Id,
DisplayName = this.DisplayName,
Surname = this.Surname,
GivenName = this.GivenName,
EmailAddress = this.Email,
PhoneNumber = this.Phone,
};
}
public override string ToString()
{
return this.Id;
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is ADUserAccount))
return false;
else
return this.DistinguishedName == ((ADUserAccount)obj).DistinguishedName;
}
public override int GetHashCode()
{
return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this.DistinguishedName);
}
}
}
@@ -1,595 +1,226 @@
using Disco.Data.Repository;
using Disco.Models.Interop.ActiveDirectory;
using Disco.Services.Interop.ActiveDirectory.Internal;
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
using System.Diagnostics;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
namespace Disco.Services.Interop.ActiveDirectory
{
public static class ActiveDirectory
{
private const int SingleSearchResult = 1;
public const int SingleSearchResult = 1;
public const int DefaultSearchResultLimit = 30;
public const int MaxForestServerSearch = 30;
public const int DomainControllerUnavailableMinutes = 10;
private static ActiveDirectoryContext context;
private static ActiveDirectoryGroupCache groupCache;
private static object contextInitializingLock = new object();
public static void Initialize(DiscoDataContext Database)
{
ADInterop.Initialize(Database);
}
public static void UpdateSearchContainers(DiscoDataContext Database, List<string> Containers)
{
ADInterop.UpdateSearchContainers(Database, Containers);
}
public static bool UpdateSearchEntireForest(DiscoDataContext Database, bool SearchEntireForest)
{
return ADInterop.UpdateSearchEntireForest(Database, SearchEntireForest);
}
public static ActiveDirectoryDomain PrimaryDomain
{
get
lock (contextInitializingLock)
{
return ADInterop.PrimaryDomain;
}
}
public static IEnumerable<ActiveDirectoryDomain> Domains
{
get
{
return ADInterop.Domains.ToList();
context = new ActiveDirectoryContext(Database);
groupCache = new ActiveDirectoryGroupCache();
}
}
public static ActiveDirectorySite Site
public static ActiveDirectoryContext Context
{
get
{
return ADInterop.Site;
}
}
public static ActiveDirectoryDomain GetDomainByDistinguishedName(string DistinguishedName)
{
return ADInterop.GetDomainByDistinguishedName(DistinguishedName);
}
public static ActiveDirectoryDomain GetDomainByNetBiosName(string NetBiosName)
{
return ADInterop.GetDomainByNetBiosName(NetBiosName);
}
public static ActiveDirectoryDomain GetDomainByDnsName(string DnsName)
{
return ADInterop.GetDomainByDnsName(DnsName);
}
public static ActiveDirectoryDomain GetDomainFromId(string Id)
{
return ADInterop.GetDomainFromId(Id);
}
public static List<string> LoadForestServers()
{
return ADInterop.LoadForestServers();
}
public static Task<List<string>> LoadForestServersAsync()
{
return ADInterop.LoadForestServersAsync();
}
public static bool SearchEntireForest
{
get
{
return ADInterop.SearchEntireForest;
}
}
#region Machine Account
private static readonly string[] MachineLoadProperties = { "name", "distinguishedName", "sAMAccountName", "objectSid", "dNSHostName", "netbootGUID", "isCriticalSystemObject" };
public static ActiveDirectoryMachineAccount RetrieveMachineAccount(DomainController DomainController, string Id, params string[] AdditionalProperties)
{
return RetrieveMachineAccount(DomainController, Id, (System.Guid?)null, (System.Guid?)null, AdditionalProperties);
}
public static ActiveDirectoryMachineAccount RetrieveMachineAccount(string Id, params string[] AdditionalProperties)
{
return RetrieveMachineAccount(Id, (System.Guid?)null, (System.Guid?)null, AdditionalProperties);
}
public static ActiveDirectoryMachineAccount RetrieveMachineAccount(DomainController DomainController, string Id, System.Guid? UUIDNetbootGUID, params string[] AdditionalProperties)
{
return RetrieveMachineAccount(DomainController, Id, UUIDNetbootGUID, (System.Guid?)null, AdditionalProperties);
}
public static ActiveDirectoryMachineAccount RetrieveMachineAccount(string Id, System.Guid? UUIDNetbootGUID, params string[] AdditionalProperties)
{
return RetrieveMachineAccount(Id, UUIDNetbootGUID, (System.Guid?)null, AdditionalProperties);
}
public static ActiveDirectoryMachineAccount RetrieveMachineAccount(string Id, System.Guid? UUIDNetbootGUID, System.Guid? MacAddressNetbootGUID, params string[] AdditionalProperties)
{
return RetrieveMachineAccount(null, Id, UUIDNetbootGUID, MacAddressNetbootGUID, AdditionalProperties);
}
public static ActiveDirectoryMachineAccount RetrieveMachineAccount(DomainController DomainController, string Id, System.Guid? UUIDNetbootGUID, System.Guid? MacAddressNetbootGUID, params string[] AdditionalProperties)
{
if (string.IsNullOrWhiteSpace(Id))
throw new ArgumentNullException("Id");
// Add $ identifier for machine accounts
if (!Id.EndsWith("$"))
Id += "$";
const string ldapFilterTemplate = "(&(objectCategory=computer)(sAMAccountName={0}))";
const string ldapNetbootGuidFilterTemplate = "(&(objectCategory=computer)(netbootGUID={0}))";
string[] loadProperites = (AdditionalProperties != null && AdditionalProperties.Length > 0)
? MachineLoadProperties.Concat(AdditionalProperties).ToArray()
: MachineLoadProperties;
ActiveDirectorySearchResult adResult;
var splitId = UserExtensions.SplitUserId(Id);
var ldapSamAccountFilter = string.Format(ldapFilterTemplate, splitId.Item2);
var domain = ADInterop.GetDomainFromId(Id);
adResult = ADInterop.SearchAll(domain, DomainController, ldapSamAccountFilter, SingleSearchResult, loadProperites).FirstOrDefault();
if (adResult == null && (UUIDNetbootGUID.HasValue || MacAddressNetbootGUID.HasValue))
{
if (UUIDNetbootGUID.HasValue)
if (context == null)
{
var ldapFilter = string.Format(ldapNetbootGuidFilterTemplate, ADInterop.FormatGuidForLdapQuery(UUIDNetbootGUID.Value));
adResult = ADInterop.SearchAll(domain, DomainController, ldapFilter, SingleSearchResult, loadProperites).FirstOrDefault();
lock (contextInitializingLock)
{
if (context == null)
throw new InvalidOperationException("Active Directory interoperability hasn't been initialized");
}
}
if (adResult == null && MacAddressNetbootGUID.HasValue)
{
var ldapFilter = string.Format(ldapNetbootGuidFilterTemplate, ADInterop.FormatGuidForLdapQuery(MacAddressNetbootGUID.Value));
adResult = ADInterop.SearchAll(domain, DomainController, ldapFilter, SingleSearchResult, loadProperites).FirstOrDefault();
}
}
if (adResult != null)
return adResult.AsMachineAccount(AdditionalProperties);
return context;
}
}
public static ActiveDirectoryGroupCache GroupCache
{
get
{
if (groupCache == null)
{
lock (contextInitializingLock)
{
if (groupCache == null)
throw new InvalidOperationException("Active Directory interoperability hasn't been initialized");
}
}
return groupCache;
}
}
#region User Accounts
public static ADUserAccount RetrieveADUserAccount(string Id, params string[] AdditionalProperties)
{
var domain = Context.GetDomainFromId(Id);
return domain.GetAvailableDomainController().RetrieveADUserAccount(Id, AdditionalProperties);
}
public static IEnumerable<ADUserAccount> SearchADUserAccounts(string Term, bool Quick, int? ResultLimit = ActiveDirectory.DefaultSearchResultLimit, params string[] AdditionalProperties)
{
if (string.IsNullOrWhiteSpace(Term))
throw new ArgumentNullException("Term");
ADDomain searchDomain;
var term = RelevantSearchTerm(Term, out searchDomain);
if (string.IsNullOrWhiteSpace(term))
return Enumerable.Empty<ADUserAccount>();
var ldapFilter = string.Format(ADUserAccount.LdapSearchFilterTemplate, ADHelpers.EscapeLdapQuery(term));
IEnumerable<ADSearchResult> searchResults;
if (searchDomain != null)
searchResults = searchDomain.SearchScope(ldapFilter, ADGroup.LoadProperties, ResultLimit);
else
return null; // Not Found
}
searchResults = Context.SearchScope(ldapFilter, ADGroup.LoadProperties, ResultLimit);
private static ActiveDirectoryMachineAccount AsMachineAccount(this ActiveDirectorySearchResult item, string[] AdditionalProperties)
{
string name = item.Result.Properties["name"][0].ToString();
string sAMAccountName = item.Result.Properties["sAMAccountName"][0].ToString();
string distinguishedName = item.Result.Properties["distinguishedName"][0].ToString();
string objectSid = ADInterop.ConvertBytesToSDDLString((byte[])item.Result.Properties["objectSid"][0]);
var dNSNameProperty = item.Result.Properties["dNSHostName"];
string dNSName = null;
if (dNSNameProperty.Count > 0)
dNSName = dNSNameProperty[0].ToString();
else
dNSName = string.Format("{0}.{1}", sAMAccountName.TrimEnd('$'), item.Domain.DnsName);
bool isCriticalSystemObject = (bool)item.Result.Properties["isCriticalSystemObject"][0];
System.Guid netbootGUIDResult = default(System.Guid);
ResultPropertyValueCollection netbootGUIDProp = item.Result.Properties["netbootGUID"];
if (netbootGUIDProp.Count > 0)
{
netbootGUIDResult = new System.Guid((byte[])netbootGUIDProp[0]);
}
// Additional Properties
Dictionary<string, object[]> additionalProperties = new Dictionary<string, object[]>();
if (AdditionalProperties != null)
foreach (string propertyName in AdditionalProperties)
{
var property = item.Result.Properties[propertyName];
var propertyValues = new List<object>();
for (int index = 0; index < property.Count; index++)
propertyValues.Add(property[index]);
additionalProperties.Add(propertyName, propertyValues.ToArray());
}
return new ActiveDirectoryMachineAccount
{
Name = name,
DistinguishedName = distinguishedName,
SamAccountName = sAMAccountName,
SecurityIdentifier = objectSid,
NetbootGUID = netbootGUIDResult,
Path = item.Result.Path,
Domain = item.Domain.NetBiosName,
DnsName = dNSName,
IsCriticalSystemObject = isCriticalSystemObject,
LoadedProperties = additionalProperties
};
}
public static string OfflineDomainJoinProvision(ActiveDirectoryDomain Domain, DomainController DomainController, string ComputerName, string OrganisationalUnit, ref ActiveDirectoryMachineAccount MachineAccount, out string DiagnosticInformation)
{
const string ldapFilterTemplate = "(&(objectCategory=computer)(sAMAccountName={0}))";
if (MachineAccount != null && MachineAccount.IsCriticalSystemObject)
throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", MachineAccount.DistinguishedName));
if (MachineAccount != null)
MachineAccount.DeleteAccount(DomainController);
var computerId = UserExtensions.SplitUserId(ComputerName);
var offlineJoinResult = ADInterop.OfflineDomainJoinProvision(Domain, DomainController, computerId.Item2, OrganisationalUnit, out DiagnosticInformation);
// Reload newly created Account
string[] loadAdditionalProperties = (MachineAccount != null && MachineAccount.LoadedProperties != null && MachineAccount.LoadedProperties.Count > 0)
? MachineAccount.LoadedProperties.Keys.ToArray()
: null;
MachineAccount = null;
string[] loadProperites = (loadAdditionalProperties != null)
? MachineLoadProperties.Concat(loadAdditionalProperties).ToArray()
: MachineLoadProperties;
var ldapSamAccountName = computerId.Item2.EndsWith("$") ? computerId.Item2 : computerId.Item2 + "$";
var ldapFilter = string.Format(ldapFilterTemplate, ldapSamAccountName);
var ldapResult = ADInterop.SearchAll(Domain, DomainController, ldapFilter, 1, loadProperites).FirstOrDefault();
if (ldapResult != null)
MachineAccount = ldapResult.AsMachineAccount(loadAdditionalProperties);
return offlineJoinResult;
return searchResults.Select(result => result.AsADUserAccount(Quick, AdditionalProperties));
}
#endregion
#region User Account
private static readonly string[] UserLoadProperties = { "name", "distinguishedName", "sAMAccountName", "objectSid", "displayName", "sn", "givenName", "memberOf", "primaryGroupID", "mail", "telephoneNumber" };
private static readonly string[] UserQuickLoadProperties = { "name", "distinguishedName", "sAMAccountName", "objectSid", "displayName", "sn", "givenName", "mail", "telephoneNumber" };
public static ActiveDirectoryUserAccount RetrieveUserAccount(string Id, params string[] AdditionalProperties)
#region Machine Accounts
public static ADMachineAccount RetrieveADMachineAccount(string Id, params string[] AdditionalProperties)
{
const string ldapFilter = "(&(objectCategory=Person)(sAMAccountName={0}))";
string[] loadProperites = (AdditionalProperties != null && AdditionalProperties.Length > 0)
? UserLoadProperties.Concat(AdditionalProperties).ToArray()
: UserLoadProperties;
return SearchBySamAccountName(Id, ldapFilter, loadProperites).Select(result => result.AsUserAccount(false, AdditionalProperties)).FirstOrDefault();
var domain = Context.GetDomainFromId(Id);
return domain.GetAvailableDomainController().RetrieveADMachineAccount(Id, AdditionalProperties);
}
public static IEnumerable<ActiveDirectoryUserAccount> SearchUserAccounts(string Term, params string[] AdditionalProperties)
public static ADMachineAccount RetrieveADMachineAccount(string Id, System.Guid? NetbootGUID, params string[] AdditionalProperties)
{
return SearchUserAccounts(Term, false, AdditionalProperties);
var domain = Context.GetDomainFromId(Id);
return domain.GetAvailableDomainController().RetrieveADMachineAccount(Id, NetbootGUID, AdditionalProperties);
}
public static IEnumerable<ActiveDirectoryUserAccount> SearchUserAccounts(string Term, bool Quick, params string[] AdditionalProperties)
public static ADMachineAccount RetrieveADMachineAccount(string Id, System.Guid? UUIDNetbootGUID, System.Guid? MacAddressNetbootGUID, params string[] AdditionalProperties)
{
const int resultLimit = 30; // Default Search Limit
if (string.IsNullOrWhiteSpace(Term))
throw new ArgumentNullException("Term");
// Apply Domain Restriction
ActiveDirectoryDomain searchDomain = null;
Term = ApplySearchTermDomainRestriction(Term, out searchDomain);
if (string.IsNullOrWhiteSpace(Term))
return Enumerable.Empty<ActiveDirectoryUserAccount>();
string ldapFilter = string.Format("(&(objectCategory=Person)(objectClass=user)(|(sAMAccountName=*{0}*)(displayName=*{0}*)))", ADInterop.EscapeLdapQuery(Term));
string[] loadProperites = Quick
? UserQuickLoadProperties
: UserLoadProperties;
if (AdditionalProperties != null && AdditionalProperties.Length > 0)
loadProperites.Concat(AdditionalProperties).ToArray();
IEnumerable<ActiveDirectorySearchResult> searchResults;
if (searchDomain == null)
searchResults = ADInterop.SearchScope(ldapFilter, resultLimit, loadProperites);
else
searchResults = ADInterop.SearchScope(searchDomain, ldapFilter, resultLimit, loadProperites);
return searchResults.Select(result => result.AsUserAccount(Quick, AdditionalProperties));
}
private static ActiveDirectoryUserAccount AsUserAccount(this ActiveDirectorySearchResult item, bool Quick, string[] AdditionalProperties)
{
string name = item.Result.Properties["name"][0].ToString();
string username = item.Result.Properties["sAMAccountName"][0].ToString();
string distinguishedName = item.Result.Properties["distinguishedName"][0].ToString();
byte[] objectSid = (byte[])item.Result.Properties["objectSid"][0];
string objectSidSDDL = ADInterop.ConvertBytesToSDDLString(objectSid);
List<string> groups = null;
ResultPropertyValueCollection displayNameProp = item.Result.Properties["displayName"];
string displayName = username;
if (displayNameProp.Count > 0)
displayName = displayNameProp[0].ToString();
string surname = null;
ResultPropertyValueCollection surnameProp = item.Result.Properties["sn"];
if (surnameProp.Count > 0)
surname = surnameProp[0].ToString();
string givenName = null;
ResultPropertyValueCollection givenNameProp = item.Result.Properties["givenName"];
if (givenNameProp.Count > 0)
givenName = givenNameProp[0].ToString();
string email = null;
ResultPropertyValueCollection emailProp = item.Result.Properties["mail"];
if (emailProp.Count > 0)
email = emailProp[0].ToString();
string phone = null;
ResultPropertyValueCollection phoneProp = item.Result.Properties["telephoneNumber"];
if (phoneProp.Count > 0)
phone = phoneProp[0].ToString();
// Don't load Groups when doing a quick search
if (!Quick)
{
int primaryGroupID = (int)item.Result.Properties["primaryGroupID"][0];
string primaryGroupSid = ADInterop.ConvertBytesToSDDLString(ADInterop.BuildPrimaryGroupSid(objectSid, primaryGroupID));
var groupDistinguishedNames = item.Result.Properties["memberOf"].Cast<string>().ToList();
groupDistinguishedNames.Add(ADGroupCache.GetGroupsDistinguishedNameForSecurityIdentifier(primaryGroupSid));
groups = ADGroupCache.GetGroups(groupDistinguishedNames).ToList();
}
// Additional Properties
Dictionary<string, object[]> additionalProperties = new Dictionary<string, object[]>();
if (AdditionalProperties != null && AdditionalProperties.Length > 0)
foreach (string propertyName in AdditionalProperties)
{
var property = item.Result.Properties[propertyName];
var propertyValues = new List<object>();
for (int index = 0; index < property.Count; index++)
propertyValues.Add(property[index]);
additionalProperties.Add(propertyName, propertyValues.ToArray());
}
return new ActiveDirectoryUserAccount
{
Domain = item.Domain.NetBiosName,
Name = name,
Surname = surname,
GivenName = givenName,
Email = email,
Phone = phone,
DistinguishedName = distinguishedName,
SamAccountName = username,
DisplayName = displayName,
SecurityIdentifier = objectSidSDDL,
Groups = groups,
Path = item.Result.Path,
LoadedProperties = additionalProperties
};
var domain = Context.GetDomainFromId(Id);
return domain.GetAvailableDomainController().RetrieveADMachineAccount(Id, UUIDNetbootGUID, MacAddressNetbootGUID, AdditionalProperties);
}
#endregion
#region Groups
private static readonly string[] GroupLoadProperties = { "name", "distinguishedName", "cn", "sAMAccountName", "objectSid", "memberOf" };
public static ActiveDirectoryGroup RetrieveGroup(string Id)
public static ADGroup RetrieveADGroup(string Id)
{
const string ldapFilter = "(&(objectCategory=Group)(objectSid={0}))";
return SearchBySamAccountName(Id, ldapFilter, GroupLoadProperties).Select(result => result.AsGroup()).FirstOrDefault();
var domain = Context.GetDomainFromId(Id);
return domain.GetAvailableDomainController().RetrieveADGroup(Id);
}
public static ActiveDirectoryGroup RetrieveGroupWithDistinguishedName(string DistinguishedName)
public static ADGroup RetrieveADGroupByDistinguishedName(string DistinguishedName)
{
ActiveDirectoryDomain domain;
using (var groupEntry = ADInterop.RetrieveDirectoryEntry(DistinguishedName, out domain))
{
if (groupEntry == null)
return null;
return groupEntry.AsGroup(domain);
}
var domain = Context.GetDomainFromDistinguishedName(DistinguishedName);
return domain.GetAvailableDomainController().RetrieveADGroupByDistinguishedName(DistinguishedName);
}
public static ActiveDirectoryGroup RetrieveGroupWithSecurityIdentifier(string SecurityIdentifier)
public static ADGroup RetrieveADGroupWithSecurityIdentifier(SecurityIdentifier SecurityIdentifier)
{
const int resultLimit = 1;
if (string.IsNullOrWhiteSpace(SecurityIdentifier))
throw new ArgumentNullException("SecurityIdentifier");
var sidBytes = ADInterop.ConvertSDDLStringToBytes(SecurityIdentifier);
var sidBinaryString = ADInterop.ConvertBytesToBinarySidString(sidBytes);
string ldapFilter = string.Format("(&(objectCategory=Group)(objectSid={0}))", sidBinaryString);
return ADInterop.SearchAll(ldapFilter, resultLimit, null).Select(result => result.AsGroup()).FirstOrDefault();
var domain = Context.GetDomainFromSecurityIdentifier(SecurityIdentifier);
return domain.GetAvailableDomainController().RetrieveADGroupWithSecurityIdentifier(SecurityIdentifier);
}
public static IEnumerable<ActiveDirectoryGroup> SearchGroups(string Term)
{
const int resultLimit = 30; // Default Search Limit
public static IEnumerable<ADGroup> SearchADGroups(string Term, int? ResultLimit = ActiveDirectory.DefaultSearchResultLimit)
{
if (string.IsNullOrWhiteSpace(Term))
throw new ArgumentNullException("Term");
// Apply Domain Restriction
ActiveDirectoryDomain searchDomain = null;
Term = ApplySearchTermDomainRestriction(Term, out searchDomain);
ADDomain searchDomain;
var term = RelevantSearchTerm(Term, out searchDomain);
if (string.IsNullOrWhiteSpace(Term))
return Enumerable.Empty<ActiveDirectoryGroup>();
if (string.IsNullOrWhiteSpace(term))
return Enumerable.Empty<ADGroup>();
string ldapFilter = string.Format("(&(objectCategory=Group)(|(sAMAccountName=*{0}*)(name=*{0}*)(cn=*{0}*)))", ADInterop.EscapeLdapQuery(Term));
var ldapFilter= string.Format(ADGroup.LdapSearchFilterTemplate, ADHelpers.EscapeLdapQuery(term));
IEnumerable<ActiveDirectorySearchResult> searchResults;
if (searchDomain == null)
searchResults = ADInterop.SearchScope(ldapFilter, resultLimit, GroupLoadProperties);
IEnumerable<ADSearchResult> searchResults;
if (searchDomain != null)
searchResults = searchDomain.SearchScope(ldapFilter, ADGroup.LoadProperties, ResultLimit);
else
searchResults = ADInterop.SearchScope(searchDomain, ldapFilter, resultLimit, GroupLoadProperties);
searchResults = Context.SearchScope(ldapFilter, ADGroup.LoadProperties, ResultLimit);
return searchResults.Select(result => result.AsGroup());
return searchResults.Select(result => result.AsADGroup());
}
private static ActiveDirectoryGroup AsGroup(this ActiveDirectorySearchResult item)
{
var name = (string)item.Result.Properties["name"][0];
var distinguishedName = (string)item.Result.Properties["distinguishedName"][0];
var cn = (string)item.Result.Properties["cn"][0];
var sAMAccountName = (string)item.Result.Properties["sAMAccountName"][0];
var objectSid = ADInterop.ConvertBytesToSDDLString((byte[])item.Result.Properties["objectSid"][0]);
var memberOf = item.Result.Properties["memberOf"].Cast<string>().ToList();
#endregion
return new ActiveDirectoryGroup()
{
Domain = item.Domain.NetBiosName,
Name = name,
DistinguishedName = distinguishedName,
CommonName = cn,
SamAccountName = sAMAccountName,
SecurityIdentifier = objectSid,
MemberOf = memberOf
};
}
private static ActiveDirectoryGroup AsGroup(this DirectoryEntry item, ActiveDirectoryDomain Domain)
#region Organisational Units
public static IEnumerable<Tuple<ADDomain, List<ADOrganisationalUnit>>> RetrieveADOrganisationalUnitStructure()
{
var name = (string)item.Properties["name"][0];
var distinguishedName = (string)item.Properties["distinguishedName"][0];
var cn = (string)item.Properties["cn"][0];
var sAMAccountName = (string)item.Properties["sAMAccountName"][0];
var objectSid = ADInterop.ConvertBytesToSDDLString((byte[])item.Properties["objectSid"][0]);
var memberOf = item.Properties["memberOf"].Cast<string>().ToList();
return new ActiveDirectoryGroup()
{
Domain = Domain.NetBiosName,
Name = name,
DistinguishedName = distinguishedName,
CommonName = cn,
SamAccountName = sAMAccountName,
SecurityIdentifier = objectSid,
MemberOf = memberOf
};
return Context.Domains
.Select(d => d.GetAvailableDomainController())
.Select(dc => Tuple.Create(dc.Domain, dc.RetrieveADOrganisationalUnitStructure()));
}
#endregion
#region Object
private static readonly string[] ObjectLoadProperties = { "objectCategory" };
private static readonly string[] ObjectLoadPropertiesAll = ObjectLoadProperties.Concat(UserLoadProperties).Concat(MachineLoadProperties).Concat(GroupLoadProperties).Distinct().ToArray();
public static IActiveDirectoryObject RetrieveObject(string Id, bool Quick)
#region Objects
public static IADObject RetrieveADObject(string Id, bool Quick)
{
const string ldapFilter = "(&(|(objectCategory=Person)(objectCategory=Computer)(objectCategory=Group))(sAMAccountName={0}))";
return SearchBySamAccountName(Id, ldapFilter, ObjectLoadPropertiesAll)
.Select<ActiveDirectorySearchResult, IActiveDirectoryObject>(result =>
{
var objectCategory = (string)result.Result.Properties["objectCategory"][0];
objectCategory = objectCategory.Substring(0, objectCategory.IndexOf(',')).ToLower();
switch (objectCategory)
{
case "cn=person":
return result.AsUserAccount(Quick, null);
case "cn=computer":
return result.AsMachineAccount(null);
case "cn=group":
return result.AsGroup();
default:
throw new InvalidOperationException("Unexpected objectCategory");
}
}).FirstOrDefault();
var domain = Context.GetDomainFromId(Id);
return domain.GetAvailableDomainController().RetrieveADObject(Id, Quick);
}
#endregion
#region Organisation Units
#region Actions
public static List<ActiveDirectoryOrganisationalUnit> RetrieveOrganisationalUnitStructure(ActiveDirectoryDomain Domain)
public static string OfflineDomainJoinProvision(string ComputerSamAccountName, string OrganisationalUnit, ref ADMachineAccount MachineAccount, out string DiagnosticInformation)
{
using (DirectoryEntry domainRoot = ADInterop.RetrieveDirectoryEntry(Domain.DistinguishedName, out Domain))
{
return ActiveDirectory.RetrieveOrganisationalUnitStructureInternal(Domain, domainRoot);
}
}
private static List<ActiveDirectoryOrganisationalUnit> RetrieveOrganisationalUnitStructureInternal(ActiveDirectoryDomain Domain, DirectoryEntry Container)
{
Dictionary<string, List<ActiveDirectoryOrganisationalUnit>> resultTree = new Dictionary<string, List<ActiveDirectoryOrganisationalUnit>>();
var domain = Context.GetDomainFromDistinguishedName(OrganisationalUnit);
var writableDomainController = domain.GetAvailableDomainController(RequireWritable: true);
using (DirectorySearcher adSearcher = new DirectorySearcher(Container, "(objectCategory=organizationalUnit)", new string[]
{
"name",
"distinguishedName"
}, SearchScope.Subtree))
{
adSearcher.PageSize = 500;
using (SearchResultCollection adResults = adSearcher.FindAll())
{
resultTree = adResults.Cast<SearchResult>().Select(adResult =>
{
string i = adResult.Properties["name"][0].ToString();
string dn = adResult.Properties["distinguishedName"][0].ToString();
return new ActiveDirectoryOrganisationalUnit
{
Domain = Domain.NetBiosName,
Name = i,
DistinguishedName = dn,
};
}).GroupBy(ou => ou.DistinguishedName.Substring(ou.DistinguishedName.IndexOf(',') + 1)).ToDictionary(g => g.Key, g => g.ToList());
}
}
// Build Tree
var results = resultTree[Domain.DistinguishedName];
foreach (var child in results)
RetrieveOrganisationalUnitStructureChildrenInternal(child, resultTree);
return results;
}
private static void RetrieveOrganisationalUnitStructureChildrenInternal(ActiveDirectoryOrganisationalUnit OrganisationalUnit, Dictionary<string, List<ActiveDirectoryOrganisationalUnit>> ResultTree)
{
List<ActiveDirectoryOrganisationalUnit> children;
if (ResultTree.TryGetValue(OrganisationalUnit.DistinguishedName, out children))
{
foreach (var child in children)
RetrieveOrganisationalUnitStructureChildrenInternal(child, ResultTree);
OrganisationalUnit.Children = children;
}
return writableDomainController.OfflineDomainJoinProvision(ComputerSamAccountName, OrganisationalUnit, ref MachineAccount, out DiagnosticInformation);
}
#endregion
#region Helpers
private static IEnumerable<ActiveDirectorySearchResult> SearchBySamAccountName(string Id, string LdapFilterTemplate, string[] LoadProperties)
private static string RelevantSearchTerm(string Term, out ADDomain Domain)
{
var splitId = UserExtensions.SplitUserId(Id);
var ldapFilter = string.Format(LdapFilterTemplate, splitId.Item2);
var domains = ADInterop.GetDomainFromId(Id);
Domain = null;
return ADInterop.SearchAll(domains, ldapFilter, SingleSearchResult, LoadProperties);
}
private static string ApplySearchTermDomainRestriction(string Term, out ActiveDirectoryDomain Domain)
{
if (string.IsNullOrWhiteSpace(Term))
throw new ArgumentNullException("Term");
return null;
var domainIndex = Term.IndexOf('\\');
if (domainIndex >= 0)
var term = Term.Trim();
var domainSeperatorIndex = term.IndexOf('\\');
if (domainSeperatorIndex >= 0)
{
var domain = Term.Substring(0, domainIndex);
// Domain Search Restriction
if (!ADInterop.TryGetDomainByNetBiosName(domain, out Domain))
return null; // Domain not found - invalid search
if (Term.Length > (domainIndex + 1))
return Term.Substring(domainIndex + 1);
if (term.Length > domainSeperatorIndex + 1)
{
var netbiosName = term.Substring(0, domainSeperatorIndex);
if (Context.TryGetDomainByNetBiosName(netbiosName, out Domain))
{
return term.Substring(domainSeperatorIndex + 1);
}
else
{
return null; // Unknown Domain
}
}
else
return null; // Domain only, no Term
}
else
{
Domain = null;
return Term;
{
return null; // No term to search, only Domain
}
}
return term;
}
#endregion
}
}
}
@@ -0,0 +1,308 @@
using Disco.Data.Repository;
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
namespace Disco.Services.Interop.ActiveDirectory
{
public class ActiveDirectoryContext
{
public ADSite Site { get; private set; }
public ADDomain PrimaryDomain { get; private set; }
public List<ADDomain> Domains { get; private set; }
public List<string> ForestServers
{
get
{
return ADDiscoverForestServers.LoadForestServersBlocking();
}
}
private bool _SearchAllForestServers { get; set; }
public bool SearchAllForestServers
{
get
{
var fs = ADDiscoverForestServers.ForestServers;
if (fs != null && fs.Count > ActiveDirectory.MaxForestServerSearch)
return false; // Never
return _SearchAllForestServers;
}
}
#region Contructor/Initializing
internal ActiveDirectoryContext(DiscoDataContext Database)
{
Initialize(Database);
}
private void Initialize(DiscoDataContext Database)
{
// Search Entire Forest (default: true)
this._SearchAllForestServers = Database.DiscoConfiguration.ActiveDirectory.SearchAllForestServers ?? true;
// Determine Site
var computerSite = ActiveDirectorySite.GetComputerSite();
this.Site = new ADSite(this, computerSite);
// Determine Domains
var computerDomain = Domain.GetComputerDomain();
this.Domains = computerDomain.Forest.Domains
.Cast<Domain>()
.Select(d => new ADDomain(this, d))
.ToList();
this.PrimaryDomain = this.Domains.Where(d => d.Name == computerDomain.Name).First();
// Determine Search Scope Containers
ReinitializeSearchContainers(Database.DiscoConfiguration.ActiveDirectory.SearchContainers);
// Determine Domain Controllers
var siteDomainControllers = computerSite.Servers
.OfType<DomainController>()
.Where(dc => dc.IsReachable())
.Select(dc => new ADDomainController(this, dc, GetDomainByName(dc.Domain.Name), IsSiteServer: true, IsWritable: false));
Site.UpdateDomainControllers(siteDomainControllers);
this.Domains.ForEach(domain => domain.UpdateDomainControllers(siteDomainControllers.Where(dc => dc.Domain == domain)));
}
#endregion
#region Domain Getters
public bool TryGetDomainFromDistinguishedName(string DistinguishedName, out ADDomain Domain)
{
// Find closest match
Domain = this.Domains.Where(d => DistinguishedName.EndsWith(d.DistinguishedName, StringComparison.OrdinalIgnoreCase))
.OrderByDescending(d => d.DistinguishedName.Length).FirstOrDefault();
return (Domain != null);
}
public ADDomain GetDomainFromDistinguishedName(string DistinguishedName)
{
ADDomain domain;
if (!TryGetDomainFromDistinguishedName(DistinguishedName, out domain))
throw new ArgumentException(string.Format("The distinguished name is from an unknown domain: [{0}]", DistinguishedName), "DistinguishedName");
return domain;
}
public bool TryGetDomainByNetBiosName(string NetBiosName, out ADDomain Domain)
{
Domain = this.Domains.FirstOrDefault(d => d.NetBiosName.Equals(NetBiosName, StringComparison.OrdinalIgnoreCase));
return (Domain != null);
}
public ADDomain GetDomainByNetBiosName(string NetBiosName)
{
ADDomain domain;
if (!TryGetDomainByNetBiosName(NetBiosName, out domain))
throw new ArgumentException(string.Format("The domain for specified NetBios name is unknown [{0}]", NetBiosName), "NetBiosName");
return domain;
}
public bool TryGetDomainByName(string Name, out ADDomain Domain)
{
Domain = this.Domains.FirstOrDefault(d => d.Name.Equals(Name, StringComparison.OrdinalIgnoreCase));
return (Domain != null);
}
public ADDomain GetDomainByName(string Name)
{
ADDomain domain;
if (!TryGetDomainByName(Name, out domain))
throw new ArgumentException(string.Format("The domain for specified DNS name is unknown [{0}]", Name), "Name");
return domain;
}
public bool TryGetDomainFromSecurityIdentifier(SecurityIdentifier SecurityIdentifier, out ADDomain Domain)
{
Domain = this.Domains.FirstOrDefault(d => d.SecurityIdentifier.IsEqualDomainSid(SecurityIdentifier));
return (Domain != null);
}
public ADDomain GetDomainFromSecurityIdentifier(SecurityIdentifier SecurityIdentifier)
{
ADDomain domain;
if (!TryGetDomainFromSecurityIdentifier(SecurityIdentifier, out domain))
throw new ArgumentException(string.Format("The domain for specified Security Identifier is unknown [{0}]", SecurityIdentifier.ToString()), "SecurityIdentifier");
return domain;
}
public bool TryGetDomainFromId(string Id, out ADDomain Domain)
{
if (string.IsNullOrWhiteSpace(Id))
throw new ArgumentNullException("Id");
var idSplit = UserExtensions.SplitUserId(Id);
if (string.IsNullOrWhiteSpace(idSplit.Item1))
throw new ArgumentException(string.Format("The Id must include the Domain [{0}]", Id), "Id");
return TryGetDomainByNetBiosName(idSplit.Item1, out Domain);
}
public ADDomain GetDomainFromId(string Id)
{
if (string.IsNullOrWhiteSpace(Id))
throw new ArgumentNullException("Id");
var idSplit = UserExtensions.SplitUserId(Id);
if (string.IsNullOrWhiteSpace(idSplit.Item1))
throw new ArgumentException(string.Format("The Id must include the Domain [{0}]", Id), "Id");
return GetDomainByNetBiosName(idSplit.Item1);
}
#endregion
public ADDirectoryEntry RetrieveDirectoryEntry(string DistinguishedName, string[] LoadProperties = null)
{
if (string.IsNullOrWhiteSpace(DistinguishedName))
throw new ArgumentNullException("DistinguishedName");
var d = GetDomainFromDistinguishedName(DistinguishedName);
var dc = d.GetAvailableDomainController();
return dc.RetrieveDirectoryEntry(DistinguishedName, LoadProperties);
}
#region Searching
public IEnumerable<ADSearchResult> SearchEntireForest(string LdapFilter, string[] LoadProperties, int? ResultLimit = null)
{
var queries = this.Domains.Select(d => Tuple.Create(d, d.DistinguishedName));
return SearchInternal(queries, LdapFilter, LoadProperties, ResultLimit);
}
public IEnumerable<ADSearchResult> SearchScope(string LdapFilter, string[] LoadProperties, int? ResultLimit = null)
{
var queries = this.Domains.SelectMany(
d => d.SearchContainers ?? new List<string>() { d.DistinguishedName },
(d, scope) => Tuple.Create(d, scope));
return SearchInternal(queries, LdapFilter, LoadProperties, ResultLimit);
}
internal IEnumerable<ADSearchResult> SearchInternal(IEnumerable<Tuple<ADDomain, string>> Queries, string LdapFilter, string[] LoadProperties, int? ResultLimit)
{
var queries = Queries.ToList();
switch (queries.Count)
{
case 0: // Nothing Queried
return Enumerable.Empty<ADSearchResult>();
case 1: // Single-search
var querySingle = queries[0];
return querySingle.Item1.SearchInternal(querySingle.Item2, LdapFilter, LoadProperties, ResultLimit);
default: // Multi-search - Parallelize
var queryTasks = queries.Select(query =>
Task<IEnumerable<ADSearchResult>>.Factory.StartNew(() =>
query.Item1.SearchInternal(query.Item2, LdapFilter, LoadProperties, ResultLimit))).ToArray();
// Block
Task.WaitAll(queryTasks);
var results = queryTasks.SelectMany(t => t.Result);
if (ResultLimit.HasValue)
results = results.Take(ResultLimit.Value);
return results;
}
}
#endregion
#region Configuration
public bool UpdateSearchAllForestServers(DiscoDataContext Database, bool SearchAllForestServers)
{
if (SearchAllForestServers == false)
{
Database.DiscoConfiguration.ActiveDirectory.SearchAllForestServers = false;
this._SearchAllForestServers = false;
return true;
}
else
{
var forestServers = ADDiscoverForestServers.LoadForestServersBlocking();
if (forestServers.Count <= ActiveDirectory.MaxForestServerSearch)
{
Database.DiscoConfiguration.ActiveDirectory.SearchAllForestServers = true;
this._SearchAllForestServers = true;
return true;
}
else
{
Database.DiscoConfiguration.ActiveDirectory.SearchAllForestServers = false;
this._SearchAllForestServers = false;
return false;
}
}
}
public void UpdateSearchContainers(DiscoDataContext Database, IEnumerable<string> Containers)
{
Dictionary<string, List<string>> searchContainers = null;
if (Containers != null)
{
searchContainers = Containers
.Where(c => !string.IsNullOrWhiteSpace(c))
.Distinct()
.Select(c =>
{
ADDomain d;
if (TryGetDomainFromDistinguishedName(c, out d))
return Tuple.Create(d, c);
else
return null;
}).Where(i => i != null)
.GroupBy(i => i.Item1)
.ToDictionary(g => g.Key.Name.ToLower(), g => g.Select(i => i.Item2).ToList());
}
if (searchContainers == null || searchContainers.Count == 0)
{
Database.DiscoConfiguration.ActiveDirectory.SearchContainers = null;
ReinitializeSearchContainers(null);
}
else
{
Database.DiscoConfiguration.ActiveDirectory.SearchContainers = searchContainers;
ReinitializeSearchContainers(searchContainers);
}
}
private void ReinitializeSearchContainers(Dictionary<string, List<string>> Containers)
{
if (Containers == null)
{
// No search restrictions (search entire domain)
foreach (var domain in this.Domains)
domain.UpdateSearchEntireDomain();
}
else
{
// Restrict search containers
var searchContainerDomains = Containers.Join(this.Domains, ok => ok.Key, ik => ik.Name, (o, i) => Tuple.Create(o, i), StringComparer.OrdinalIgnoreCase);
foreach (var domainContainers in searchContainerDomains)
domainContainers.Item2.UpdateSearchContainers(domainContainers.Item1.Value);
// Ignore domains without configured containers
var unconfiguredContainers = this.Domains.Except(searchContainerDomains.Select(sc => sc.Item2));
foreach (var domain in unconfiguredContainers)
domain.UpdateSearchContainers(new List<string>());
}
}
#endregion
}
}
@@ -1,7 +1,4 @@
using Disco.Models.Interop.ActiveDirectory;
using Disco.Models.Repository;
using Disco.Services.Interop.ActiveDirectory.Internal;
using System;
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
@@ -16,23 +13,7 @@ namespace Disco.Services.Interop.ActiveDirectory
{
#region Domain/Directory Extensions
public static DomainController RetrieveWritableDomainController(this ActiveDirectoryDomain domain)
{
return ADInterop.RetrieveWritableDomainController(domain);
}
public static IEnumerable<DomainController> RetrieveReachableDomainControllers(this ActiveDirectorySite site, ActiveDirectoryDomain domain)
{
return site.Servers.OfType<DomainController>().Where(dc => dc.Reachable() && dc.Domain.Name.Equals(domain.DnsName));
}
public static IEnumerable<DomainController> RetrieveReachableDomainControllers(this ActiveDirectoryDomain domain)
{
var d = Domain.GetDomain(new DirectoryContext(DirectoryContextType.Domain, domain.DnsName));
return d.FindAllDomainControllers().OfType<DomainController>().Where(dc => dc.Reachable());
}
public static bool Reachable(this DirectoryServer ds)
public static bool IsReachable(this DirectoryServer ds)
{
using (Ping p = new Ping())
{
@@ -41,351 +22,82 @@ namespace Disco.Services.Interop.ActiveDirectory
}
}
public static string GetFriendlyOrganisationalUnitName(this ActiveDirectoryDomain domain, string DistinguishedName)
public static IEnumerable<DomainController> WhereReachable(this DomainControllerCollection domainControllers)
{
if (!DistinguishedName.EndsWith(domain.DistinguishedName, StringComparison.InvariantCultureIgnoreCase))
throw new ArgumentException(string.Format("The Distinguished Name [{0}] doesn't exist within this domain [{1}]", DistinguishedName, domain.DistinguishedName));
StringBuilder name = new StringBuilder();
name.Append('[').Append(domain.NetBiosName).Append(']');
var subDN = DistinguishedName.Substring(0, DistinguishedName.Length - domain.DistinguishedName.Length);
var subDNComponents = subDN.Split(',');
subDNComponents
.Where(c => !string.IsNullOrWhiteSpace(c))
.Reverse()
.Select(c => c.Substring(c.IndexOf('=') + 1))
.ToList()
.ForEach(c => name.Append(" > ").Append(c));
return name.ToString();
return domainControllers.Cast<DomainController>().Where(dc => dc.IsReachable());
}
public static string GetDefaultComputerContainer(this ActiveDirectoryDomain domain)
public static IEnumerable<ADDomainController> WhereReachable(this IEnumerable<ADDomainController> domainControllers)
{
return string.Format("CN=Computers,{0}", domain.DistinguishedName);
return domainControllers.Where(dc => dc.DomainController.IsReachable());
}
// Directory Entry Properties (Generic Helpers)
public static T Value<T>(this PropertyCollection properties, string PropertyName)
{
var p = properties.Values<T>(PropertyName);
return p.FirstOrDefault();
}
public static IEnumerable<T> Values<T>(this PropertyCollection properties, string PropertyName)
{
var p = properties[PropertyName];
return p.OfType<T>();
}
#endregion
#region User Account Extensions
public static object GetPropertyValue(this ActiveDirectoryUserAccount account, string PropertyName, int Index = 0)
#region ADObject Builders
// User Accounts
public static ADUserAccount AsADUserAccount(this ADSearchResult SearchResult, bool Quick, string[] AdditionalProperties)
{
switch (PropertyName.ToLower())
{
case "name":
return account.Name;
case "samaccountname":
return account.SamAccountName;
case "distinguishedname":
return account.DistinguishedName;
case "objectsid":
return account.SecurityIdentifier;
case "sn":
return account.Surname;
case "givenname":
return account.GivenName;
case "mail":
return account.Email;
case "telephonenumber":
return account.Phone;
default:
object[] adProperty;
if (account.LoadedProperties.TryGetValue(PropertyName, out adProperty) && Index <= adProperty.Length)
return adProperty[Index];
else
return null;
}
return ADUserAccount.FromSearchResult(SearchResult, Quick, AdditionalProperties);
}
#endregion
#region Machine Account Extensions
public static void DeleteAccount(this ActiveDirectoryMachineAccount account, DomainController DomainController)
public static IEnumerable<ADUserAccount> AsADUserAccounts(this IEnumerable<ADSearchResult> SearchResults, bool Quick, string[] AdditionalProperties)
{
if (account.IsCriticalSystemObject)
throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", account.DistinguishedName));
using (DirectoryEntry deAccount = DomainController.RetrieveDirectoryEntry(account.DistinguishedName))
{
deAccount.DeleteObjectRecursive();
}
}
public static void DeleteAccount(this ActiveDirectoryMachineAccount account)
{
var domain = account.GetDomain();
using (var domainController = domain.RetrieveWritableDomainController())
{
account.DeleteAccount(domainController);
}
return SearchResults.Select(sr => ADUserAccount.FromSearchResult(sr, Quick, AdditionalProperties));
}
private static void SetNetbootGUID(this ActiveDirectoryMachineAccount account, DomainController DomainController, System.Guid updatedNetbootGUID)
// Machine Accounts
public static ADMachineAccount AsADMachineAccount(this ADSearchResult SearchResult, string[] AdditionalProperties)
{
if (account.IsCriticalSystemObject)
throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", account.DistinguishedName));
using (DirectoryEntry deAccount = DomainController.RetrieveDirectoryEntry(account.DistinguishedName))
{
PropertyValueCollection netbootGUIDProp = deAccount.Properties["netbootGUID"];
bool flag = netbootGUIDProp.Count > 0;
if (flag)
{
netbootGUIDProp.Clear();
}
netbootGUIDProp.Add(updatedNetbootGUID.ToByteArray());
deAccount.CommitChanges();
}
return ADMachineAccount.FromSearchResult(SearchResult, AdditionalProperties);
}
public static void SetDescription(this ActiveDirectoryMachineAccount account, DomainController DomainController, string Description)
public static IEnumerable<ADMachineAccount> AsADMachineAccounts(this IEnumerable<ADSearchResult> SearchResults, string[] AdditionalProperties)
{
using (DirectoryEntry deAccount = DomainController.RetrieveDirectoryEntry(account.DistinguishedName))
{
PropertyValueCollection descriptionProp = deAccount.Properties["description"];
if (descriptionProp.Count > 0)
{
descriptionProp.Clear();
}
if (!string.IsNullOrEmpty(Description))
{
descriptionProp.Add(Description);
}
deAccount.CommitChanges();
}
}
public static void SetDescription(this ActiveDirectoryMachineAccount account, string Description)
{
var domain = account.GetDomain();
using (var domainController = domain.RetrieveWritableDomainController())
{
account.SetDescription(domainController, Description);
}
return SearchResults.Select(sr => ADMachineAccount.FromSearchResult(sr, AdditionalProperties));
}
public static void SetDescription(this ActiveDirectoryMachineAccount account, DomainController DomainController, Device Device)
// Groups
public static ADGroup AsADGroup(this ADSearchResult SearchResult)
{
System.Text.StringBuilder descriptionBuilder = new System.Text.StringBuilder();
if (Device.AssignedUserId != null)
{
descriptionBuilder.Append(Device.AssignedUser.UserId).Append(" (").Append(Device.AssignedUser.DisplayName).Append("); ");
}
if (Device.DeviceModelId.HasValue)
{
descriptionBuilder.Append(Device.DeviceModel.Description).Append("; ");
}
descriptionBuilder.Append(Device.DeviceProfile.Description).Append(";");
string description = descriptionBuilder.ToString().Trim();
if (description.Length > 1024)
description = description.Substring(0, 1024);
account.SetDescription(DomainController, description);
return ADGroup.FromSearchResult(SearchResult);
}
public static void SetDescription(this ActiveDirectoryMachineAccount account, Device Device)
public static IEnumerable<ADGroup> AsADGroups(this IEnumerable<ADSearchResult> SearchResults)
{
var domain = account.GetDomain();
using (var domainController = domain.RetrieveWritableDomainController())
{
account.SetDescription(domainController, Device);
}
return SearchResults.Select(sr => ADGroup.FromSearchResult(sr));
}
public static ADGroup AsADGroup(this ADDirectoryEntry DirectoryEntry)
{
return ADGroup.FromDirectoryEntry(DirectoryEntry);
}
public static IEnumerable<ADGroup> AsADGroups(this IEnumerable<ADDirectoryEntry> DirectoryEntries)
{
return DirectoryEntries.Select(de => ADGroup.FromDirectoryEntry(de));
}
public static void DisableAccount(this ActiveDirectoryMachineAccount account, DomainController DomainController)
// Organisational Units
public static ADOrganisationalUnit AsADOrganisationalUnit(this ADSearchResult SearchResult)
{
if (account.IsCriticalSystemObject)
throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", account.DistinguishedName));
using (DirectoryEntry deAccount = DomainController.RetrieveDirectoryEntry(account.DistinguishedName))
{
int accountControl = (int)deAccount.Properties["userAccountControl"][0];
int updatedAccountControl = (accountControl | 2);
if (accountControl != updatedAccountControl)
{
deAccount.Properties["userAccountControl"][0] = updatedAccountControl;
deAccount.CommitChanges();
}
}
return ADOrganisationalUnit.FromSearchResult(SearchResult);
}
public static void DisableAccount(this ActiveDirectoryMachineAccount account)
public static IEnumerable<ADOrganisationalUnit> AsADOrganisationalUnit(this IEnumerable<ADSearchResult> SearchResults)
{
var domain = account.GetDomain();
using (var domainController = domain.RetrieveWritableDomainController())
{
account.DisableAccount(domainController);
}
}
public static void EnableAccount(this ActiveDirectoryMachineAccount account, DomainController DomainController)
{
if (account.IsCriticalSystemObject)
throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", account.DistinguishedName));
using (DirectoryEntry deAccount = DomainController.RetrieveDirectoryEntry(account.DistinguishedName))
{
int accountControl = (int)deAccount.Properties["userAccountControl"][0];
if ((accountControl & 2) == 2)
{
int updatedAccountControl = (accountControl ^ 2);
deAccount.Properties["userAccountControl"][0] = updatedAccountControl;
deAccount.CommitChanges();
}
}
}
public static void EnableAccount(this ActiveDirectoryMachineAccount account)
{
var domain = account.GetDomain();
using (var domainController = domain.RetrieveWritableDomainController())
{
account.EnableAccount(domainController);
}
}
public static bool UpdateNetbootGUID(this ActiveDirectoryMachineAccount account, DomainController DomainController, string UUID, string MACAddress)
{
if (account.IsCriticalSystemObject)
throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", account.DistinguishedName));
Guid netbootGUID = Guid.Empty;
if (!string.IsNullOrWhiteSpace(UUID))
{
netbootGUID = ActiveDirectoryExtensions.NetbootGUIDFromUUID(UUID);
}
else if (!string.IsNullOrWhiteSpace(MACAddress))
{
netbootGUID = ActiveDirectoryExtensions.NetbootGUIDFromMACAddress(MACAddress);
}
if (netbootGUID != System.Guid.Empty && netbootGUID != account.NetbootGUID)
{
account.SetNetbootGUID(DomainController, netbootGUID);
return true;
}
else
{
return false;
}
}
public static bool UpdateNetbootGUID(this ActiveDirectoryMachineAccount account, string UUID, string MACAddress)
{
var domain = account.GetDomain();
using (var domainController = domain.RetrieveWritableDomainController())
{
return account.UpdateNetbootGUID(domainController, UUID, MACAddress);
}
}
public static System.Guid NetbootGUIDFromMACAddress(string MACAddress)
{
string strippedMACAddress = MACAddress.Trim().Replace(":", string.Empty).Replace("-", string.Empty);
bool flag = strippedMACAddress.Length == 12;
System.Guid NetbootGUIDFromMACAddress;
if (flag)
{
System.Guid guid = new System.Guid(string.Format("00000000-0000-0000-0000-{0}", strippedMACAddress));
NetbootGUIDFromMACAddress = guid;
}
else
{
NetbootGUIDFromMACAddress = System.Guid.Empty;
}
return NetbootGUIDFromMACAddress;
}
public static System.Guid NetbootGUIDFromUUID(string UUID)
{
System.Guid result = new System.Guid(UUID);
return result;
}
public static object GetPropertyValue(this ActiveDirectoryMachineAccount account, string PropertyName, int Index = 0)
{
switch (PropertyName.ToLower())
{
case "name":
return account.Name;
case "samaccountname":
return account.SamAccountName;
case "distinguishedname":
return account.DistinguishedName;
case "objectsid":
return account.SecurityIdentifier;
case "netbootguid":
return account.NetbootGUID;
default:
object[] adProperty;
if (account.LoadedProperties.TryGetValue(PropertyName, out adProperty) && Index <= adProperty.Length)
return adProperty[Index];
else
return null;
}
}
public static IPStatus PingComputer(this ActiveDirectoryMachineAccount account, int Timeout = 2000)
{
using (var p = new Ping())
{
PingReply reply = p.Send(account.DnsName, Timeout);
return reply.Status;
}
}
public static void MoveOrganisationalUnit(this ActiveDirectoryMachineAccount account, DomainController DomainController, string NewOrganisationUnit)
{
if (account.IsCriticalSystemObject)
throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", account.DistinguishedName));
var parentDistinguishedName = account.ParentDistinguishedName();
if (parentDistinguishedName != null && !parentDistinguishedName.Equals(NewOrganisationUnit, StringComparison.InvariantCultureIgnoreCase))
{
var domain = account.GetDomain();
// If no OU provided, place in default Computers container
if (string.IsNullOrWhiteSpace(NewOrganisationUnit))
NewOrganisationUnit = domain.GetDefaultComputerContainer();
if (!NewOrganisationUnit.EndsWith(domain.DistinguishedName, StringComparison.InvariantCultureIgnoreCase))
throw new InvalidOperationException(string.Format("Unable to move AD Account from one domain [{0}] to another [{1}].", account.DistinguishedName, NewOrganisationUnit));
using (DirectoryEntry ou = DomainController.RetrieveDirectoryEntry(NewOrganisationUnit))
{
using (DirectoryEntry i = DomainController.RetrieveDirectoryEntry(account.DistinguishedName))
{
i.UsePropertyCache = false;
i.MoveTo(ou);
}
}
}
}
public static string ParentDistinguishedName(this ActiveDirectoryMachineAccount account)
{
// Determine Parent
if (!string.IsNullOrWhiteSpace(account.DistinguishedName))
return account.DistinguishedName.Substring(account.DistinguishedName.IndexOf(",") + 1);
else
return null;
}
public static ActiveDirectoryDomain GetDomain(this ActiveDirectoryMachineAccount account)
{
var domain = ActiveDirectory.GetDomainByNetBiosName(account.Domain);
if (domain == null)
throw new InvalidOperationException(string.Format("Unable to find Domain [{0}] for account [{1}]", account.Domain, account.Name));
else
return domain;
return SearchResults.Select(sr => ADOrganisationalUnit.FromSearchResult(sr));
}
#endregion
}
}
@@ -0,0 +1,226 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
namespace Disco.Services.Interop.ActiveDirectory
{
public class ActiveDirectoryGroupCache
{
private ConcurrentDictionary<SecurityIdentifier, Tuple<ADGroup, DateTime>> securityIdentifierCache;
private ConcurrentDictionary<string, Tuple<ADGroup, DateTime>> distinguishedNameCache;
private const long CacheTimeoutTicks = 6000000000; // 10 Minutes
private const int CacheCleanIntervalMinutes = 15;
private DateTime cacheCleanNext;
private object cacheCleanLock = new object();
private Task cacheCleanTask;
public ActiveDirectoryGroupCache()
{
this.securityIdentifierCache = new ConcurrentDictionary<SecurityIdentifier, Tuple<ADGroup, DateTime>>();
this.distinguishedNameCache = new ConcurrentDictionary<string, Tuple<ADGroup, DateTime>>(StringComparer.OrdinalIgnoreCase);
cacheCleanNext = DateTime.Now.AddMinutes(CacheCleanIntervalMinutes);
}
public ADGroup GetGroup(string DistinguishedName)
{
// Check Cache
Tuple<ADGroup, DateTime> groupRecord = TryDistinguishedNameCache(DistinguishedName);
if (groupRecord == null)
{
// Load from AD
var group = ActiveDirectory.RetrieveADGroupByDistinguishedName(DistinguishedName);
SetValue(group);
return group;
}
else
{
// Return from Cache
return groupRecord.Item1;
}
}
public ADGroup GetGroup(SecurityIdentifier SecurityIdentifier)
{
// Check Cache
Tuple<ADGroup, DateTime> groupRecord = TrySecurityIdentifierCache(SecurityIdentifier);
if (groupRecord == null)
{
// Load from AD
var group = ActiveDirectory.RetrieveADGroupWithSecurityIdentifier(SecurityIdentifier);
SetValue(group);
return group;
}
else
{
// Return from Cache
return groupRecord.Item1;
}
}
public IEnumerable<ADGroup> GetRecursiveGroups(IEnumerable<string> DistinguishedNames)
{
List<ADGroup> groups = new List<ADGroup>();
foreach (var distinguishedName in DistinguishedNames)
foreach (var group in GetGroupsRecursive(distinguishedName, new Stack<ADGroup>()))
if (!groups.Contains(group))
{
groups.Add(group);
yield return group;
}
}
public IEnumerable<ADGroup> GetRecursiveGroups(string DistinguishedName)
{
foreach (var group in GetGroupsRecursive(DistinguishedName, new Stack<ADGroup>()))
yield return group;
}
private IEnumerable<ADGroup> GetGroupsRecursive(string DistinguishedName, Stack<ADGroup> RecursiveTree)
{
var group = GetGroup(DistinguishedName);
if (group != null && !RecursiveTree.Contains(group))
{
yield return group;
if (group.MemberOf != null)
{
RecursiveTree.Push(group);
foreach (var parentDistinguishedName in group.MemberOf)
foreach (var parentGroup in GetGroupsRecursive(parentDistinguishedName, RecursiveTree))
yield return parentGroup;
RecursiveTree.Pop();
}
}
}
private Tuple<ADGroup, DateTime> TryDistinguishedNameCache(string DistinguishedName)
{
Tuple<ADGroup, DateTime> groupRecord;
if (distinguishedNameCache.TryGetValue(DistinguishedName, out groupRecord))
{
if (groupRecord.Item2 > DateTime.Now)
return groupRecord;
else
{
if (distinguishedNameCache.TryRemove(DistinguishedName, out groupRecord))
securityIdentifierCache.TryRemove(groupRecord.Item1.SecurityIdentifier, out groupRecord);
}
}
return null;
}
private Tuple<ADGroup, DateTime> TrySecurityIdentifierCache(SecurityIdentifier SecurityIdentifier)
{
Tuple<ADGroup, DateTime> groupRecord;
if (securityIdentifierCache.TryGetValue(SecurityIdentifier, out groupRecord))
{
if (groupRecord.Item2 > DateTime.Now)
return groupRecord;
else
{
if (securityIdentifierCache.TryRemove(SecurityIdentifier, out groupRecord))
distinguishedNameCache.TryRemove(groupRecord.Item1.DistinguishedName, out groupRecord);
}
}
return null;
}
private bool SetValue(ADGroup Group)
{
Tuple<ADGroup, DateTime> groupRecord = Tuple.Create(Group, DateTime.Now.AddTicks(CacheTimeoutTicks));
Tuple<ADGroup, DateTime> oldGroupRecord;
var distinguishedName = Group.DistinguishedName;
var securityIdentifier = Group.SecurityIdentifier;
if (distinguishedNameCache.ContainsKey(distinguishedName))
{
if (distinguishedNameCache.TryGetValue(distinguishedName, out oldGroupRecord))
{
distinguishedNameCache.TryUpdate(distinguishedName, groupRecord, oldGroupRecord);
}
}
else
{
distinguishedNameCache.TryAdd(distinguishedName, groupRecord);
}
if (securityIdentifierCache.ContainsKey(securityIdentifier))
{
if (securityIdentifierCache.TryGetValue(securityIdentifier, out oldGroupRecord))
{
securityIdentifierCache.TryUpdate(securityIdentifier, groupRecord, oldGroupRecord);
}
}
else
{
securityIdentifierCache.TryAdd(securityIdentifier, groupRecord);
}
return true;
}
#region Stale Cache Clean
private void EnsureCleanCache()
{
if (cacheCleanTask == null && cacheCleanNext < DateTime.Now)
{
lock (cacheCleanLock)
{
if (cacheCleanTask == null && cacheCleanNext < DateTime.Now)
{
cacheCleanTask = Task.Factory.StartNew(CleanCache);
}
}
}
}
private void CleanCache()
{
DateTime now = DateTime.Now;
// Clean Cache
var dnKeys = distinguishedNameCache.Keys.ToArray();
foreach (var dnKey in dnKeys)
{
Tuple<ADGroup, DateTime> groupRecord;
if (distinguishedNameCache.TryGetValue(dnKey, out groupRecord))
{
if (groupRecord.Item2 <= now)
{
distinguishedNameCache.TryRemove(dnKey, out groupRecord);
}
}
}
// Clean SID Cache
var siKeys = securityIdentifierCache.Keys.ToArray();
foreach (var siKey in siKeys)
{
Tuple<ADGroup, DateTime> groupRecord;
if (securityIdentifierCache.TryGetValue(siKey, out groupRecord))
{
if (groupRecord.Item2 <= now)
{
securityIdentifierCache.TryRemove(siKey, out groupRecord);
}
}
}
// Schedule Next Clean
cacheCleanNext = now.AddMinutes(CacheCleanIntervalMinutes);
cacheCleanTask = null;
}
#endregion
}
}
@@ -0,0 +1,18 @@

using System.Security.Principal;
namespace Disco.Services.Interop.ActiveDirectory
{
public interface IADObject
{
ADDomain Domain { get; }
string DistinguishedName { get; }
SecurityIdentifier SecurityIdentifier { get; }
string Id { get; }
string SamAccountName { get; }
string Name { get; }
string DisplayName { get; }
}
}
@@ -1,226 +0,0 @@
using Disco.Data.Repository;
using Disco.Models.Interop.ActiveDirectory;
using Disco.Services.Tasks;
using Quartz;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace Disco.Services.Interop.ActiveDirectory.Internal
{
public class ADGroupCache : ScheduledTask
{
private static ConcurrentDictionary<string, Tuple<ActiveDirectoryGroup, DateTime>> _SecurityIdentifierCache = new ConcurrentDictionary<string, Tuple<ActiveDirectoryGroup, DateTime>>();
private static ConcurrentDictionary<string, Tuple<ActiveDirectoryGroup, DateTime>> _DistinguishedNameCache = new ConcurrentDictionary<string, Tuple<ActiveDirectoryGroup, DateTime>>();
private const long CacheTimeoutTicks = 6000000000; // 10 Minutes
public static IEnumerable<string> GetGroups(IEnumerable<string> DistinguishedNames)
{
List<ActiveDirectoryGroup> groups = new List<ActiveDirectoryGroup>();
foreach (var distinguishedName in DistinguishedNames)
foreach (var group in GetGroupsRecursive(distinguishedName, new Stack<ActiveDirectoryGroup>()))
if (!groups.Contains(group))
{
groups.Add(group);
yield return group.NetBiosId;
}
}
public static IEnumerable<string> GetGroups(string DistinguishedName)
{
foreach (var group in GetGroupsRecursive(DistinguishedName, new Stack<ActiveDirectoryGroup>()))
yield return group.NetBiosId;
}
public static string GetGroupsDistinguishedNameForSecurityIdentifier(string SecurityIdentifier)
{
var group = GetGroupBySecurityIdentifier(SecurityIdentifier);
if (group == null)
return null;
else
return group.DistinguishedName;
}
private static IEnumerable<ActiveDirectoryGroup> GetGroupsRecursive(string DistinguishedName, Stack<ActiveDirectoryGroup> RecursiveTree)
{
var group = GetGroup(DistinguishedName);
if (group != null && !RecursiveTree.Contains(group))
{
yield return group;
if (group.MemberOf != null)
{
RecursiveTree.Push(group);
foreach (var parentDistinguishedName in group.MemberOf)
foreach (var parentGroup in GetGroupsRecursive(parentDistinguishedName, RecursiveTree))
yield return parentGroup;
RecursiveTree.Pop();
}
}
}
private static ActiveDirectoryGroup GetGroup(string DistinguishedName)
{
// Check Cache
Tuple<ActiveDirectoryGroup, DateTime> groupRecord = TryCache(DistinguishedName);
if (groupRecord == null)
{
// Load from AD
var group = ActiveDirectory.RetrieveGroupWithDistinguishedName(DistinguishedName);
SetValue(group);
return group;
}
else
{
// Return from Cache
return groupRecord.Item1;
}
}
private static ActiveDirectoryGroup GetGroupBySecurityIdentifier(string SecurityIdentifier)
{
// Check Cache
Tuple<ActiveDirectoryGroup, DateTime> groupRecord = TrySecurityIdentifierCache(SecurityIdentifier);
if (groupRecord == null)
{
// Load from AD
var group = ActiveDirectory.RetrieveGroupWithSecurityIdentifier(SecurityIdentifier);
SetValue(group);
return group;
}
else
{
// Return from Cache
return groupRecord.Item1;
}
}
private static Tuple<ActiveDirectoryGroup, DateTime> TryCache(string DistinguishedName)
{
string distinguishedName = DistinguishedName.ToLower();
Tuple<ActiveDirectoryGroup, DateTime> groupRecord;
if (_DistinguishedNameCache.TryGetValue(distinguishedName, out groupRecord))
{
if (groupRecord.Item2 > DateTime.Now)
return groupRecord;
else
{
if (_DistinguishedNameCache.TryRemove(distinguishedName, out groupRecord))
_SecurityIdentifierCache.TryRemove(groupRecord.Item1.SecurityIdentifier, out groupRecord);
}
}
return null;
}
private static Tuple<ActiveDirectoryGroup, DateTime> TrySecurityIdentifierCache(string SecurityIdentifier)
{
Tuple<ActiveDirectoryGroup, DateTime> groupRecord;
if (_SecurityIdentifierCache.TryGetValue(SecurityIdentifier, out groupRecord))
{
if (groupRecord.Item2 > DateTime.Now)
return groupRecord;
else
{
if (_SecurityIdentifierCache.TryRemove(SecurityIdentifier, out groupRecord))
_DistinguishedNameCache.TryRemove(groupRecord.Item1.DistinguishedName.ToLower(), out groupRecord);
}
}
return null;
}
private static bool SetValue(ActiveDirectoryGroup Group)
{
Tuple<ActiveDirectoryGroup, DateTime> groupRecord = new Tuple<ActiveDirectoryGroup, DateTime>(Group, DateTime.Now.AddTicks(CacheTimeoutTicks));
Tuple<ActiveDirectoryGroup, DateTime> oldGroupRecord;
string key = Group.DistinguishedName.ToLower();
if (_DistinguishedNameCache.ContainsKey(key))
{
if (_DistinguishedNameCache.TryGetValue(key, out oldGroupRecord))
{
_DistinguishedNameCache.TryUpdate(key, groupRecord, oldGroupRecord);
}
}
else
{
_DistinguishedNameCache.TryAdd(key, groupRecord);
}
string securityIdentifier = Group.SecurityIdentifier;
if (_SecurityIdentifierCache.ContainsKey(securityIdentifier))
{
if (_SecurityIdentifierCache.TryGetValue(securityIdentifier, out oldGroupRecord))
{
_SecurityIdentifierCache.TryUpdate(securityIdentifier, groupRecord, oldGroupRecord);
}
}
else
{
_SecurityIdentifierCache.TryAdd(securityIdentifier, groupRecord);
}
return true;
}
private static void CleanStaleCache()
{
// Clean Cache
var groupKeys = _DistinguishedNameCache.Keys.ToArray();
foreach (string groupKey in groupKeys)
{
Tuple<ActiveDirectoryGroup, DateTime> groupRecord;
if (_DistinguishedNameCache.TryGetValue(groupKey, out groupRecord))
{
if (groupRecord.Item2 <= DateTime.Now)
{
_DistinguishedNameCache.TryRemove(groupKey, out groupRecord);
}
}
}
// Clean SID Cache
groupKeys = _SecurityIdentifierCache.Keys.ToArray();
foreach (string groupKey in groupKeys)
{
Tuple<ActiveDirectoryGroup, DateTime> groupRecord;
if (_SecurityIdentifierCache.TryGetValue(groupKey, out groupRecord))
{
if (groupRecord.Item2 <= DateTime.Now)
{
_SecurityIdentifierCache.TryRemove(groupKey, out groupRecord);
}
}
}
}
public override string TaskName { get { return "AD Group Cache - Clean Stale Cache"; } }
public override bool SingleInstanceTask { get { return true; } }
public override bool CancelInitiallySupported { get { return false; } }
public override bool LogExceptionsOnly { get { return true; } }
public override void InitalizeScheduledTask(DiscoDataContext Database)
{
// Run @ every 15mins
// Next 15min interval
DateTime now = DateTime.Now;
int mins = (15 - (now.Minute % 15));
if (mins < 10)
mins += 15;
DateTimeOffset startAt = new DateTimeOffset(now).AddMinutes(mins).AddSeconds(now.Second * -1).AddMilliseconds(now.Millisecond * -1);
TriggerBuilder triggerBuilder = TriggerBuilder.Create().StartAt(startAt).
WithSchedule(SimpleScheduleBuilder.RepeatMinutelyForever(15));
this.ScheduleTask(triggerBuilder);
}
protected override void ExecuteTask()
{
CleanStaleCache();
}
}
}
@@ -1,682 +0,0 @@
using Disco.Data.Repository;
using Disco.Models.Interop.ActiveDirectory;
using Disco.Services.Tasks;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
namespace Disco.Services.Interop.ActiveDirectory.Internal
{
internal static class ADInterop
{
public static List<ActiveDirectoryDomain> Domains { get; private set; }
public static ActiveDirectoryDomain PrimaryDomain { get; private set; }
public static ActiveDirectorySite Site { get; private set; }
internal static List<string> _ForestServers { get; set; }
private static bool _SearchEntireForest { get; set; }
private static bool _Initialized = false;
private static object _InitializeLock = new object();
#region Initialization
public static void Initialize(DiscoDataContext Database)
{
if (!_Initialized)
{
lock (_InitializeLock)
{
if (!_Initialized)
{
_SearchEntireForest = Database.DiscoConfiguration.ActiveDirectory.SearchEntireForest ?? true; // Default True
using (var computerDomain = Domain.GetComputerDomain())
{
PrimaryDomain = GetDomainInternal(computerDomain, Database);
Domains = computerDomain.Forest.Domains.Cast<Domain>().Select(d =>
{
if (d.Name == PrimaryDomain.DnsName)
return PrimaryDomain;
else
return GetDomainInternal(d, Database);
}).ToList();
}
Site = ActiveDirectorySite.GetComputerSite();
}
}
}
}
private static ActiveDirectoryDomain GetDomainInternal(Domain d, DiscoDataContext Database)
{
string ldapPath = string.Format("LDAP://{0}/", d.Name);
string defaultNamingContext;
string configurationNamingContext;
string netBiosName;
using (var adRootDSE = new DirectoryEntry(ldapPath + "RootDSE"))
{
defaultNamingContext = adRootDSE.Properties["defaultNamingContext"][0].ToString();
configurationNamingContext = adRootDSE.Properties["configurationNamingContext"][0].ToString();
}
using (var configSearchRoot = new DirectoryEntry(ldapPath + "CN=Partitions," + configurationNamingContext))
{
var configSearchFilter = string.Format("(&(objectcategory=Crossref)(dnsRoot={0})(netBIOSName=*))", d.Name);
var configSearchLoadProperites = new string[] { "NetBIOSName" };
using (var configSearcher = new DirectorySearcher(configSearchRoot, configSearchFilter, configSearchLoadProperites, System.DirectoryServices.SearchScope.OneLevel))
{
SearchResult configResult = configSearcher.FindOne();
if (configResult != null)
netBiosName = configResult.Properties["NetBIOSName"][0].ToString();
else
netBiosName = null;
}
}
// Search Containers
var searchContainersAll = Database.DiscoConfiguration.ActiveDirectory.SearchContainers;
List<string> searchContainers = null;
if (searchContainersAll == null || searchContainersAll.Count == 0 || !searchContainersAll.TryGetValue(d.Name.ToLower(), out searchContainers))
searchContainers = new List<string>() { defaultNamingContext }; // No search constraints set - search entire tree
return new ActiveDirectoryDomain(d.Name, netBiosName, defaultNamingContext, searchContainers);
}
public static void UpdateSearchContainers(DiscoDataContext Database, IEnumerable<string> Containers)
{
Dictionary<string, List<string>> searchContainers = null;
if (Containers != null)
{
searchContainers = Containers
.Where(c => !string.IsNullOrWhiteSpace(c))
.Distinct()
.Select(c =>
{
ActiveDirectoryDomain d;
if (TryGetDomainByDistinguishedName(c, out d))
return Tuple.Create(d, c);
else
return null;
}).Where(i => i != null)
.GroupBy(i => i.Item1)
.ToDictionary(g => g.Key.DnsName.ToLower(), g => g.Select(i => i.Item2).ToList());
}
if (searchContainers == null || searchContainers.Count == 0)
{
Database.DiscoConfiguration.ActiveDirectory.SearchContainers = null;
// No search constraints set - search entire tree
Domains.ForEach(d => d.UpdateSearchContainers(new string[] { d.DistinguishedName }));
}
else
{
Database.DiscoConfiguration.ActiveDirectory.SearchContainers = searchContainers;
Domains.ForEach(d =>
{
List<string> domainContainers;
if (searchContainers.TryGetValue(d.DnsName.ToLower(), out domainContainers))
d.UpdateSearchContainers(domainContainers);
else
d.UpdateSearchContainers(Enumerable.Empty<string>());
});
}
}
public static bool UpdateSearchEntireForest(DiscoDataContext Database, bool SearchEntireForest)
{
if (SearchEntireForest == false)
{
Database.DiscoConfiguration.ActiveDirectory.SearchEntireForest = false;
ADInterop._SearchEntireForest = false;
return true;
}
else
{
var forestServers = LoadForestServers();
if (forestServers.Count <= ActiveDirectory.MaxForestServerSearch)
{
Database.DiscoConfiguration.ActiveDirectory.SearchEntireForest = true;
ADInterop._SearchEntireForest = true;
return true;
}
else
{
Database.DiscoConfiguration.ActiveDirectory.SearchEntireForest = false;
ADInterop._SearchEntireForest = false;
return false;
}
}
}
#endregion
#region Domain Getters
public static bool TryGetDomainByDistinguishedName(string DistinguishedName, out ActiveDirectoryDomain Domain)
{
// Find closest match
Domain = ADInterop.Domains.Where(d => DistinguishedName.EndsWith(d.DistinguishedName, StringComparison.InvariantCultureIgnoreCase))
.OrderByDescending(d => d.DistinguishedName.Length).FirstOrDefault();
return (Domain != null);
}
public static ActiveDirectoryDomain GetDomainByDistinguishedName(string DistinguishedName)
{
ActiveDirectoryDomain domain;
if (!TryGetDomainByDistinguishedName(DistinguishedName, out domain))
throw new ArgumentException(string.Format("The domain is unknown distinguished name: [{0}]", DistinguishedName), "Id");
return domain;
}
public static bool TryGetDomainByNetBiosName(string NetBiosName, out ActiveDirectoryDomain Domain)
{
Domain = ADInterop.Domains.FirstOrDefault(d => d.NetBiosName.Equals(NetBiosName, StringComparison.InvariantCultureIgnoreCase));
return (Domain != null);
}
public static ActiveDirectoryDomain GetDomainByNetBiosName(string NetBiosName)
{
ActiveDirectoryDomain domain;
if (!TryGetDomainByNetBiosName(NetBiosName, out domain))
throw new ArgumentException(string.Format("The specified domain is unknown [{0}]", NetBiosName), "Id");
return domain;
}
public static bool TryGetDomainByDnsName(string DnsName, out ActiveDirectoryDomain Domain)
{
Domain = ADInterop.Domains.FirstOrDefault(d => d.DnsName.Equals(DnsName, StringComparison.InvariantCultureIgnoreCase));
return (Domain != null);
}
public static ActiveDirectoryDomain GetDomainByDnsName(string DnsName)
{
ActiveDirectoryDomain domain;
if (!TryGetDomainByDnsName(DnsName, out domain))
throw new ArgumentException(string.Format("The specified domain is unknown [{0}]", DnsName), "Id");
return domain;
}
public static bool TryGetDomainFromId(string Id, out ActiveDirectoryDomain Domain)
{
if (string.IsNullOrWhiteSpace(Id))
throw new ArgumentNullException("Id");
var idSplit = UserExtensions.SplitUserId(Id);
if (idSplit.Item1 == null)
throw new ArgumentException(string.Format("The Id must include the Domain [{0}]", Id), "Id");
if (string.IsNullOrWhiteSpace(idSplit.Item1))
throw new ArgumentException(string.Format("The Id Domain was empty [{0}]", Id), "Id");
if (string.IsNullOrWhiteSpace(idSplit.Item2))
throw new ArgumentException(string.Format("The Id Name was empty [{0}]", Id), "Id");
return TryGetDomainByNetBiosName(idSplit.Item1, out Domain);
}
public static ActiveDirectoryDomain GetDomainFromId(string Id)
{
if (string.IsNullOrWhiteSpace(Id))
throw new ArgumentNullException("Id");
var idSplit = UserExtensions.SplitUserId(Id);
if (idSplit.Item1 == null)
throw new ArgumentException(string.Format("The Id must include the Domain [{0}]", Id), "Id");
if (string.IsNullOrWhiteSpace(idSplit.Item1))
throw new ArgumentException(string.Format("The Id Domain was empty [{0}]", Id), "Id");
if (string.IsNullOrWhiteSpace(idSplit.Item2))
throw new ArgumentException(string.Format("The Id Name was empty [{0}]", Id), "Id");
return GetDomainByNetBiosName(idSplit.Item1);
}
public static List<string> LoadForestServers()
{
if (_ForestServers == null)
{
lock (_InitializeLock)
{
if (_ForestServers == null)
{
var status = ADDiscoverForestServers.ScheduleNow();
status.CompletionTask.Wait();
}
}
}
return _ForestServers;
}
public static Task<List<string>> LoadForestServersAsync()
{
if (_ForestServers != null)
return Task.FromResult(_ForestServers);
lock (_InitializeLock)
{
if (_ForestServers != null)
return Task.FromResult(_ForestServers);
// Invoke Scheduled Task
var status = ADDiscoverForestServers.ScheduleNow();
return status.CompletionTask.ContinueWith(t =>
{
return ADInterop._ForestServers.ToList();
});
}
}
public static bool SearchEntireForest
{
get
{
if (_ForestServers != null && _ForestServers.Count > ActiveDirectory.MaxForestServerSearch)
return false; // Never
return _SearchEntireForest;
}
}
#endregion
#region Searching
public static IEnumerable<ActiveDirectorySearchResult> SearchAll(string LdapFilter, string[] LoadProperties)
{
return SearchAll(Domains, LdapFilter, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchAll(string LdapFilter, int ResultLimit, string[] LoadProperties)
{
return SearchAll(Domains, LdapFilter, ResultLimit, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchAll(IEnumerable<Tuple<ActiveDirectoryDomain, DomainController>> DomainsWithController, string LdapFilter, string[] LoadProperties)
{
return SearchAll(DomainsWithController, LdapFilter, null, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchAll(IEnumerable<ActiveDirectoryDomain> Domains, string LdapFilter, string[] LoadProperties)
{
return SearchAll(Domains, LdapFilter, null, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchAll(ActiveDirectoryDomain Domain, DomainController DomainController, string LdapFilter, string[] LoadProperties)
{
return SearchAll(Domain, DomainController, LdapFilter, null, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchAll(ActiveDirectoryDomain Domain, string LdapFilter, string[] LoadProperties)
{
return SearchAll(Domain, LdapFilter, null, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchAll(IEnumerable<ActiveDirectoryDomain> Domains, string LdapFilter, int? ResultLimit, string[] LoadProperties)
{
if (Domains == null || Domains.Count() == 0)
return Enumerable.Empty<ActiveDirectorySearchResult>();
var queries = Domains.Select(d => Tuple.Create(d, (DomainController)null)).ToList();
return SearchAll(queries, LdapFilter, ResultLimit, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchAll(IEnumerable<Tuple<ActiveDirectoryDomain, DomainController>> DomainsWithController, string LdapFilter, int? ResultLimit, string[] LoadProperties)
{
var queries = DomainsWithController.ToList();
IEnumerable<ActiveDirectorySearchResult> results;
switch (queries.Count)
{
case 0:
results = Enumerable.Empty<ActiveDirectorySearchResult>();
break;
case 1:
var singleQuery = queries.First();
results = SearchDomain(singleQuery.Item1, singleQuery.Item2, null, LdapFilter, ResultLimit, LoadProperties);
break;
default:
var taskFactory = new TaskFactory<IEnumerable<ActiveDirectorySearchResult>>();
var tasks = queries
.Select(query =>
taskFactory.StartNew(() =>
SearchDomain(query.Item1, query.Item2, null, LdapFilter, ResultLimit, LoadProperties))
).ToArray();
Task.WaitAll(tasks);
results = tasks.SelectMany(t => t.Result);
break;
}
if (ResultLimit.HasValue)
results = results.Take(ResultLimit.Value);
return results.ToList();
}
public static IEnumerable<ActiveDirectorySearchResult> SearchAll(ActiveDirectoryDomain Domain, string LdapFilter, int? ResultLimit, string[] LoadProperties)
{
return SearchAll(Domain, null, LdapFilter, ResultLimit, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchAll(ActiveDirectoryDomain Domain, DomainController DomainController, string LdapFilter, int? ResultLimit, string[] LoadProperties)
{
return SearchDomain(Domain, DomainController, null, LdapFilter, ResultLimit, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchScope(string LdapFilter, string[] LoadProperties)
{
return SearchScope(Domains, LdapFilter, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchScope(string LdapFilter, int ResultLimit, string[] LoadProperties)
{
return SearchScope(Domains, LdapFilter, ResultLimit, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchScope(IEnumerable<ActiveDirectoryDomain> Domains, string LdapFilter, string[] LoadProperties)
{
return SearchScope(Domains, LdapFilter, null, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchScope(IEnumerable<Tuple<ActiveDirectoryDomain, DomainController>> DomainsWithController, string LdapFilter, string[] LoadProperties)
{
return SearchScope(DomainsWithController, LdapFilter, null, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchScope(ActiveDirectoryDomain Domain, string LdapFilter, string[] LoadProperties)
{
return SearchScope(Domain, LdapFilter, null, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchScope(ActiveDirectoryDomain Domain, DomainController DomainController, string LdapFilter, string[] LoadProperties)
{
return SearchScope(Domain, DomainController, LdapFilter, null, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchScope(IEnumerable<ActiveDirectoryDomain> Domains, string LdapFilter, int? ResultLimit, string[] LoadProperties)
{
return SearchScope(Domains.Select(d => Tuple.Create(d, (DomainController)null)), LdapFilter, ResultLimit, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchScope(ActiveDirectoryDomain Domain, string LdapFilter, int? ResultLimit, string[] LoadProperties)
{
return SearchScope(Domain, null, LdapFilter, ResultLimit, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchScope(ActiveDirectoryDomain Domain, DomainController DomainController, string LdapFilter, int? ResultLimit, string[] LoadProperties)
{
if (Domain.SearchContainers == null || Domain.SearchContainers.Count == 0)
return Enumerable.Empty<ActiveDirectorySearchResult>();
var query = new List<Tuple<ActiveDirectoryDomain, DomainController>>() {
Tuple.Create(Domain, DomainController)
};
return SearchScope(query, LdapFilter, ResultLimit, LoadProperties);
}
public static IEnumerable<ActiveDirectorySearchResult> SearchScope(IEnumerable<Tuple<ActiveDirectoryDomain, DomainController>> DomainsWithController, string LdapFilter, int? ResultLimit, string[] LoadProperties)
{
var queries = DomainsWithController.SelectMany(d => d.Item1.SearchContainers, (d, sc) => Tuple.Create(d.Item1, d.Item2, sc)).ToList();
IEnumerable<ActiveDirectorySearchResult> results;
switch (queries.Count)
{
case 0:
results = Enumerable.Empty<ActiveDirectorySearchResult>();
break;
case 1:
var singleQuery = queries.First();
results = SearchDomain(singleQuery.Item1, singleQuery.Item2, singleQuery.Item3, LdapFilter, ResultLimit, LoadProperties);
break;
default:
var taskFactory = new TaskFactory<IEnumerable<ActiveDirectorySearchResult>>();
var tasks = queries
.Select(query =>
taskFactory.StartNew(() =>
SearchDomain(query.Item1, query.Item2, query.Item3, LdapFilter, ResultLimit, LoadProperties).ToList())
).ToArray();
Task.WaitAll(tasks);
results = tasks.SelectMany(t => t.Result);
break;
}
if (ResultLimit.HasValue)
results = results.Take(ResultLimit.Value);
return results.ToList();
}
private static IEnumerable<ActiveDirectorySearchResult> SearchDomain(ActiveDirectoryDomain Domain, DomainController DomainController, string SearchRoot, string LdapFilter, int? ResultLimit, string[] LoadProperties)
{
string ldapServer;
if (DomainController != null)
{
ldapServer = DomainController.Name;
}
else
{
var domainDC = Site.RetrieveReachableDomainControllers(Domain).FirstOrDefault();
if (domainDC != null)
ldapServer = domainDC.Name;
else
ldapServer = Domain.DnsName;
}
string searchRoot = SearchRoot ?? Domain.DistinguishedName;
string ldapPath = string.Format(@"LDAP://{0}/{1}", ldapServer, searchRoot);
using (DirectoryEntry rootEntry = new DirectoryEntry(ldapPath))
{
using (DirectorySearcher searcher = new DirectorySearcher(rootEntry, LdapFilter, LoadProperties, System.DirectoryServices.SearchScope.Subtree))
{
searcher.PageSize = 500;
if (ResultLimit.HasValue)
searcher.SizeLimit = ResultLimit.Value;
return searcher.FindAll().Cast<SearchResult>().Select(result => new ActiveDirectorySearchResult()
{
Domain = Domain,
SearchRoot = searchRoot,
Result = result,
});
}
}
}
#endregion
public static string OfflineDomainJoinProvision(ActiveDirectoryDomain Domain, DomainController DomainController, string ComputerSamAccountName, string OrganisationalUnit, out string DiagnosticInformation)
{
StringBuilder diagnosticInfo = new StringBuilder();
string DJoinResult = null;
if (!string.IsNullOrWhiteSpace(ComputerSamAccountName))
ComputerSamAccountName = ComputerSamAccountName.TrimEnd('$');
if (string.IsNullOrWhiteSpace(ComputerSamAccountName) || ComputerSamAccountName.Length > 24)
throw new System.ArgumentException("Invalid Computer Name; > 0 and <= 24", "ComputerName");
// Ensure Specified OU Exists
if (!string.IsNullOrEmpty(OrganisationalUnit))
{
try
{
using (var deOU = DomainController.RetrieveDirectoryEntry(OrganisationalUnit))
{
if (deOU == null)
throw new Exception(string.Format("OU's Directory Entry couldn't be found at [{0}]", OrganisationalUnit));
}
}
catch (Exception ex)
{
throw new ArgumentException(string.Format("An error occurred while trying to locate the specified OU: {0}", OrganisationalUnit), "OrganisationalUnit", ex);
}
}
string tempFileName = System.IO.Path.GetTempFileName();
string argumentOU = (!string.IsNullOrWhiteSpace(OrganisationalUnit)) ? string.Format(" /MACHINEOU \"{0}\"", OrganisationalUnit) : string.Empty;
string arguments = string.Format("/PROVISION /DOMAIN \"{0}\" /DCNAME \"{1}\" /MACHINE \"{2}\"{3} /REUSE /SAVEFILE \"{4}\"",
Domain.DnsName,
DomainController.Name,
ComputerSamAccountName,
argumentOU,
tempFileName
);
ProcessStartInfo commandStarter = new ProcessStartInfo("DJOIN.EXE", arguments)
{
CreateNoWindow = true,
ErrorDialog = false,
LoadUserProfile = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
diagnosticInfo.AppendFormat("{0} {1}", "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 = System.Convert.ToBase64String(System.IO.File.ReadAllBytes(tempFileName));
System.IO.File.Delete(tempFileName);
}
if (string.IsNullOrWhiteSpace(DJoinResult))
throw new System.InvalidOperationException(string.Format("Domain Join Unsuccessful{0}Error: {1}{0}Output: {2}", System.Environment.NewLine, stdError, stdOutput));
DiagnosticInformation = diagnosticInfo.ToString();
return DJoinResult;
}
public static DirectoryEntry RetrieveDirectoryEntry(string DistinguishedName, out ActiveDirectoryDomain Domain)
{
if (string.IsNullOrWhiteSpace(DistinguishedName))
throw new ArgumentNullException("DistinguishedName");
// Find Domain
var domain = GetDomainByDistinguishedName(DistinguishedName);
if (domain == null)
throw new ArgumentException(string.Format("Unknown domain for DistinguishedName: {0}", DistinguishedName), "DistinguishedName");
var domainDC = Site.RetrieveReachableDomainControllers(domain).FirstOrDefault();
var ldapServer = domainDC != null ? domainDC.Name : domain.DnsName;
Domain = domain;
return new DirectoryEntry(string.Format(@"LDAP://{0}/{1}", ldapServer, DistinguishedName));
}
public static DomainController RetrieveWritableDomainController(this ActiveDirectoryDomain domain)
{
var adContext = new DirectoryContext(DirectoryContextType.Domain, domain.DnsName);
using (var adDomain = Domain.GetDomain(adContext))
{
return adDomain.FindDomainController(LocatorOptions.WriteableRequired);
}
}
public static DirectoryEntry RetrieveDirectoryEntry(this DomainController domainController, string DistinguishedName)
{
return new DirectoryEntry(string.Format(@"LDAP://{0}/{1}", domainController.Name, DistinguishedName));
}
public static void DeleteObjectRecursive(this DirectoryEntry directoryEntry)
{
DeleteObjectRecursiveInternal(directoryEntry);
using (var deParent = directoryEntry.Parent)
{
deParent.Children.Remove(directoryEntry);
}
}
private static void DeleteObjectRecursiveInternal(DirectoryEntry directoryEntry)
{
List<DirectoryEntry> children = directoryEntry.Children.Cast<DirectoryEntry>().ToList();
foreach (var child in children)
{
DeleteObjectRecursive(child);
directoryEntry.Children.Remove(child);
child.Dispose();
}
}
#region Helpers
internal static string ConvertBytesToSDDLString(byte[] SID)
{
SecurityIdentifier sID = new SecurityIdentifier(SID, 0);
return sID.ToString();
}
internal static byte[] ConvertSDDLStringToBytes(string SidSsdlString)
{
SecurityIdentifier sID = new SecurityIdentifier(SidSsdlString);
var sidBytes = new byte[sID.BinaryLength];
sID.GetBinaryForm(sidBytes, 0);
return sidBytes;
}
internal static byte[] BuildPrimaryGroupSid(byte[] UserSID, int PrimaryGroupId)
{
var groupSid = (byte[])UserSID.Clone();
int ridOffset = groupSid.Length - 4;
int groupId = PrimaryGroupId;
for (int i = 0; i < 4; i++)
{
groupSid[ridOffset + i] = (byte)(groupId & 0xFF);
groupId >>= 8;
}
return groupSid;
}
internal static string ConvertBytesToBinarySidString(byte[] SID)
{
StringBuilder escapedSid = new StringBuilder();
foreach (var sidByte in SID)
{
escapedSid.Append('\\');
escapedSid.Append(sidByte.ToString("x2"));
}
return escapedSid.ToString();
}
internal static string EscapeLdapQuery(string query)
{
return query.Replace("*", "\\2a").Replace("(", "\\28").Replace(")", "\\29").Replace("\\", "\\5c").Replace("NUL", "\\00").Replace("/", "\\2f");
}
internal static string FormatGuidForLdapQuery(System.Guid g)
{
checked
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
byte[] array = g.ToByteArray();
for (int i = 0; i < array.Length; i++)
{
byte b = array[i];
sb.Append("\\");
sb.Append(b.ToString("X2"));
}
return sb.ToString();
}
}
#endregion
}
}
@@ -1,231 +0,0 @@
using Disco.Data.Repository;
using Disco.Models.Interop.ActiveDirectory;
using Disco.Models.Repository;
using Disco.Services.Logging;
using Disco.Services.Tasks;
using Quartz;
using System;
using System.Collections;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
using System.Linq;
namespace Disco.Services.Interop.ActiveDirectory.Internal
{
public class ADUpdateLastNetworkLogonDateJob : ScheduledTask
{
public override string TaskName { get { return "Active Directory - Update Last Network Logon Dates Task"; } }
public override bool SingleInstanceTask { get { return true; } }
public override bool CancelInitiallySupported { get { return false; } }
public override void InitalizeScheduledTask(DiscoDataContext Database)
{
// ActiveDirectoryUpdateLastNetworkLogonDateJob @ 11:30pm
TriggerBuilder triggerBuilder = TriggerBuilder.Create().
WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(23, 30));
this.ScheduleTask(triggerBuilder);
}
protected override void ExecuteTask()
{
int changeCount;
this.Status.UpdateStatus(1, "Starting", "Connecting to the Database and initializing the environment");
using (DiscoDataContext database = new DiscoDataContext())
{
UpdateLastNetworkLogonDates(database, this.Status);
this.Status.UpdateStatus(95, "Updating Database", "Writing last network logon dates to the Database");
changeCount = database.SaveChanges();
this.Status.Finished(string.Format("{0} Device last network logon dates updated", changeCount), "/Config/SystemConfig");
}
SystemLog.LogInformation(new string[]
{
"Updated LastNetworkLogon Device Property for Device/s",
changeCount.ToString()
});
}
public static ScheduledTaskStatus ScheduleImmediately()
{
var existingTask = ScheduledTasks.GetTaskStatuses(typeof(ADUpdateLastNetworkLogonDateJob)).Where(s => s.IsRunning).FirstOrDefault();
if (existingTask != null)
return existingTask;
var instance = new ADUpdateLastNetworkLogonDateJob();
return instance.ScheduleTask();
}
public static bool UpdateLastNetworkLogonDate(Device Device)
{
const string ldapFilterTemplate = "(&(objectCategory=Computer)(sAMAccountName={0}))";
string[] ldapProperties = new string[] { "lastLogon", "lastLogonTimestamp" };
System.DateTime? lastLogon = null;
if (!string.IsNullOrEmpty(Device.DeviceDomainId))
{
var deviceSamAccountName = UserExtensions.SplitUserId(Device.DeviceDomainId).Item2 + "$";
var ldapFilter = string.Format(ldapFilterTemplate, ADInterop.EscapeLdapQuery(deviceSamAccountName));
var domain = ADInterop.GetDomainFromId(Device.DeviceDomainId);
IEnumerable<DomainController> domainControllers;
if (ADInterop.SearchEntireForest)
domainControllers = domain.RetrieveReachableDomainControllers();
else
domainControllers = ADInterop.Site.RetrieveReachableDomainControllers(domain);
lastLogon = domainControllers.Select(dc =>
{
using (var directoryRoot = dc.RetrieveDirectoryEntry(domain.DistinguishedName))
{
using (var directorySearcher = new DirectorySearcher(directoryRoot, ldapFilter, ldapProperties, SearchScope.Subtree))
{
var directoryResult = directorySearcher.FindOne();
if (directoryResult != null)
{
long lastLogonValue = default(long);
long lastLogonTimestampValue = default(long);
var lastLogonProperty = directoryResult.Properties["lastLogon"];
if (lastLogonProperty != null && lastLogonProperty.Count > 0)
lastLogonValue = (long)lastLogonProperty[0];
var lastLogonTimestampProperty = directoryResult.Properties["lastLogonTimestamp"];
if (lastLogonTimestampProperty != null && lastLogonTimestampProperty.Count > 0)
lastLogonTimestampValue = (long)lastLogonTimestampProperty[0];
long highedValue = Math.Max(lastLogonValue, lastLogonTimestampValue);
if (highedValue > 0)
return (DateTime?)new DateTime((DateTime.FromFileTime(highedValue).Ticks / 10000000L) * 10000000L);
else
return null;
}
}
}
return null;
}).Where(dt => dt.HasValue).Max();
}
if (lastLogon.HasValue &&
(
!Device.LastNetworkLogonDate.HasValue
|| Device.LastNetworkLogonDate.Value < lastLogon
))
{
Device.LastNetworkLogonDate = lastLogon;
return true;
}
return false;
}
private static void UpdateLastNetworkLogonDates(DiscoDataContext Database, ScheduledTaskStatus status)
{
const string ldapFilter = "(objectCategory=Computer)";
string[] ldapProperties = new string[] { "sAMAccountName", "lastLogon" };
status.UpdateStatus(2, "Initializing", "Determining Domains and Available Domain Controllers");
// Determine Domain Controllers to Query
IEnumerable<Tuple<ActiveDirectoryDomain, DomainController>> domainControllers;
if (ADInterop.SearchEntireForest)
domainControllers = ADInterop.Domains.SelectMany(d => d.RetrieveReachableDomainControllers(), (d, dc) => Tuple.Create(d, dc));
else
domainControllers = ADInterop.Domains.SelectMany(d => ADInterop.Site.RetrieveReachableDomainControllers(d), (d, dc) => Tuple.Create(d, dc));
// Determine Queries
var requiredRueries = domainControllers
.Where(s => s.Item1.SearchContainers != null && s.Item1.SearchContainers.Count > 0)
.SelectMany(s => s.Item1.SearchContainers, (s, c) => Tuple.Create(s.Item1, s.Item2, c)).ToList();
var queries = Enumerable.Range(0, requiredRueries.Count).Select(i =>
{
var q = requiredRueries[i];
return Tuple.Create(i, q.Item1, q.Item2, q.Item3);
});
var queryResults = queries.SelectMany(q =>
{
var queryIndex = q.Item1;
var domain = q.Item2;
var domainController = q.Item3;
var searchRoot = q.Item4;
// Update Status
double progress = 5 + (queryIndex * (90 / requiredRueries.Count));
status.UpdateStatus(progress, string.Format("Querying Domain [{0}] using controller [{1}]", domain.NetBiosName, domainController.Name), string.Format("Searching: {0}", searchRoot));
// Perform Query
using (var directoryRoot = domainController.RetrieveDirectoryEntry(searchRoot))
{
using (var directorySearcher = new DirectorySearcher(directoryRoot, ldapFilter, ldapProperties, SearchScope.Subtree))
{
directorySearcher.PageSize = 500;
var directoryResults = directorySearcher.FindAll();
if (directoryResults != null)
{
return directoryResults.Cast<SearchResult>().Select(result =>
{
var samAccountProperity = result.Properties["sAMAccountName"];
long lastLogonValue = default(long);
long lastLogonTimestampValue = default(long);
var lastLogonProperty = result.Properties["lastLogon"];
if (lastLogonProperty != null && lastLogonProperty.Count > 0)
lastLogonValue = (long)lastLogonProperty[0];
var lastLogonTimestampProperty = result.Properties["lastLogonTimestamp"];
if (lastLogonTimestampProperty != null && lastLogonTimestampProperty.Count > 0)
lastLogonTimestampValue = (long)lastLogonTimestampProperty[0];
long highedValue = Math.Max(lastLogonValue, lastLogonTimestampValue);
if (highedValue > 0)
{
var computerName = string.Format(@"{0}\{1}", domain.NetBiosName, samAccountProperity[0].ToString().TrimEnd('$'));
var lastLogon = new DateTime((DateTime.FromFileTime(highedValue).Ticks / 10000000L) * 10000000L);
return Tuple.Create(computerName, lastLogon);
}
else
return null;
}).Where(i => i != null).ToList();
}
else
{
return Enumerable.Empty<Tuple<string, DateTime>>();
}
}
}
}).GroupBy(r => r.Item1, StringComparer.InvariantCultureIgnoreCase).ToDictionary(g => g.Key.ToUpper(), g => g.Max(i => i.Item2));
status.UpdateStatus(90, "Processing Results", "Processing last network logon dates and looking for updates");
foreach (Device device in Database.Devices.Where(device => device.DeviceDomainId != null))
{
DateTime lastLogonDate;
if (queryResults.TryGetValue(device.DeviceDomainId.ToUpper(), out lastLogonDate))
{
if (!device.LastNetworkLogonDate.HasValue)
device.LastNetworkLogonDate = lastLogonDate;
else
{
// Change accuracy to the second
lastLogonDate = new DateTime((lastLogonDate.Ticks / 10000000L) * 10000000L);
if (device.LastNetworkLogonDate.Value < lastLogonDate)
device.LastNetworkLogonDate = lastLogonDate;
}
}
}
}
}
}
@@ -274,12 +274,12 @@ namespace Disco.Services
Location = i,
References = o.ToList()
},
StringComparer.InvariantCultureIgnoreCase);
StringComparer.OrdinalIgnoreCase);
}
public static IEnumerable<JobLocationReference> JobLocationReferences(this IEnumerable<JobTableStatusItemModel> Items)
{
return Items.Where(i => !string.IsNullOrWhiteSpace(i.DeviceHeldLocation) && i.DeviceHeld.HasValue && !i.DeviceReturnedDate.HasValue)
.GroupBy(i => i.DeviceHeldLocation, StringComparer.InvariantCultureIgnoreCase)
.GroupBy(i => i.DeviceHeldLocation, StringComparer.OrdinalIgnoreCase)
.Select(i => new JobLocationReference()
{
Location = i.Key,
+1 -1
View File
@@ -56,7 +56,7 @@ namespace Disco.Services.Logging
var appDomain = AppDomain.CurrentDomain;
var logModuleTypes = (from a in appDomain.GetAssemblies()
where !a.GlobalAssemblyCache && !a.IsDynamic && a.FullName.StartsWith("Disco.", StringComparison.InvariantCultureIgnoreCase)
where !a.GlobalAssemblyCache && !a.IsDynamic && a.FullName.StartsWith("Disco.", StringComparison.OrdinalIgnoreCase)
from type in a.GetTypes()
where typeof(LogBase).IsAssignableFrom(type) && !type.IsAbstract
select type);
@@ -28,7 +28,7 @@ namespace Disco.Services.Plugins.Features.WarrantyProvider
public static PluginFeatureManifest FindPluginFeature(string PluginIdOrWarrantyProviderId)
{
var defs = Plugins.GetPluginFeatures(typeof(WarrantyProviderFeature));
var def = defs.FirstOrDefault(d => d.PluginManifest.Id.Equals(PluginIdOrWarrantyProviderId, StringComparison.InvariantCultureIgnoreCase));
var def = defs.FirstOrDefault(d => d.PluginManifest.Id.Equals(PluginIdOrWarrantyProviderId, StringComparison.OrdinalIgnoreCase));
if (def != null)
return def;
else
@@ -36,7 +36,7 @@ namespace Disco.Services.Plugins.Features.WarrantyProvider
{
using (var providerInstance = d.CreateInstance<WarrantyProviderFeature>())
{
if (providerInstance.WarrantyProviderId != null && providerInstance.WarrantyProviderId.Equals(PluginIdOrWarrantyProviderId, StringComparison.InvariantCultureIgnoreCase))
if (providerInstance.WarrantyProviderId != null && providerInstance.WarrantyProviderId.Equals(PluginIdOrWarrantyProviderId, StringComparison.OrdinalIgnoreCase))
{
return d;
}
+1 -1
View File
@@ -101,7 +101,7 @@ namespace Disco.Services.Plugins
// Check for Compatibility
var compatibilityData = Plugins.LoadCompatibilityData(database);
var pluginCompatibility = compatibilityData.Plugins.FirstOrDefault(i => i.Id.Equals(packageManifest.Id, StringComparison.InvariantCultureIgnoreCase) && packageManifest.Version == Version.Parse(i.Version));
var pluginCompatibility = compatibilityData.Plugins.FirstOrDefault(i => i.Id.Equals(packageManifest.Id, StringComparison.OrdinalIgnoreCase) && packageManifest.Version == Version.Parse(i.Version));
if (pluginCompatibility != null && !pluginCompatibility.Compatible)
throw new InvalidOperationException(string.Format("The plugin [{0} v{1}] is not compatible: {2}", packageManifest.Id, packageManifest.VersionFormatted, pluginCompatibility.Reason));
+1 -1
View File
@@ -240,7 +240,7 @@ namespace Disco.Services.Plugins
foreach (string referenceFilename in Directory.EnumerateFiles(pluginLocation, "*.dll", SearchOption.TopDirectoryOnly))
{
if (!referenceFilename.Equals(assembly.Location, StringComparison.InvariantCultureIgnoreCase))
if (!referenceFilename.Equals(assembly.Location, StringComparison.OrdinalIgnoreCase))
{
// Ignore Excluded Assemblies
if (!PluginExcludedAssemblies.Contains(Path.GetFileNameWithoutExtension(referenceFilename)))
+4 -4
View File
@@ -302,7 +302,7 @@ namespace Disco.Services.Plugins
foreach (var serverItem in serverData.Plugins)
{
var serverItemVersion = Version.Parse(serverItem.Version);
var localItem = localItems.FirstOrDefault(i => i.Id.Equals(serverItem.Id, StringComparison.InvariantCultureIgnoreCase) && serverItemVersion == localItemVersions[i]);
var localItem = localItems.FirstOrDefault(i => i.Id.Equals(serverItem.Id, StringComparison.OrdinalIgnoreCase) && serverItemVersion == localItemVersions[i]);
if (localItem != null)
joinedItems.Remove(localItem);
@@ -373,7 +373,7 @@ namespace Disco.Services.Plugins
if (pluginManifest != null)
{
// Check Version Compatibility
var pluginCompatibility = compatibilityData.Value.Plugins.FirstOrDefault(i => i.Id.Equals(pluginManifest.Id, StringComparison.InvariantCultureIgnoreCase) && pluginManifest.Version == Version.Parse(i.Version));
var pluginCompatibility = compatibilityData.Value.Plugins.FirstOrDefault(i => i.Id.Equals(pluginManifest.Id, StringComparison.OrdinalIgnoreCase) && pluginManifest.Version == Version.Parse(i.Version));
if (pluginCompatibility != null && !pluginCompatibility.Compatible)
throw new InvalidOperationException(string.Format("The plugin [{0} v{1}] is not compatible: {2}", pluginManifest.Id, pluginManifest.VersionFormatted, pluginCompatibility.Reason));
@@ -496,7 +496,7 @@ namespace Disco.Services.Plugins
// Check Compatibility
if (CompatibilityData == null)
CompatibilityData = LoadCompatibilityData(Database);
var pluginCompatibility = CompatibilityData.Plugins.FirstOrDefault(i => i.Id.Equals(packageManifest.Id, StringComparison.InvariantCultureIgnoreCase) && packageManifest.Version == Version.Parse(i.Version));
var pluginCompatibility = CompatibilityData.Plugins.FirstOrDefault(i => i.Id.Equals(packageManifest.Id, StringComparison.OrdinalIgnoreCase) && packageManifest.Version == Version.Parse(i.Version));
if (pluginCompatibility != null && !pluginCompatibility.Compatible)
throw new InvalidOperationException(string.Format("The plugin [{0} v{1}] is not compatible: {2}", packageManifest.Id, packageManifest.VersionFormatted, pluginCompatibility.Reason));
@@ -595,7 +595,7 @@ namespace Disco.Services.Plugins
public static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.RequestingAssembly != null && args.RequestingAssembly.Location.StartsWith(PluginPath, StringComparison.InvariantCultureIgnoreCase) && _PluginManifests != null)
if (args.RequestingAssembly != null && args.RequestingAssembly.Location.StartsWith(PluginPath, StringComparison.OrdinalIgnoreCase) && _PluginManifests != null)
{
// Try best guess first
PluginManifest requestingPlugin = _PluginManifests.Values.Where(p => p.Type.Assembly == args.RequestingAssembly).FirstOrDefault();
+1 -1
View File
@@ -235,7 +235,7 @@ namespace Disco.Services.Plugins
{
// Check for Compatibility
var compatibilityData = Plugins.LoadCompatibilityData(database);
var pluginCompatibility = compatibilityData.Plugins.FirstOrDefault(i => i.Id.Equals(updateManifest.Id, StringComparison.InvariantCultureIgnoreCase) && updateManifest.Version == Version.Parse(i.Version));
var pluginCompatibility = compatibilityData.Plugins.FirstOrDefault(i => i.Id.Equals(updateManifest.Id, StringComparison.OrdinalIgnoreCase) && updateManifest.Version == Version.Parse(i.Version));
if (pluginCompatibility != null && !pluginCompatibility.Compatible)
throw new InvalidOperationException(string.Format("The plugin [{0} v{1}] is not compatible: {2}", updateManifest.Id, updateManifest.VersionFormatted, pluginCompatibility.Reason));
+1 -2
View File
@@ -1,5 +1,4 @@
using Disco.Data.Repository;
using Disco.Models.Interop.ActiveDirectory;
using Disco.Models.Repository;
using Disco.Models.Services.Jobs.JobLists;
using Disco.Models.Services.Searching;
@@ -171,7 +170,7 @@ namespace Disco.Services.Searching
public static List<User> SearchUsersUpstream(string Term, int? LimitCount = null)
{
IEnumerable<ActiveDirectoryUserAccount> matches = ActiveDirectory.SearchUserAccounts(Term, Quick: true);
IEnumerable<ADUserAccount> matches = ActiveDirectory.SearchADUserAccounts(Term, Quick: true);
if (LimitCount.HasValue)
matches = matches.Take(LimitCount.Value);
+3 -3
View File
@@ -12,7 +12,7 @@ namespace Disco.Services
{
public static bool IsInPrimaryDomain(this User u)
{
return u.Domain.Equals(Disco.Services.Interop.ActiveDirectory.ActiveDirectory.PrimaryDomain.NetBiosName, StringComparison.InvariantCultureIgnoreCase);
return u.Domain.Equals(ActiveDirectory.Context.PrimaryDomain.NetBiosName, StringComparison.OrdinalIgnoreCase);
}
public static string FriendlyId(this User u)
@@ -23,8 +23,8 @@ namespace Disco.Services
public static string FriendlyUserId(string UserId)
{
var splitUserId = SplitUserId(UserId);
if (splitUserId.Item1 != null && splitUserId.Item1.Equals(ActiveDirectory.PrimaryDomain.NetBiosName, StringComparison.InvariantCultureIgnoreCase))
if (splitUserId.Item1 != null && splitUserId.Item1.Equals(ActiveDirectory.Context.PrimaryDomain.NetBiosName, StringComparison.OrdinalIgnoreCase))
return splitUserId.Item2;
else
return UserId;
+6 -7
View File
@@ -1,5 +1,4 @@
using Disco.Data.Repository;
using Disco.Models.Interop.ActiveDirectory;
using Disco.Models.Repository;
using Disco.Services.Authorization;
using Disco.Services.Authorization.Roles;
@@ -206,9 +205,9 @@ namespace Disco.Services.Users
Cache.FlushCache();
}
internal static IEnumerable<ActiveDirectoryUserAccount> SearchUsers(DiscoDataContext Database, string Term)
internal static IEnumerable<ADUserAccount> SearchUsers(DiscoDataContext Database, string Term)
{
var adImportedUsers = ActiveDirectory.SearchUserAccounts(Term, Quick: true);
var adImportedUsers = ActiveDirectory.SearchADUserAccounts(Term, Quick: true);
foreach (var adU in adImportedUsers.Select(adU => adU.ToRepositoryUser()))
{
var existingUser = Database.Users.Find(adU.UserId);
@@ -230,7 +229,7 @@ namespace Disco.Services.Users
if (UserId.EndsWith("$"))
{
// Machine Account
var adAccount = ActiveDirectory.RetrieveMachineAccount(UserId);
var adAccount = ActiveDirectory.RetrieveADMachineAccount(UserId);
if (adAccount == null)
return null;
@@ -244,10 +243,10 @@ namespace Disco.Services.Users
{
// User Account
ActiveDirectoryUserAccount adAccount;
ADUserAccount adAccount;
try
{
adAccount = ActiveDirectory.RetrieveUserAccount(UserId);
adAccount = ActiveDirectory.RetrieveADUserAccount(UserId);
if (adAccount == null)
throw new ArgumentException(string.Format("Invalid Username: '{0}'; User not found in Active Directory", UserId), "Username");
@@ -280,7 +279,7 @@ namespace Disco.Services.Users
}
Database.SaveChanges();
var token = AuthorizationToken.BuildToken(user, adAccount.Groups);
var token = AuthorizationToken.BuildToken(user, adAccount.Groups.Select(g => g.Id));
return new Tuple<User, AuthorizationToken>(user, token);
}