Feature #42: Active Directory Interop Upgrade
AD Interop moved to Disco.Services; Supports multi-domain environments, sites, and searching restricted with OUs.
This commit is contained in:
@@ -0,0 +1,531 @@
|
||||
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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services.Interop.ActiveDirectory
|
||||
{
|
||||
public static class ActiveDirectory
|
||||
{
|
||||
private const int SingleSearchResult = 1;
|
||||
public const int MaxForestServerSearch = 30;
|
||||
|
||||
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
|
||||
{
|
||||
return ADInterop.PrimaryDomain;
|
||||
}
|
||||
}
|
||||
public static IEnumerable<ActiveDirectoryDomain> Domains
|
||||
{
|
||||
get
|
||||
{
|
||||
return ADInterop.Domains.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public static ActiveDirectorySite Site
|
||||
{
|
||||
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)
|
||||
{
|
||||
var ldapFilter = string.Format(ldapNetbootGuidFilterTemplate, ADInterop.FormatGuidForLdapQuery(UUIDNetbootGUID.Value));
|
||||
adResult = ADInterop.SearchAll(domain, DomainController, ldapFilter, SingleSearchResult, loadProperites).FirstOrDefault();
|
||||
}
|
||||
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);
|
||||
else
|
||||
return null; // Not Found
|
||||
}
|
||||
|
||||
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.SearchDomain(Domain, DomainController, Domain.DistinguishedName, ldapFilter, 1, loadProperites).FirstOrDefault();
|
||||
|
||||
if (ldapResult != null)
|
||||
MachineAccount = ldapResult.AsMachineAccount(loadAdditionalProperties);
|
||||
|
||||
return offlineJoinResult;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region User Account
|
||||
private static readonly string[] UserLoadProperties = { "name", "distinguishedName", "sAMAccountName", "objectSid", "displayName", "sn", "givenName", "memberOf", "primaryGroupID", "mail", "telephoneNumber" };
|
||||
|
||||
public static ActiveDirectoryUserAccount RetrieveUserAccount(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(AdditionalProperties)).FirstOrDefault();
|
||||
}
|
||||
public static IEnumerable<ActiveDirectoryUserAccount> SearchUserAccounts(string Term, params string[] AdditionalProperties)
|
||||
{
|
||||
const int resultLimit = 30; // Default Search Limit
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Term))
|
||||
throw new ArgumentNullException("Term");
|
||||
|
||||
string ldapFilter = string.Format("(&(objectCategory=Person)(objectClass=user)(|(sAMAccountName=*{0}*)(displayName=*{0}*)))", ADInterop.EscapeLdapQuery(Term));
|
||||
|
||||
string[] loadProperites = (AdditionalProperties != null && AdditionalProperties.Length > 0)
|
||||
? UserLoadProperties.Concat(AdditionalProperties).ToArray()
|
||||
: UserLoadProperties;
|
||||
|
||||
return ADInterop.SearchScope(ldapFilter, resultLimit, loadProperites).Select(result => result.AsUserAccount(AdditionalProperties));
|
||||
}
|
||||
|
||||
private static ActiveDirectoryUserAccount AsUserAccount(this ActiveDirectorySearchResult item, 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);
|
||||
|
||||
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();
|
||||
|
||||
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));
|
||||
List<string> groups = ADGroupCache.GetGroups(groupDistinguishedNames).ToList();
|
||||
|
||||
// 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 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
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Groups
|
||||
private static readonly string[] GroupLoadProperties = { "name", "distinguishedName", "cn", "sAMAccountName", "objectSid", "memberOf" };
|
||||
|
||||
public static ActiveDirectoryGroup RetrieveGroup(string Id)
|
||||
{
|
||||
const string ldapFilter = "(&(objectCategory=Group)(objectSid={0}))";
|
||||
|
||||
return SearchBySamAccountName(Id, ldapFilter, GroupLoadProperties).Select(result => result.AsGroup()).FirstOrDefault();
|
||||
}
|
||||
public static ActiveDirectoryGroup RetrieveGroupWithDistinguishedName(string DistinguishedName)
|
||||
{
|
||||
ActiveDirectoryDomain domain;
|
||||
|
||||
using (var groupEntry = ADInterop.RetrieveDirectoryEntry(DistinguishedName, out domain))
|
||||
{
|
||||
if (groupEntry == null)
|
||||
return null;
|
||||
|
||||
return groupEntry.AsGroup(domain);
|
||||
}
|
||||
}
|
||||
public static ActiveDirectoryGroup RetrieveGroupWithSecurityIdentifier(string 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();
|
||||
}
|
||||
public static IEnumerable<ActiveDirectoryGroup> SearchGroups(string Term)
|
||||
{
|
||||
const int resultLimit = 30; // Default Search Limit
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Term))
|
||||
throw new ArgumentNullException("Term");
|
||||
|
||||
string ldapFilter = string.Format("(&(objectCategory=Group)(|(sAMAccountName=*{0}*)(name=*{0}*)(cn=*{0}*)))", ADInterop.EscapeLdapQuery(Term));
|
||||
|
||||
return ADInterop.SearchScope(ldapFilter, resultLimit, GroupLoadProperties).Select(result => result.AsGroup());
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
return new ActiveDirectoryGroup()
|
||||
{
|
||||
Domain = item.Domain.DnsName,
|
||||
Name = name,
|
||||
DistinguishedName = distinguishedName,
|
||||
CommonName = cn,
|
||||
SamAccountName = sAMAccountName,
|
||||
SecurityIdentifier = objectSid,
|
||||
MemberOf = memberOf
|
||||
};
|
||||
}
|
||||
private static ActiveDirectoryGroup AsGroup(this DirectoryEntry item, ActiveDirectoryDomain Domain)
|
||||
{
|
||||
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.DnsName,
|
||||
Name = name,
|
||||
DistinguishedName = distinguishedName,
|
||||
CommonName = cn,
|
||||
SamAccountName = sAMAccountName,
|
||||
SecurityIdentifier = objectSid,
|
||||
MemberOf = memberOf
|
||||
};
|
||||
}
|
||||
#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)
|
||||
{
|
||||
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(null);
|
||||
case "cn=computer":
|
||||
return result.AsMachineAccount(null);
|
||||
case "cn=group":
|
||||
return result.AsGroup();
|
||||
default:
|
||||
throw new InvalidOperationException("Unexpected objectCategory");
|
||||
}
|
||||
}).FirstOrDefault();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Organisation Units
|
||||
|
||||
public static List<ActiveDirectoryOrganisationalUnit> RetrieveOrganisationalUnitStructure(ActiveDirectoryDomain Domain)
|
||||
{
|
||||
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>>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
private static IEnumerable<ActiveDirectorySearchResult> SearchBySamAccountName(string Id, string LdapFilterTemplate, string[] LoadProperties)
|
||||
{
|
||||
var splitId = UserExtensions.SplitUserId(Id);
|
||||
var ldapFilter = string.Format(LdapFilterTemplate, splitId.Item2);
|
||||
var domains = ADInterop.GetDomainFromId(Id);
|
||||
|
||||
return ADInterop.SearchAll(domains, ldapFilter, SingleSearchResult, LoadProperties);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
using Disco.Models.Interop.ActiveDirectory;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Services.Interop.ActiveDirectory.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.DirectoryServices;
|
||||
using System.DirectoryServices.ActiveDirectory;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services.Interop.ActiveDirectory
|
||||
{
|
||||
public static class ActiveDirectoryExtensions
|
||||
{
|
||||
#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)
|
||||
{
|
||||
using (Ping p = new Ping())
|
||||
{
|
||||
var pr = p.Send(ds.Name, 500);
|
||||
return (pr.Status == IPStatus.Success);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetFriendlyOrganisationalUnitName(this ActiveDirectoryDomain domain, string DistinguishedName)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
public static string GetDefaultComputerContainer(this ActiveDirectoryDomain domain)
|
||||
{
|
||||
return string.Format("CN=Computers,{0}", domain.DistinguishedName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region User Account Extensions
|
||||
public static object GetPropertyValue(this ActiveDirectoryUserAccount 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 "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;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Machine Account Extensions
|
||||
|
||||
public static void DeleteAccount(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))
|
||||
{
|
||||
deAccount.DeleteObjectRecursive();
|
||||
}
|
||||
}
|
||||
public static void DeleteAccount(this ActiveDirectoryMachineAccount account)
|
||||
{
|
||||
var domain = account.GetDomain();
|
||||
|
||||
using (var domainController = domain.RetrieveWritableDomainController())
|
||||
{
|
||||
account.DeleteAccount(domainController);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetNetbootGUID(this ActiveDirectoryMachineAccount account, DomainController DomainController, System.Guid updatedNetbootGUID)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
public static void SetDescription(this ActiveDirectoryMachineAccount account, DomainController DomainController, string Description)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetDescription(this ActiveDirectoryMachineAccount account, DomainController DomainController, 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);
|
||||
|
||||
account.SetDescription(DomainController, description);
|
||||
}
|
||||
public static void SetDescription(this ActiveDirectoryMachineAccount account, Device Device)
|
||||
{
|
||||
var domain = account.GetDomain();
|
||||
|
||||
using (var domainController = domain.RetrieveWritableDomainController())
|
||||
{
|
||||
account.SetDescription(domainController, Device);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DisableAccount(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];
|
||||
int updatedAccountControl = (accountControl | 2);
|
||||
if (accountControl != updatedAccountControl)
|
||||
{
|
||||
deAccount.Properties["userAccountControl"][0] = updatedAccountControl;
|
||||
deAccount.CommitChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
public static void DisableAccount(this ActiveDirectoryMachineAccount account)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Services.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.DirectoryServices.ActiveDirectory;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services.Interop.ActiveDirectory.Internal
|
||||
{
|
||||
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; } }
|
||||
|
||||
protected override void ExecuteTask()
|
||||
{
|
||||
var forestServers = DiscoverForestServers();
|
||||
ADInterop._ForestServers = forestServers;
|
||||
|
||||
// Restrict Searching Entire Forest if to many servers
|
||||
using (DiscoDataContext Database = new DiscoDataContext())
|
||||
{
|
||||
var searchEntireForest = Database.DiscoConfiguration.ActiveDirectory.SearchEntireForest;
|
||||
|
||||
// Check explicitly configured: No
|
||||
if (!searchEntireForest.HasValue || searchEntireForest.Value)
|
||||
{
|
||||
// Not Configured, or explicitly configured: Yes
|
||||
if (forestServers.Count > ActiveDirectory.MaxForestServerSearch)
|
||||
{
|
||||
// Update Database
|
||||
Database.DiscoConfiguration.ActiveDirectory.SearchEntireForest = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default
|
||||
Database.DiscoConfiguration.ActiveDirectory.SearchEntireForest = true;
|
||||
}
|
||||
|
||||
Database.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static ScheduledTaskStatus ScheduleNow()
|
||||
{
|
||||
var 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()
|
||||
{
|
||||
using (var computerDomain = Domain.GetComputerDomain())
|
||||
{
|
||||
return computerDomain.Forest.Domains.Cast<Domain>().SelectMany(d => d.FindAllDomainControllers().Cast<DomainController>()).Select(dc => dc.Name).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
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.SamAccountName;
|
||||
}
|
||||
}
|
||||
public static IEnumerable<string> GetGroups(string DistinguishedName)
|
||||
{
|
||||
foreach (var group in GetGroupsRecursive(DistinguishedName, new Stack<ActiveDirectoryGroup>()))
|
||||
yield return group.SamAccountName;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,617 @@
|
||||
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
|
||||
List<string> searchContainersAll = Database.DiscoConfiguration.ActiveDirectory.SearchContainers;
|
||||
List<string> searchContainers = null;
|
||||
|
||||
if (searchContainersAll != null && searchContainersAll.Count > 0)
|
||||
searchContainers = Database.DiscoConfiguration.ActiveDirectory.SearchContainers.Where(c => c.EndsWith(defaultNamingContext, StringComparison.InvariantCultureIgnoreCase)).ToList();
|
||||
else
|
||||
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)
|
||||
{
|
||||
if (Containers != null)
|
||||
{
|
||||
var distinctContainers = Containers
|
||||
.Where(c => !string.IsNullOrWhiteSpace(c))
|
||||
.Distinct().ToList();
|
||||
|
||||
Containers = distinctContainers.Where(c => !distinctContainers.Any(s => (c != s) && (c.EndsWith(s))));
|
||||
}
|
||||
|
||||
if (Containers == null || Containers.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 = Containers.ToList();
|
||||
|
||||
Domains.ForEach(d => { d.UpdateSearchContainers(Containers.Where(c => c.EndsWith(d.DistinguishedName, StringComparison.InvariantCultureIgnoreCase))); });
|
||||
}
|
||||
}
|
||||
|
||||
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<Tuple<ActiveDirectoryDomain, DomainController>> DomainsWithController, string LdapFilter, int? ResultLimit, string[] LoadProperties)
|
||||
{
|
||||
var query = DomainsWithController
|
||||
.SelectMany(d => SearchAll(d.Item1, d.Item2, LdapFilter, ResultLimit, LoadProperties));
|
||||
|
||||
if (ResultLimit.HasValue)
|
||||
query = query.Take(ResultLimit.Value);
|
||||
|
||||
return query.ToList();
|
||||
}
|
||||
public static IEnumerable<ActiveDirectorySearchResult> SearchAll(IEnumerable<ActiveDirectoryDomain> Domains, string LdapFilter, int? ResultLimit, string[] LoadProperties)
|
||||
{
|
||||
var query = Domains
|
||||
.SelectMany(domain => SearchAll(domain, LdapFilter, ResultLimit, LoadProperties));
|
||||
|
||||
if (ResultLimit.HasValue)
|
||||
query = query.Take(ResultLimit.Value);
|
||||
|
||||
return query.ToList();
|
||||
}
|
||||
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> SearchAll(ActiveDirectoryDomain Domain, string LdapFilter, int? ResultLimit, string[] LoadProperties)
|
||||
{
|
||||
return SearchDomain(Domain, 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<Tuple<ActiveDirectoryDomain, DomainController>> DomainsWithController, string LdapFilter, int? ResultLimit, string[] LoadProperties)
|
||||
{
|
||||
var query = DomainsWithController
|
||||
.SelectMany(d => SearchScope(d.Item1, d.Item2, LdapFilter, ResultLimit, LoadProperties));
|
||||
|
||||
if (ResultLimit.HasValue)
|
||||
query = query.Take(ResultLimit.Value);
|
||||
|
||||
return query.ToList();
|
||||
}
|
||||
public static IEnumerable<ActiveDirectorySearchResult> SearchScope(IEnumerable<ActiveDirectoryDomain> Domains, string LdapFilter, int? ResultLimit, string[] LoadProperties)
|
||||
{
|
||||
var query = Domains
|
||||
.SelectMany(domain => SearchScope(domain, LdapFilter, ResultLimit, LoadProperties));
|
||||
|
||||
if (ResultLimit.HasValue)
|
||||
query = query.Take(ResultLimit.Value);
|
||||
|
||||
return query.ToList();
|
||||
}
|
||||
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)
|
||||
return Enumerable.Empty<ActiveDirectorySearchResult>();
|
||||
|
||||
var query = Domain.SearchContainers
|
||||
.SelectMany(container => SearchDomain(Domain, DomainController, container, LdapFilter, ResultLimit, LoadProperties));
|
||||
|
||||
if (ResultLimit.HasValue)
|
||||
query = query.Take(ResultLimit.Value);
|
||||
|
||||
return query.ToList();
|
||||
}
|
||||
|
||||
public static IEnumerable<ActiveDirectorySearchResult> SearchDomain(ActiveDirectoryDomain Domain, string SearchRoot, string LdapFilter, int? ResultLimit, string[] LoadProperties)
|
||||
{
|
||||
return SearchDomain(Domain, null, SearchRoot, LdapFilter, ResultLimit, LoadProperties);
|
||||
}
|
||||
public static IEnumerable<ActiveDirectorySearchResult> SearchDomain(ActiveDirectoryDomain Domain, DomainController DomainController, string SearchRoot, string LdapFilter, int? ResultLimit, string[] LoadProperties)
|
||||
{
|
||||
string ldapServer = DomainController == null ? Domain.DnsName : DomainController.Name;
|
||||
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);
|
||||
|
||||
Domain = domain;
|
||||
|
||||
return new DirectoryEntry(string.Format(@"LDAP://{0}/{1}", domain.DnsName, 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user