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.
Related
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.
We have many different pipelines in Azure data factory with many data sets. Mainly we have data sets of Azure data lake store and Azure Blobs. I want to know the file size of all files (from all datasets of all pipelines). I am able to iterate all the datasets from all the pipeline using DataFactoryManagementClient in C# but when I am trying to see fileName or folderName of the dataset, I am getting null. You can see my below code -
private static void GetDataSetSize(DataFactoryManagementClient dataFactoryManagementClient)
{
string resourceGroupName = "resourceGroupName";
foreach (var dataFactory in dataFactoryManagementClient.DataFactories.List(resourceGroupName).DataFactories)
{
var linkedServices = new List<LinkedService>(dataFactoryManagementClient.LinkedServices.List(resourceGroupName, dataFactory.Name).LinkedServices);
var datasets = dataFactoryManagementClient.Datasets.List(resourceGroupName, dataFactory.Name).Datasets;
foreach (var dataset in datasets)
{
var lsTypeProperties = linkedServices.First(ls => ls.Name == dataset.Properties.LinkedServiceName).Properties.TypeProperties;
if(lsTypeProperties.GetType() == typeof(AzureDataLakeStoreLinkedService))//AzureDataLakeStoreLinkedService))
{
AzureDataLakeStoreLinkedService outputLinkedService = lsTypeProperties as AzureDataLakeStoreLinkedService;
var folder = GetBlobFolderPathDL(dataset);
var file = GetBlobFileNameDL(dataset);
}
}
}
}
public static string GetBlobFolderPathDL(Dataset dataset)
{
if (dataset == null || dataset.Properties == null)
{
return string.Empty;
}
AzureDataLakeStoreDataset dlDataset = dataset.Properties.TypeProperties as AzureDataLakeStoreDataset;
if (dlDataset == null)
{
return string.Empty;
}
return dlDataset.FolderPath;
}
public static string GetBlobFileNameDL(Dataset dataset)
{
if (dataset == null || dataset.Properties == null)
{
return string.Empty;
}
AzureDataLakeStoreDataset dlDataset = dataset.Properties.TypeProperties as AzureDataLakeStoreDataset;
if (dlDataset == null)
{
return string.Empty;
}
return dlDataset.FileName;
}
With this, I want to generate monitoring tool which will tell me how data is increasing for each file/dataset?
FYI - I am going to monitor retries, failures of each slice. I can get this information without any issue, but now the problem is about getting the file name and folder path because it's returning me null(It seems to be a bug in API). Once I have folder and file path, then using DataLakeStoreFileSystemManagementClient I will get the file size of those files. I am planning to ingest all this data (size, fileName, retries, failure etc) into SQL database and on top of it - I will generate reports which will tell me how my data is growing daily or hourly etc.
I want to make it generic, in such a way that - if in future I add new dataset or pipeline - I get the size of all newly added datasets also without changing any code.
Please help me how can I achieve this. Suggest me if there is an alternate way if possible.
Just place this code in your main method and execute.You may able to see your datasets folderpath and filenames.Use this and change accordingly to your requirement.
Hope this helps!
foreach (var dataFactory in dataFactoryManagementClient.DataFactories.List(resourceGroupName).DataFactories)
{
var datasets = dataFactoryManagementClient.Datasets.List(resourceGroupName, dataFactory.Name).Datasets;
foreach (var dataset in datasets)
{
var lsTypeProperties = dataFactoryManagementClient.Datasets.Get(resourceGroupName,dataFactory.Name,dataset.Name);
if (lsTypeProperties.Dataset.Properties.TypeProperties.GetType() == typeof(AzureDataLakeStoreDataset))//AzureDataLakeStoreDataset))
{
AzureDataLakeStoreDataset OutputDataSet = lsTypeProperties.Dataset.Properties.TypeProperties as AzureDataLakeStoreDataset;
Console.WriteLine(OutputDataSet.FolderPath);
Console.WriteLine(OutputDataSet.FileName);
Console.ReadKey();
}
}
}
I've read plenty of similar StackOverflow questions but none seem to address the issue I'm seeing. If I query for a user using userprincipalname I get back a search result with exactly 34 properties. None of the custom properties are returned. If I query again using a custom property like employeeNumber I get back a result with 71 properties. All custom properties are included.
My issue is that I don't have the employeeNumber at run time, just the userprincipalname. I need to get back all of the custom properties all of the time. Hopefully that makes sense. Here's my practice code:
string sid = "";
using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
{
UserPrincipal user = UserPrincipal.Current;
//sid = user.SamAccountName;
sid = user.UserPrincipalName;
//sid = user.Sid.ToString();
DirectoryEntry entry = user.GetUnderlyingObject() as DirectoryEntry;
if (entry.Properties.Contains("employeeNumber"))
{
//this doesn't work
}
}
DirectoryEntry ldapConnection = new DirectoryEntry("companyname.com");
ldapConnection.Path = "LDAP://DC=companyname,DC=com";
ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
DirectorySearcher search = new DirectorySearcher(ldapConnection);
search.Filter = string.Format("(&(ObjectClass=user)(userprincipalname={0}))", sid); // <-- this doesn't get custom properties
//search.Filter = string.Format("(employeeNumber={0})", "11663"); <-- this works
var result = search.FindOne(); // FindOne();
if (result.Properties.Contains("employeeNumber"))
{
//this never happens either :(
}
The above never returns the employeeNumber field, but if I uncomment the second search.Filter line and manually search by employeeNumber I find a result and it contains all of the fields that I need.
EDIT: I found a very good MSDN article Here which describes how to extend the UserPrincipal object to get custom attributes. The only issue is that it's giving me an empty string every time I access it even though I have verified that the property IS set in AD! Any help is appreciated.
EDIT 2: Here's the code for the custom principal extension:
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("Person")]
public class UserPrincipalExtension : UserPrincipal
{
public UserPrincipalExtension(PrincipalContext context)
: base(context)
{
}
public UserPrincipalExtension(PrincipalContext context, string samAccountName, string password, bool enabled)
: base(context, samAccountName, password, enabled)
{
}
public static new UserPrincipalExtension FindByIdentity(PrincipalContext context, IdentityType type, string identityValue)
{
return (UserPrincipalExtension)FindByIdentityWithType(context, typeof(UserPrincipalExtension), type, identityValue);
}
PersonSearchFilter searchFilter;
new public PersonSearchFilter AdvancedSearchFilter
{
get
{
if (searchFilter == null)
searchFilter = new PersonSearchFilter(this);
return searchFilter;
}
}
[DirectoryProperty("employeeNumber")]
public string EmployeeNumber
{
get
{
if (ExtensionGet("employeeNumber").Length != 1)
return string.Empty;
return (string)ExtensionGet("employeeNumber")[0];
}
set
{
ExtensionSet("employeeNumber", value);
}
}
}
And the custom search filter:
public class PersonSearchFilter : AdvancedFilters
{
public PersonSearchFilter(Principal p)
: base(p)
{
}
public void SAMAccountName(string value, MatchType type)
{
this.AdvancedFilterSet("sAMAccountName", value, typeof(string), type);
}
}
Usage:
UserPrincipalExtension filter = new UserPrincipalExtension(context);
filter.AdvancedSearchFilter.SAMAccountName(UserPrincipal.Current.SamAccountName, MatchType.Equals);
PrincipalSearcher search = new PrincipalSearcher(filter);
foreach (var result in search.FindAll())
{
var q = (UserPrincipalExtension)result;
var m = q.EmployeeNumber;
}
var m is always an empty string even though ALL AD entries have an employeeNumber.
EDIT: From active directory:
I am more confidant in how to fix your second method so I will answer that first. You need to specify the property in DirectorySearcher.PropertiesToLoad when you do your search for the property to show up.
DirectorySearcher search = new DirectorySearcher(ldapConnection);
search.Filter = string.Format("(&(ObjectClass=user)(userprincipalname={0}))", sid);
search.PropertiesToLoad.Add("employeeNumber");
var result = search.FindOne(); // FindOne();
if (result.Properties.Contains("employeeNumber"))
{
//This should now work.
}
The reason it worked for string.Format("(employeeNumber={0})", "11663"); is because any search clause you add automatically gets put in to the PropertiesToLoad collection.
For your first method, I think you need to call DirectoryEntry.RefreshCache and pass in the property to make it show up.
string sid = "";
using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
{
UserPrincipal user = UserPrincipal.Current;
//sid = user.SamAccountName;
sid = user.UserPrincipalName;
//sid = user.Sid.ToString();
DirectoryEntry entry = user.GetUnderlyingObject() as DirectoryEntry;
entry.RefreshCache(new[] {"employeeNumber"});
if (entry.Properties.Contains("employeeNumber"))
{
}
}
But I am not 100% sure if that will work or not.
For your custom user principal, I am not sure what is wrong. The only difference I see between what you are doing and what I have in a project that does it that I know works is you use [DirectoryObjectClass("Person")] but I have [DirectoryObjectClass("user")]. Perhaps that is the issue
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("user")] //Maybe this will fix it???
public class UserPrincipalExtension : UserPrincipal
{
I am writing some ETL code to move data between an external system and SharePoint Online.
I am using the nuget package Microsoft.SharePointOnline.CSOM to communicate with SP in C#.
I am using the following code to update my field values.
spListItem[fieldName] = "Test Value";
spListItem.Update();
spClientContext.ExecuteQuery();
I noticed with Choice fields, if I save a non existing value SharePoint does not complain and just adds the value even if Allow 'Fill-in' choices is set to NO.
Is there a validate function anywhere in SharePoint? I saw some methods like ValidateUpdateListItem, but they didn't seem to do what I needed.
You could consider to validate choice field value before saving its value as demonstrated below:
static class ListItemExtensions
{
public static bool TryValidateAndUpdateChoiceFieldValue(this ListItem item, string fieldName, string fieldValue)
{
var ctx = item.Context;
var field = item.ParentList.Fields.GetByInternalNameOrTitle(fieldName);
ctx.Load(field);
ctx.ExecuteQuery();
var choiceField = ctx.CastTo<FieldChoice>(field);
if (!choiceField.FillInChoice)
{
var allowedValues = choiceField.Choices;
if (!allowedValues.Contains(fieldValue))
{
return false;
}
}
item.Update();
return true;
}
}
In that case the ListItem will be updated once the validation is
succeeded.
Usage
using (var ctx = new ClientContext(webUri))
{
var list = ctx.Web.Lists.GetByTitle(listTitle);
var listItem = list.GetItemById(itemId);
if(listItem.TryValidateAndUpdateChoiceFieldValue(fieldName,fieldValue))
ctx.ExecuteQuery();
}
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