Get machine name from Active Directory - active-directory

I have performed an "LDAP://" query to get a list of computers within a specified OU, my issue is not being able to collect just the computer "name" or even "cn".
DirectoryEntry toShutdown = new DirectoryEntry("LDAP://" + comboBox1.Text.ToString());
DirectorySearcher machineSearch = new DirectorySearcher(toShutdown);
//machineSearch.Filter = "(objectCatergory=computer)";
machineSearch.Filter = "(objectClass=computer)";
machineSearch.SearchScope = SearchScope.Subtree;
machineSearch.PropertiesToLoad.Add("name");
SearchResultCollection allMachinesCollected = machineSearch.FindAll();
Methods myMethods = new Methods();
string pcName;
foreach (SearchResult oneMachine in allMachinesCollected)
{
//pcName = oneMachine.Properties.PropertyNames.ToString();
pcName = oneMachine.Properties["name"].ToString();
MessageBox.Show(pcName);
}
Help much appreciated.

If you can upgrade to .NET 3.5, I would definitely recommend doing so.
With .NET 3.5, you get a new System.DirectoryServices.AccountManagement namespace which makes a lot of these takes much easier.
To find all computers and enumerate them, you'd do something like this:
// define a domain context - use your NetBIOS domain name
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "YOURDOMAIN");
// set up the principal searcher and give it a "prototype" of what you want to
// search for (Query by Example) - here: a ComputerPrincipal
PrincipalSearcher srch = new PrincipalSearcher();
srch.QueryFilter = new ComputerPrincipal(ctx);;
// do the search
PrincipalSearchResult<Principal> results = srch.FindAll();
// enumerate over results
foreach(ComputerPrincipal cp in results)
{
string computerName = cp.Name;
}
Check out the Managing Directory Security Principals in the .NET Framework 3.5 on MSDN Magazine for more information on the new S.DS.AM namespace and what it offers.
If you can't move up to .NET 3.5 - you just need to keep in mind that the .Properties["name"] that you get from the search result is a collection of values - so in order to grab the actual pc name, use this:
pcName = oneMachine.Properties["name"][0].ToString();
You need to index the .Properties["name"] collection with a [0] to get the first entry (typically also the only entry - hardly any computer has more than one name).

Related

Getting "NEXT VALUE FOR" for a SQL Server sequence using EF Core 3.1 - impossible?

I'm writing a new ASP.NET Core Web API, and one of my requirements is to be able to leverage EF Core 3.1 to grab the next value of a sequence defined in my SQL Server as the ID for a record I need to store.
I'm struggling to find a way to do this - in EF 6.x, I used a method directly on the DbContext descendant like this:
public int GetNextSequenceValue()
{
var rawQuery = Database.SqlQuery<int>("SELECT NEXT VALUE FOR dbo.TestSequence;");
var task = rawQuery.SingleAsync();
int nextVal = task.Result;
return nextVal;
}
and for EF Core up to 2.1, I would have been able to use Database.ExecuteSqlCommand() to run a SQL snippet and get back results. But it seems, in EF Core 3.x, I'm out of luck....
I know there are the .FromSqlRaw() and .FromSqlInterpolated methods on the DbSet - but since I only need to return the next value of a sequence (an INT), that's not going to fly. And I also know these methods also exist on the context.Database level which looks like it would be really close to what I had in EF 6.x - but here, those methods will only return the number of rows affected - I haven't found a way to send back the new value from the SEQUENCE.
Can it really be that in EF Core 3.x, I have to actually resort back to way-old ADO.NET code to fetch that value?? Is there REALLY no way to execute an arbitrary SQL snippet and get back some results from the context??
If you want to run an arbitrary TSQL batch and return a scalar value, you can do it like this:
var p = new SqlParameter("#result", System.Data.SqlDbType.Int);
p.Direction = System.Data.ParameterDirection.Output;
context.Database.ExecuteSqlRaw("set #result = next value for some_seq", p);
var nextVal = (int)p.Value;
Looks like executing raw SQL is not priority for EF Core, so up to now (EF Core 3.1) it's providing publicly just few basic limited methods. FromSql requires entity type or keyless entity type, and ExecuteSqlRaw / ExecuteSqlInterpolated are the "modern" bridge to ADO.NET ExecuteNonQuery which returns the affected rows.
The good thing is that EF Core is built on top of a public service architecture, so it can be used to add some missing functionalities. For instance, services can be used to build the so called IRelationalCommand, which has all the DbCommand execute methods, in particular ExecuteScalar needed for SQL in question.
Since EF Core model supports sequences, there is also a service for building the IRelationalCommand needed to retrieve the next value (used internally by HiLo value generators).
With that being said, following is a sample implementation of the custom method in question using the aforementioned concepts:
using System;
using System.Globalization;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Update;
namespace Microsoft.EntityFrameworkCore
{
public static partial class CustomExtensions
{
public static long GetNextSequenceValue(this DbContext context, string name, string schema = null)
{
var sqlGenerator = context.GetService<IUpdateSqlGenerator>();
var sql = sqlGenerator.GenerateNextSequenceValueOperation(name, schema ?? context.Model.GetDefaultSchema());
var rawCommandBuilder = context.GetService<IRawSqlCommandBuilder>();
var command = rawCommandBuilder.Build(sql);
var connection = context.GetService<IRelationalConnection>();
var logger = context.GetService<IDiagnosticsLogger<DbLoggerCategory.Database.Command>>();
var parameters = new RelationalCommandParameterObject(connection, null, null, context, logger);
var result = command.ExecuteScalar(parameters);
return Convert.ToInt64(result, CultureInfo.InvariantCulture);
}
}
}
In your fluent api configs you can create migration that set ID automatically to be next value from Sequence
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasSequence<int>("OrderNumbers");
modelBuilder.Entity<Order>()
.Property(o => o.OrderNo)
.HasDefaultValueSql("NEXT VALUE FOR shared.OrderNumbers");
}
For creating sequence:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasSequence<int>("OrderNumbers", schema: "shared")
.StartsAt(1000)
.IncrementsBy(5);
}
Read more from here: https://www.talkingdotnet.com/use-sql-server-sequence-in-entity-framework-core-primary-key/
For people suffering with oracle version of this problem, here's a solution:
var p = new OracleParameter("result", OracleDbType.Decimal, null, System.Data.ParameterDirection.Output);
Database.ExecuteSqlRaw($"BEGIN :result := my_seq.nextval; END;", p);
var nextVal = p.Value;
Ugly, but the best thing I could come up with:
var connection = repcontext.Database.GetDbConnection();
connection.Open();
using var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT NEXT VALUE FOR AA.TransSeq;";
var obj = cmd.ExecuteScalar();
connection.Close();
seqnum = (int)obj;
This code should work in a variety of situations:
public static class DbSequence
{
private const string sqlCode = "SELECT NEXT VALUE FOR {0}.{1};";
public static T GetNextSeq<T>(this DbContext dbContext, string seqName)
{
var sqlCnn = dbContext.Database.GetDbConnection();
bool cnnClosed = sqlCnn.State != ConnectionState.Open;
if (cnnClosed) sqlCnn.Open();
try
{
using (var sqlCmd = sqlCnn.CreateCommand())
{
sqlCmd.Transaction = dbContext.Database.CurrentTransaction?.GetDbTransaction();
sqlCmd.CommandText = string.Format(sqlCode, "dbo", seqName);
var result = sqlCmd.ExecuteScalar();
if ((result == null) || (result == DBNull.Value)) throw new InvalidOperationException();
return (T)result;
}
}
finally
{
if (cnnClosed) sqlCnn.Close();
}
}
}
This code works when the connection is closed, opening it when needed and closing it after itself. It should also work when a transaction has been initiated. According to this source: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-sequence-transact-sql?view=sql-server-ver16#general-remarks sequences run outside of transactions. Still, if it's available, I set up a transaction for the command. I also use generics and extension methods.

ldap nested group membership

My user is "SPR" and it is located under dc=aaaldap,dc=com
Now the filter i am trying to send is (IDEA: TO extract all groups to which user SPR belongs to)
Filter:
(&(objectclass=*)(memberof:1.2.840.113556.1.4.1941:=cn=SPR,dc=aaaldap,dc=com))
As part of this search result, i am getting response from AD server as ldapsearchresref (which to my understanding is an indication from ldap server that it is not able to find the entry in its server and thus giving a reference to a URL of another server which might help in resolving the entry).
My doubt is why it is not able to find any entry where in i am sure entry do exists?
Also, secondly i read somewhere that LDAP search filter doesn't work with commas. Can someone help me with this?
To fond all Groups a User is a member of including Nested groupsYou need to search the groups for the member attribute:
(member:1.2.840.113556.1.4.1941:=(cn=SPR,dc=aaaldap,dc=com))
-jim
If you need to find all the groups of that user belongs to you can user PrincipalContext.
Let me show you how
PrincipalContext pr = new PrincipalContext(ContextType.Domain, "aaaldap.com", "dc=aaaldap,dc=com", username, password);
List<string> lst = new List<string>();
UserPrincipal user = UserPrincipal.FindByIdentity(pr, DomainId);
if (user != null)
{
PrincipalSearchResult<Principal> results = user.GetGroups();
foreach (Principal p in results)
{
lst.Add(p.ToString());
}
lst.OrderBy(item => item.ToString());
}
pr.Dispose();
return lst;
I guess that is what you were looking for.
Cheers
When using LDAP and querying you can sometimes get a referring URL which means the account is known, but in a different domain. This happens when I query our global catalog, so I don't anymore. :)
This works on our domain here. Note I have commas in my filter.
private static void showMemberships()
{
// instantiate the DirectoryEntry instance with the FQDN of the domain to connect to
DirectoryEntry directoryObject = new DirectoryEntry("LDAP://CHILD.DOMAIN.ORG");
// create a DirectorySearcher object and pass the DirectoryEntry instance
DirectorySearcher ds = new DirectorySearcher(directoryObject);
// set search filter using LDAP query format
// this example fetches members for a group limiting to users only (not groups)
// and where the users are not in the stale objects ou
ds.Filter = "(&(objectCategory=User)(!ou=Stale Objects)(memberOf=CN=GROUPNAME,CN=Users,DC=CHILD,DC=DOMAIN,DC=ORG))";
// perform the search using the filter and store in a SearchResultsCollection
SearchResultCollection results = ds.FindAll();
// iterate through the results and do something with the info
foreach (SearchResult current in results)
{
string userId = current.Properties["cn"][0].ToString().Trim().ToUpper();
string userDn = current.Properties["distinguishedName"][0].ToString().Trim().ToUpper();
Console.Write(userId + " (" + userDn + ")\n");
}
// set the resource instances as released
directoryObject.Close();
directoryObject.Dispose();
}

EF ObjectQuery<T> Context, Parameters, Connection properties equivalent on DbSet<T>

In the earlier versions of Entity Framework, we were able to reach the Context out of ObjectQuery in order to read Parameters, Connection, etc. as below:
var query = (ObjectQuery<T>)source;
cmd.Connection = (SqlConnection)((EntityConnection)query.Context.Connection).StoreConnection;
cmd.Parameters.AddRange(
query.Parameters.Select(x => new SqlParameter(
x.Name, x.Value ?? DBNull.Value)
).ToArray()
);
When I look at the DbSet<T> object, I am unable to find any equivalent of this. My purpose here is to create extensions which will manipulate the query and get the result out of it.
Here is an instance: http://philsversion.com/2011/09/07/async-entity-framework-queries
Or should I write the extension for DbContext class and work with Set method?
Any idea?
Edit
Here is what I did so far. Basic implementation so far but certainly not ready for production. Any suggestions on this?
public static async Task<IEnumerable<T>> QueryAsync<T>(this DbContext #this, System.Linq.Expressions.Expression<Func<T, bool>> predicate = null)
where T : class {
var query = (predicate != null) ? #this.Set<T>().Where(predicate) : #this.Set<T>();
var cmd = new SqlCommand();
cmd.Connection = (SqlConnection)(#this.Database.Connection);
cmd.CommandText = query.ToString();
if (cmd.Connection.State == System.Data.ConnectionState.Closed) {
cmd.Connection.ConnectionString = new SqlConnectionStringBuilder(cmd.Connection.ConnectionString) {
AsynchronousProcessing = true
}.ToString();
cmd.Connection.Open();
}
cmd.Disposed += (o, e) => {
cmd.Clone();
};
var source = ((IObjectContextAdapter)#this).ObjectContext.Translate<T>(
await cmd.ExecuteReaderAsync()
);
return source;
}
This is a nice workaround, although I don't think you can make it much more generally applicable than what you already have.
A few things to keep in mind:
- Depending on the EF query, e.g. if you are using Include or not, the columns returned in the reader might not match the properties in the type T you are passsing.
- Depending on whether you have inheritance in your model, the T that you pass to translate may not always be the right thing to materialize for every row returned.
- After the task returned by ExecuteReaderAsync completes, you still have to retrieve each row, which depending on the execution plan for the query and the latency you are getting with the server is potentially also a blocking operation.
Async support is not coming to EF in 5.0 but we worked with other teams to make sure we have all the necessary building blocks included in .NET 4.5 and the feature is pretty high in our priority list. I encourage you to vote for it in our UserVoice site.

Find Group size in active directory

I have the following code. I get a directory entry for a user (strpath).
And then I get the groups where the user is listed.
How can I get the number of users in each group?
DirectoryEntry myDE = new System.DirectoryServices.DirectoryEntry(strpath);
object obGroups = myDE.Invoke("Groups");
foreach (object ob in (IEnumerable)obGroups)
{
DirectoryEntry obGpEntry = new DirectoryEntry(ob);
GroupsListBox.Items.Add(obGpEntry.Name );
}
If you're on .NET 3.5 (or can upgrade to it), there's a massively extended System.DirectoryServices.AccountManagement namespace that makes these jobs of managing user, groups and their memberships a whole lot easier.
Check out the MSDN article Managing Directory Security Principals in the .NET Framework 3.5 for an introduction to S.DS.AM.
You can get a user principal like this:
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "YOURDOMAIN");
UserPrincipal user = UserPrincipal.FindByIdentity("some user name");
PrincipalSearchResult<Principal> userGroups = user.GetGroups();
foreach (Principal p in myGroups)
{
GroupPrincipal gp = (p as GroupPrincipal);
if (gp != null)
{
int memberCount = gp.Members.Count;
}
}
This way, you can enumerate all groups a given user has, and enumerating those groups, you can find out how many members (users and other groups) each group has.

Best way to quickly determine whether a user account is a member of an AD group?

I currently have some code that pulls down a list of users in a group and then iterates through that group to determine if a given account exists, but it seems like there ought to be a more concise (and perhaps faster) way to accomplish this.
This code (VB.NET) attempts to use the member property of the group object, but it is returning false even when the user is a member of that group. Can anyone see what I am doing wrong here?
Dim group As DirectoryEntry = GetNetworkObject(GroupDomanName, NetworkObjectType.NetworkGroup, GroupName)
Dim user As DirectoryEntry =GetNetworkObject(UserDomainName, NetworkObjectType.NetworkUser, Login)
Return group.Properties("member").Contains(user.Path)
FYI: The GetNetworkObject calls just return a directoryEntry object, I have confirmed that the correct object is being returned for both the group and user object.
If you are on .NET 3.5 stack, System.DirectoryServices.AccountManagement.dll assembly has a nice API on top of AD. The following method can be implemented to solve your issue:
static bool IsUserMemberOf(string userName, string groupName)
{
using (var ctx = new PrincipalContext(ContextType.Domain))
using (var groupPrincipal = GroupPrincipal.FindByIdentity(ctx, groupName))
using (var userPrincipal = UserPrincipal.FindByIdentity(ctx, userName))
{
return userPrincipal.IsMemberOf(groupPrincipal);
}
}
// Usage:
bool result = IsUserMemberOf("CONTOSO\\john.doe", "CONTOSO\\Administrators");
I don't know how this method performs but it is a clean solution.
Here is what I've used in the past in a VBS Script that worked very well:
Set wshNet = CreateObject("WScript.Network") 'Setup connection to the Network
Set fso = CreateObject("Scripting.FileSystemObject") 'Create File System Object for any file manipulations
Set ADSysInfo = CreateObject("ADSystemInfo") 'Setup connection to Active Directory
Set CurrentUser = GetObject("LDAP://" & ADSysInfo.UserName) 'Setup current user to look for in Active Directory
strGroups = LCase(Join(CurrentUser.MemberOf)) 'Grabs all the groups the current user is a member of
I then use an InStr to see if the user is part of that group:
If InStr(strGroups, "MyGroup") Then MyGroupSub
You might be able to adapt the above in your project.
By the way, I noticed that in your code you have groupdoman as your last parameter for 'group' Not sure if you wanted that to be groupdomain or not:
Dim group As DirectoryEntry = GetNetworkObject(GroupDomanName, NetworkObjectType.NetworkGroup, GroupName, groupdoman)
vs
Dim group As DirectoryEntry = GetNetworkObject(GroupDomanName, NetworkObjectType.NetworkGroup, GroupName, groupdomain)
Let me know if this helps! JFV
I found an answer that seems to work in NET 2.0, is relatively quick and overcomes the possible issue of groups containing more than 100 items (which require range searching)
Here is the code that I wound up with:
Dim DSearcher As New DirectorySearcher(group, "(&(objectClass=user)(cn=" + Login + "))", New String() {"member;Range=0-5000"}, SearchScope.OneLevel)
group = GetNetworkObject(GroupDomanName, NetworkObjectType.NetworkGroup, GroupName)
user = GetNetworkObject(UserDomainName, NetworkObjectType.NetworkUser, Login)
DSearcher.AttributeScopeQuery = "member"
Return (DSearcher.FindOne() IsNot Nothing)
Here's another way using the directory searcher and memberOf. This is using the current users objectSID but you could change that to some other identifier.
dSearch.Filter = String.Format("(&(memberOf={0})(objectSid={1}))", groupDN, WindowsIdentity.GetCurrent.User)
Return dSearch.FindOne() IsNot Nothing
if your going to use user input which might contain invalid characters, you should always escape them...
searchName = searchName.Replace("\", "\5c"). _
Replace("/", "\2f"). _
Replace("*", "\2a"). _
Replace("(", "\28"). _
Replace(")", "\29")

Resources