Reading Group Policy Settings using C# - c#

How do I go about iterating over available and/or set settings in a given GPO (using name or GUID) in an AD domain? Without having to export to XML/HTML using powershell, etc.
I'm using C# (.NET 4.0).

That question got me hyped so I went to research it. So a +1
Some solutions I found from the top being the best to bottom being the worst
A good explanation with an example and example script!
This one, tells you to go through the registry but you gotta figure out who to access the AD
Pinvoke: Queries for a group policy override for specified power settings.

I had a similar problem, and didn't want to download and install the Microsoft GPO library (Microsoft.GroupPolicy.Management). I wanted to do it all with System.DirectoryServices. It took a little digging, but it can be done.
First retrieve your container using DirectorySearcher. You'll need to have already opened a directory entry to pass into the searcher. The filter you want is:
string filter = "(&" + "(objectClass=organizationalUnit)" + "(OU=" + container + "))";
and the property you're interested in is named "gPLink", so create an array with that property in it:
string[] requestProperties = { "gPLink" };
Now retrieve the results, and pull out the gPLink, if available.
using (var searcher = new DirectorySearcher(directory, filter, properties, SearchScope.Subtree))
{
SearchResultCollection results = searcher.FindAll();
DirectoryEntry entry = results[0].GetDirectoryEntry();
string gpLink = entry.Properties["gPLink"].Value;
If gpLink is null, there is no GPO associated with the container (OU).
Otherwise, gpLink will contain a string such as this:
"[LDAP://cn={31B2F340-016D-11D2-945F-00C04FB984F9},cn=policies,cn=system,DC=Test,DC=Domain;0]"
In the text above, you can see a CN for the GPO. All we need to do now is retrieve the GPO from the DC.
For that, we use a filter that looks like this:
string filter = "(&" +
"(objectClass=groupPolicyContainer)" +
"(cn={31B2F340-016D-11D2-945F-00C04FB984F9}))";
You'll want to create a Properties array that include the following:
Properties = { "objectClass", "cn", "distinguishedName", "instanceType", "whenCreated",
"whenChanged", "displayName", "uSNCreated", "uSNChanged", "showInAdvancedViewOnly",
"name", "objectGUID", "flags", "versionNumber", "systemFlags", "objectCategory",
"isCriticalSystemObject", "gPCFunctionalityVersion", "gPCFileSysPath",
"gPCMachineExtensionNames", "dSCorePropagationData", "nTSecurityDescriptor" };
Now use DirectorySearcher to retrieve the GPO. You'll get back a DirectoryEntry in the results that contains all of the above fields in the Properties collection. Some are COM objects, so you'll have to handle those appropriately.

Here is a better and more complete example then above.
class Program
{
static void Main(string[] args)
{
DirectoryEntry rootDse = new DirectoryEntry("LDAP://rootDSE");
DirectoryEntry root = new DirectoryEntry("GC://" + rootDse.Properties["defaultNamingContext"].Value.ToString());
DirectorySearcher searcher = new DirectorySearcher(root);
searcher.Filter = "(objectClass=groupPolicyContainer)";
foreach (SearchResult gpo in searcher.FindAll())
{
var gpoDesc = gpo.GetDirectoryEntry().Properties["distinguishedName"].Value.ToString();
Console.WriteLine($"GPO: {gpoDesc}");
DirectoryEntry gpoObject = new DirectoryEntry($"LDAP://{gpoDesc}");
try
{
Console.WriteLine($"DisplayName: {gpoObject.Properties["displayName"].Value.ToString()}");
}
catch
{
}
try
{
Console.WriteLine($"PCFileSysPath: {gpoObject.Properties["gPCFileSysPath"].Value.ToString()}");
}
catch
{
}
try
{
Console.WriteLine($"VersionNumber: {gpoObject.Properties["versionNumber"].Value.ToString()}");
}
catch
{
}
try
{
Console.WriteLine($"UserExtensionNames: {gpoObject.Properties["gPCUserExtensionNames"].Value.ToString()}");
}
catch
{
}
try
{
Console.WriteLine($"MachineExtensionNames: {gpoObject.Properties["gPCMachineExtensionNames"].Value.ToString()}");
}
catch
{
}
try
{
Console.WriteLine($"PCFunctionality: {gpoObject.Properties["gPCFunctionalityVersion"].Value.ToString()}");
}
catch
{
}
}
Console.ReadKey();
}
}

UPDATED: Working copy. You can now use c# to get read and parse a given GPO without having to use Powershell or write anything to disk.
using Microsoft.GroupPolicy;
var guid = new Guid("A7DE85DE-1234-F34D-99AD-5AFEDF7D7B4A");
var gpo = new GPDomain("Centoso.local");
var gpoData = gpo.GetGpo(guid);
var gpoXmlReport = gpoData.GenerateReport(ReportType.Xml).ToString();
using (XmlReader reader = XmlReader.Create(new StringReader(gpoXmlReport)))
{
string field;
while (reader.MoveToNextAttribute())
{
foreach (string attr in attributes)
{
// do something
}
}
}
This uses the Group Policy Management Console (GPMC) tools:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa814316(v=vs.85).aspx
Microsoft.GroupPolicy Namespace
https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.grouppolicy(v=vs.85).aspx

Related

C# code works from Console function but does not work in SQL CLR Stored Procedure

PLEASE HELP!!! I have a code to get data from AD. This used to work in SQL2014/Visual Studio2013. Now, we are migrating to SQL2016. I tested the code in a Console App and it worked just fine. It just does not work when I create the same code into a SQL CLR Stored Proc using Visual Studio 2017.
This is the code in the Console App:
static void Main(string[] args)
{
DirectoryEntry ldapConnection;
DirectorySearcher search;
SearchResult result;
DirectoryEntry ldapconnection = null;
string szJDEID = "";
string szErrorMessage = "";
string szNetworkID = "xyz";
//--- Create and return new LDAP connection with desired settings
ldapConnection = new DirectoryEntry("LDAP://DC.company.com:389", "userid", "password");
ldapConnection.Path = "LDAP://DC=company,DC=com";
ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
//--- create search object which operates on ldap connection object and set search object to only find the user specified
search = new DirectorySearcher(ldapconnection);
search.Filter = "(&(samaccountname=" + szNetworkID.Trim() + ")(memberOf=CN=JDEdwards Users,OU=Mail Enabled Manual,OU=Groups,OU=Global,DC=company,DC=com))";
result = search.FindOne();
if (result != null)
{
//--- create an array of properties that we would like and add them to the search object
string[] requiredproperties = new string[] { "extensionattribute13" };
foreach (string property in requiredproperties)
search.PropertiesToLoad.Add(property);
result = search.FindOne();
if (result != null)
{
foreach (string property in requiredproperties)
foreach (object mycollection in result.Properties[property])
szJDEID = mycollection.ToString();
}
}
else
{
szErrorMessage = "ERROR: This user does not belong to the JDEdwards Users AD Group. Please check with the IT Helpdesk team.";
}
}
I get the value of szJDEID as stored in Extension Attribute13. When I put the same code in a SQL CLR Stored Proc, the Logic always returns the szErrorMessage value.
What am I missing?
Thanks in advance.
Finally. As you had rightly pointed earlier, the bindDn was incorrect. The issue is we moved from one DC to another. I used lip.exe to find out the principal - userid#domain.com. Also, the ldapConnection.Path was not needed anymore as it was incorrect with the new DC. So, finally, it is working. Thanks Clint.

C# Active Directory Search

I have this powershell function and i want to make it as a C# function.
How can i put it into C#?
Get-ADComputer -filter {Name -Like 'myComp'} -property * | select DistinguishedName
You should be able to do this quite easily. Add a reference to System.DirectoryServices.AccountManagement and then use this code:
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, 'YourDomain'))
{
ComputerPrincipal computer = ComputerPrincipal.FindByIdentity (ctx, "name");
if (computer != null)
{
// do whatever you need to do with your computer principal
string distinguishedName = computer.DistinguishedName;
}
}
Update: if you don't know your domain ........ - you can also use:
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
in which case the principal context is created for the current domain you're located in.
You can use C# in the following manner
Connect to the Domain controller and get the DomainContext
Use that to look up the computer objects based on a name.
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, Environment.UserDomainName)
{
using (PrincipalSearcher srch = new PrincipalSearcher(new ComputerPrincipal(ctx) { Name = "ServerName"}))
{
return srch.FindAll().Cast<ComputerPrincipal>().ToList().Select(x => x.DistinguishedName);
}
}
Above returns a list of DistinguishedNames that matches the Server Name.
All the other answers suggest using the System.DirectoryServices.AccountManagement namespace. While that will work, it is really just a wrapper around the System.DirectoryServices namespace to make things a bit easier to use. It does make things easier (sometimes) but it does so at the cost of performance.
For example, in all the examples you've been given, your code will retrieve every attribute with a value from the computer object in AD, even though you only want one attribute.
If you use DirectorySearcher, you can make the search and retrieve only that one attribute that you want:
public string GetComputerDn(string computerName) {
var searcher = new DirectorySearcher {
Filter = $"(&(objectClass=computer)(sAMAccountName={computerName}$))",
PropertiesToLoad = { "distinguishedName" } //return only the distinguishedName attribute
};
var result = searcher.FindOne();
if (result == null) return null;
return (string) result.Properties["distinguishedName"][0];
}
Note that in AD, the sAMAccountName of computer objects is what you would normally refer to as the "computer name", followed by $, which is why the filter is what it is.
Please try this:
Add reference to Active Directory Services (%programfiles%\Reference Assemblies\Microsoft\Framework.NETFramework\\System.DirectoryServices.AccountManagement.dll)
public string GetComputerName(string computerName)
{
using (var context = new PrincipalContext(ContextType.Domain, "your domain name goes here"))
{
using (var group = GroupPrincipal.FindByIdentity(context, "Active Directory Group Name goes here"))
{
var computers = #group.GetMembers(true);
return computers.FirstOrDefault(c => c.Name == computerName).DistinguishedName;
}
}
return null; // or return "Not Found"
}

Retrieving complete information for the memberof (AD) attribute

I need to retrieve the complete information on the membership for an user. This was scripted in SSIS (Microsoft Visual Studio 10) with the script transformation editor component written in C#.
By the way, in the CONSOLE (cmd) if we retrieve with the dsget user "cn=...." -memberof I am able to retrieve all the groups of an user...
What I want is to get the membership like:
CN=Name1,OU=WZA,OU=WWWWW,DC=XXXX,DC=YYYY,DC=ZZZZ
CN=Name2,OU=WZA,OU=WWWWW,DC=XXXX,DC=YYYY,DC=ZZZY
CN=Name3,OU=WZA,OU=WWWWW,DC=XXXX,DC=YYYY,DC=ZZZA
what I am getting: (only the first line...)
CN=Name1,OU=WZA,OU=WWWWW,DC=XXXX,DC=YYYY,DC=ZZZZ
The code follows. How to change it to receive the complete information that AD holds in the memberOf attribute?
(in SSIS the attribute has a data type of unicode string with 3999 characters set as maximum, so truncation is hard to happen)
public class ScriptMain : UserComponent
{
public override void CreateNewOutputRows()
{
string directory = Variables.LDAPConnection;
string filter = Variables.LDAPFilter;
string[] desiredAttributes = { "memberOf",
"displayname"
};
using (DirectoryEntry searchRoot = new DirectoryEntry(directory))
using (DirectorySearcher searcher = new DirectorySearcher(searchRoot, filter, desiredAttributes))
{
searcher.PageSize = 100;
searcher.SearchScope = SearchScope.Subtree;
searcher.ReferralChasing = ReferralChasingOption.All;
using (SearchResultCollection results = searcher.FindAll())
{
foreach (SearchResult result in results)
{
Output0Buffer.AddRow();
if (result.Properties["memberOf"] != null && result.Properties["memberOf"].Count > 0)
{
Output0Buffer.memberOf= result.Properties["memberOf"][0].ToString();
}
if (result.Properties["displayname"] != null && result.Properties["displayname"].Count > 0)
{
Output0Buffer.displayname = result.Properties["displayname"][0].ToString();
}
}
}
}
Output0Buffer.SetEndOfRowset();
}
}
ps I do not know enough C#...
You are only using the first record in the memberOf attribute:
Output0Buffer.memberOf= result.Properties["memberOf"][0].ToString();
result.Properties["memberOf"] is an array. So you have to loop through that array and get each value.

Getting all startup items?

Ok, I guess I'm having a brain fart here and cant find my way out of it. What I'm trying to accomplish is to list all startup items (applications, processes, etc) and display them on a form (like what you get with msconfig.exe). I thought this code would do it:
private List<StartupItems> getStartupItems()
{
try
{
ManagementClass cls = new ManagementClass("Win32_StartupCommand");
ManagementObjectCollection coll = cls.GetInstances();
List<StartupItems> items = new List<StartupItems>();
foreach (ManagementObject obj in coll)
{
items.Add(
new StartupItems
{
Command = obj["Command"].ToString(),
Description = obj["Description"].ToString(),
Name = obj["Name"].ToString(),
Location = obj["Location"].ToString(),
User = obj["User"].ToString()
});
}
return items;
}
catch (Exception ex)
{
_message = ex.ToString();
_status = false;
return null;
}
But all that gets are the enabled ones with my username. What I'm trying to get is all items, either my username or system, all the enabled ones and all the disabled ones as well (just like msconfig). I've done tons of searching and cannot find anything really any different than what I'm using.
Have you considered reading directly from the registry?
One alternative would be to run autorunssc (it's the command-line version of autoruns) in the background and read its response.

How can I retrieve Active Directory users by Common Name more quickly?

I am querying information from Active Directory. I have code that works, but it's really slow.
This is the code I currently use:
static void Main(string[] args)
{
SearchResultCollection sResults = null;
try
{
//modify this line to include your domain name
string path = "LDAP://EXTECH";
//init a directory entry
DirectoryEntry dEntry = new DirectoryEntry(path);
//init a directory searcher
DirectorySearcher dSearcher = new DirectorySearcher(dEntry);
//This line applies a filter to the search specifying a username to search for
//modify this line to specify a user name. if you want to search for all
//users who start with k - set SearchString to "k"
dSearcher.Filter = "(&(objectClass=user))";
//perform search on active directory
sResults = dSearcher.FindAll();
//loop through results of search
foreach (SearchResult searchResult in sResults)
{
if (searchResult.Properties["CN"][0].ToString() == "Adit")
{
////loop through the ad properties
//foreach (string propertyKey in
//searchResult.Properties["st"])
//{
//pull the collection of objects with this key name
ResultPropertyValueCollection valueCollection =
searchResult.Properties["manager"];
foreach (Object propertyValue in valueCollection)
{
//loop through the values that have a specific name
//an example of a property that would have multiple
//collections for the same name would be memberof
//Console.WriteLine("Property Name: " + valueCollection..ToString());
Console.WriteLine("Property Value: " + (string)propertyValue.ToString());
//["sAMAccountName"][0].ToString();
}
//}
Console.WriteLine(" ");
}
}
}
catch (InvalidOperationException iOe)
{
//
}
catch (NotSupportedException nSe)
{
//
}
finally
{
// dispose of objects used
if (sResults != null)
sResults.Dispose();
}
Console.ReadLine();
}
What would faster code look like to get user information from AD?
You can call UserPrincipal.FindByIdentity inside System.DirectoryServices.AccountManagement:
using System.DirectoryServices.AccountManagement;
using (var pc = new PrincipalContext(ContextType.Domain, "MyDomainName"))
{
var user = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, "MyDomainName\\" + userName);
}
The reason why your code is slow is that your LDAP query retrieves every single user object in your domain even though you're only interested in one user with a common name of "Adit":
dSearcher.Filter = "(&(objectClass=user))";
So to optimize, you need to narrow your LDAP query to just the user you are interested in. Try something like:
dSearcher.Filter = "(&(objectClass=user)(cn=Adit))";
In addition, don't forget to dispose these objects when done:
DirectoryEntry dEntry
DirectorySearcher dSearcher
Well, if you know where your user lives in the AD hierarchy (e.g. quite possibly in the "Users" container, if it's a small network), you could also bind to the user account directly, instead of searching for it.
DirectoryEntry deUser = new DirectoryEntry("LDAP://cn=John Doe,cn=Users,dc=yourdomain,dc=com");
if (deUser != null)
{
... do something with your user
}
And if you're on .NET 3.5 already, you could even use the vastly expanded System.DirectorySrevices.AccountManagement namespace with strongly typed classes for each of the most common AD objects:
// bind to your domain
PrincipalContext pc = new PrincipalContext(ContextType.Domain, "LDAP://dc=yourdomain,dc=com");
// find the user by identity (or many other ways)
UserPrincipal user = UserPrincipal.FindByIdentity(pc, "cn=John Doe");
There's loads of information out there on System.DirectoryServices.AccountManagement - check out this excellent article on MSDN by Joe Kaplan and Ethan Wilansky on the topic.
You can simplify this code to:
DirectorySearcher searcher = new DirectorySearcher();
searcher.Filter = "(&(objectCategory=user)(cn=steve.evans))";
SearchResultCollection results = searcher.FindAll();
if (results.Count == 1)
{
//do what you want to do
}
else if (results.Count == 0)
{
//user does not exist
}
else
{
//found more than one user
//something is wrong
}
If you can narrow down where the user is you can set searcher.SearchRoot to a specific OU that you know the user is under.
You should also use objectCategory instead of objectClass since objectCategory is indexed by default.
You should also consider searching on an attribute other than CN. For example it might make more sense to search on the username (sAMAccountName) since it's guaranteed to be unique.
I'm not sure how much of your "slowness" will be due to the loop you're doing to find entries with particular attribute values, but you can remove this loop by being more specific with your filter. Try this page for some guidance ... Search Filter Syntax

Categories