Showing posts with label Active Directory. Show all posts
Showing posts with label Active Directory. Show all posts

November 18, 2009

Add Uses to Group in Bulk using C#

So I had a task to take a list of usernames in a text document and add them to AD and map them to group(s). I am sure I could have done this quickly in Powershell, but I wanted to do it in C#. You can get the code here. The application is console based and uses some of our past AD classes.

The format was simple:
user1
user2
user3
A snippet of the code is below:
//using log4net;
//using ActiveDs;
public void Run()
{
foreach (AdUser user in ReadUsers())
{
using (user)
{
Dictionary userGroups = GetUserGroupsDn(user.Entry);

foreach (string groupDn in _groupsToAddUserTo.Keys)
{
if (userGroups.ContainsKey(groupDn))
continue; // User already in this group

AdGroup group = _groupsToAddUserTo[groupDn];

try
{
group.NativeObject.Add(user.Entry.Path);

log.Info(string.Format("User: {0} added to Group: {1}",
user.Entry.Properties["sAMAccountName"][0], group.NativeObject.Name));
}
catch (Exception)
{
log.Error(string.Format("User: {0} can't be added to Group: {1}. Possibly this is it's primary group already.",
user.Entry.Properties["sAMAccountName"][0], group.NativeObject.Name));
}
}
}
}
}
...and main:
 private void Run(string[] args)
{

//logging removed for brevity in snippet

Settings settings = SettingsReader.ReadFromConfig();

Adder adder = new Adder(settings);
adder.UsersToProcess = ReadUsersToProcess(settings.UsersFilePath);

adder.Run();
}
Anyone want to show how this can be done in powershell in 25 or so lines?

October 5, 2009

Working with Active Directory and C# -- sample code

After 8 posts on this, I think it makes sense to just give the link to the files here:

The ADLib code can be found here. This contains:

1. AdGroup: interface (IADsGroup) for group
2. AdUser: interface (IADsUser) for user
3. GroupType: enum of group types in AD
4. AdDomain: Policy, Password, Group and User Interaction within AD
5. Go here for the dotnetdevguide...and buy the book as well!

Active Directory and C# VII

Time to continue this series of posts on the domain side. You can download all the files from here. I will upload this file from this post this week. You can see the first post related to this class file here. This post refers to policy and password in AD.
 public DateTime GetPasswordLastChanged(DirectoryEntry entry)
{
return ((IADsUser)entry.NativeObject).PasswordLastChanged;
}

public DateTime GetPasswordExpirationDate(DirectoryEntry entry)
{
return ((IADsUser)entry.NativeObject).PasswordExpirationDate;
}

public bool ChangePasswordAtNextLogon(DirectoryEntry entry)
{
// MSDN: pswLastSet
// If this value is set to 0 and the User-Account-Control attribute
// does not contain the UF_DONT_EXPIRE_PASSWD flag, then the user must
// set the password at the next logon.

if (PasswordNeverExpires(entry))
return false;

Int64 val = 0;

try
{
val = UnboxAdsiInt64(entry.Properties["pwdLastSet"].Value);
}
catch (Exception)
{
val = 0;
}

return (val == 0);
}

public bool PasswordNeverExpires(DirectoryEntry entry)
{
return GetUserAccountControlFlag(entry, ADS_USER_FLAG.ADS_UF_DONT_EXPIRE_PASSWD);
}

public bool GetUserAccountControlFlag(DirectoryEntry entry, ADS_USER_FLAG flag)
{
int userAccountControl = (int)entry.Properties["userAccountControl"].Value;
return (userAccountControl & (int)flag) != 0;
}

///
/// Gets the Password Expiration
/// Date for a domain user
/// Returns MaxValue if never expiring
/// Returns MinValue if user must
/// change password at next logon
///

///
///
public DateTime GetPasswordExpiration(DirectoryEntry user)
{
return new PasswordExpires(GetPolicy()).GetExpiration(user);
}

private DomainPolicy GetPolicy()
{
lock(this)
{
if(_policy == null)
_policy = new DomainPolicy(OpenRootDSE());

return _policy;
}
}

October 2, 2009

Active Directory and C# VI

Time to continue this series of posts on the domain side. You can download all the files from here. I will upload this file from this post this week. You can see the first post related to this class file here. This post once again refers to the sAMAccount, Groups and SID administration in AD.

public DirectoryEntry OpenEntryBySAMAccountName(string sAMAccountName)
{
//from config
string rootPath = SearchPath;
String filter = string.Format("(&(|(objectClass=user)(objectClass=group))(sAMAccountName={0}))", sAMAccountName);
string[] propsToLoad = new string[0];

using (DirectoryEntry entry = OpenExistingEntry(rootPath))
{
if (entry == null)
throw new Exception(string.Format("Failed to open root for search by SAMAccountName. {0}", rootPath));

using (DirectorySearcher searcher = new DirectorySearcher(entry, filter, propsToLoad))
{
SearchResult sr = searcher.FindOne();

if (sr == null)
return null;

return sr.GetDirectoryEntry();
}
}
}

public List GetAllGroupsSAMAccountNames()
{
List adGroups = GetAllGroups(new string[] { "sAMAccountName" });

try
{
List groups = new List();

foreach (AdGroup adGroup in adGroups)
groups.Add((string)adGroup.Entry.Properties["sAMAccountName"].Value);

return groups;
}
finally
{
Dispose(adGroups);
}
}

public List GetAllGroups(string[] propsToLoad)
{
//from config
string rootPath = GroupsSearchPath;
String filter = "(objectClass=group)";
List groups = new List();

using (DirectoryEntry entry = OpenExistingEntry(rootPath))
{
if (entry == null)
throw new Exception(string.Format("Root to search groups by SAMAccountName failed to open. {0}", rootPath));

using (DirectorySearcher searcher = new DirectorySearcher(entry, filter, propsToLoad))
{
using (SearchResultCollection src = searcher.FindAll())
{
foreach (SearchResult sr in src)
{
groups.Add(new AdGroup(sr.GetDirectoryEntry()));
}
}
}
}

return groups;
}

private DirectoryEntry OpenRootDSE()
{
return OpenEntry("rootDSE");
}

public static Int64 UnboxAdsiInt64(object ADsLargeInteger)
{
IADsLargeInteger val = (IADsLargeInteger)ADsLargeInteger;
Int64 res = ((Int64)val.HighPart <<>
Turns out I need one more post for this to discuss password expiration and policy. Then this huge class file will be uploaded for you to use!

October 1, 2009

Active Directory and C# V

Time to continue this series of posts on the domain side. You can download all the files from here. I will upload this file from this post this week. You can see the first post related to this class file here. This post refers to the sAMAccount, Groups and SID administration in AD.
 
public AdUser OpenUserBySAMAccountName(string userSAMAccountName, params string[] propsToLoad)
{
//usersearchpath from config
string rootPath = UsersSearchPath;
String filter = string.Format("(&(objectClass=user)(sAMAccountName={0}))", userSAMAccountName);

using (DirectoryEntry entry = OpenExistingEntry(rootPath))
{
if (entry == null)
throw new Exception(string.Format("Root to search user by SAMAccountName failed to open. {0}", rootPath));

using (DirectorySearcher searcher = new DirectorySearcher(entry, filter, propsToLoad))
{
SearchResult sr = searcher.FindOne();

if (sr == null)
return null;

return new AdUser(sr.GetDirectoryEntry());
}
}
}

public string GetSidPath(DirectoryEntry entry)
{
byte[] sidBytes = (byte[])entry.Properties["objectSid"][0];
SecurityIdentifier sid = new SecurityIdentifier(sidBytes, 0);
return string.Format("LDAP://", sid.ToString());
}

public List GetUserGroups(DirectoryEntry user)
{
List groups = new List();

//we are building an '|' clause
List sids = new List();
user.RefreshCache(new string[] { "tokenGroups" });
foreach (byte[] sid in user.Properties["tokenGroups"])
{
//append each member into the filter
sids.Add(string.Format("(objectSid={0})", BuildFilterOctetString(sid)));
}

if (sids.Count == 0)
return groups;

//end our initial filter
string filter = "(|" + string.Join("", sids.ToArray()) + ")";
//GroupsSearchPath from config
DirectoryEntry searchRoot = OpenEntry(GroupsSearchPath);
using (searchRoot)
{

//we now have our filter, we can just search for the groups
DirectorySearcher ds = new DirectorySearcher(
searchRoot,
filter
);

using (SearchResultCollection src = ds.FindAll())
{
foreach (SearchResult sr in src)
{
groups.Add((string)sr.Properties["samAccountName"][0]);
}
}
}

return groups;
}

private string BuildFilterOctetString(byte[] bytes)
{
StringBuilder sb = new StringBuilder();

for (int i = 0; i < bytes.Length; i++)
{
sb.AppendFormat("\\{0}", bytes[i].ToString("X2"));
}

return sb.ToString();
}


I'll upload this file tomorrow. This will make it a lot easier to follow.

September 29, 2009

Active Directory and C# IV

To continue this series of posts, I'd now like to talk about groups and my group helper class. It is actually quite simple and kind of looks just like the User helper. You can download all the files from here. I will upload this file from this post this week. You can see the first post related to this class file here. This post refers to the groups administration in AD.
  public AdGroup OpenGroup(string groupName)
{
if (groupName.IndexOf('=') != -1)
return OpenGroupByDN(groupName);
else
{
//GroupsSearchPath from config
if (string.IsNullOrEmpty(GroupsSearchPath))
throw new Exception("To search groups by SAMAccountName the PathToSearchForGroupsBySAMAcountName setting should be specified.");

return OpenGroupBySAMAccountName(groupName);
}
}

public AdGroup OpenGroupByDN(string groupDN)
{
DirectoryEntry entry = OpenExistingEntry(groupDN);

if (entry == null)
return null;

return new AdGroup(entry);
}

public AdGroup OpenGroupBySAMAccountName(string groupSAMAccountName)
{
//GroupsSearchPath from config
string rootPath = GroupsSearchPath;

String filter = string.Format("(&(objectClass=group)(sAMAccountName={0}))", groupSAMAccountName);
string[] propsToLoad = new string[0];

using (DirectoryEntry entry = OpenExistingEntry(rootPath))
{
if (entry == null)
throw new Exception(string.Format("Root to search groups by SAMAccountName failed to open. {0}", rootPath));

using (DirectorySearcher searcher = new DirectorySearcher(entry, filter, propsToLoad))
{
SearchResult sr = searcher.FindOne();

if (sr == null)
return null;

return new AdGroup(sr.GetDirectoryEntry());
}
}
}
I'll post one more time on this and also upload the file for you to download.

September 25, 2009

Active Directory and C# III

To continue this series of posts, I'd now like to talk about groups and my group helper class. It is actually quite simple and kind of looks just like the User helper. You can download all the files from here. I will upload this file from this post next week.

Before we get started, I wanted to tell you all that if you do any AD programming you should look at a book written by Joe Kaplan and Ryan Dunn . It is really the best book out there. The authors also have a site here for you to look at and a forum which is helpful. My next bit of code actually relies on code from their book. They have the code available on thei site and it can be found here. You can use the entire build or just some of the files. The main one for me was PasswordExpires (Listing 10.8, 10.9, & 10.10 in full). I use a few others in the root of their project as well ... it might be just easier to include their project. Go out and buy the book though...it is great!
using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices;
using System.Security.Principal;
using DotNetDevGuide.DirectoryServices;
using ActiveDs;
using DotNetDevGuide.DirectoryServices.Chapter10;

namespace AdLib
{
public class AdDomain
{
private const int ADS_UF_ACCOUNTDISABLE = 2;
private DomainPolicy _policy;

//UsersSearchPath can be from a config location
public void CreateUser(string userName, Dictionary props)
{
CreateUser(userName, UsersSearchPath, props);
}

public void CreateUser(string userName, string path, Dictionary props)
{
path = GetFullPath(path);

using (DirectoryEntry parent = OpenEntry(path))
{
DirectoryEntry user = parent.Children.Add(
String.Format("CN={0}", userName),
"user"
);

using (user)
{
// Set default props
user.Properties["sAMAccountName"].Add(userName);
user.CommitChanges();


// Set user defined props
foreach (string propName in props.Keys)
{
if (propName.ToLower() == "password")
continue;

user.Properties[propName].Add(props[propName]);
user.CommitChanges();
}

if (props.ContainsKey("password"))
((IADsUser)user.NativeObject).SetPassword((string)props["password"]);

user.CommitChanges();

EnableAccount(user);
}
}
}

private void EnableAccount(DirectoryEntry entry)
{
int userAccountControl = (int)entry.Properties["userAccountControl"][0];
userAccountControl &= ~ADS_UF_ACCOUNTDISABLE;
entry.Properties["userAccountControl"][0] = userAccountControl;
entry.CommitChanges();
}

//GroupsSearchPath can be from a config location
public void CreateGroup(string groupName, int type)
{
CreateGroup(groupName, type, GroupsSearchPath);
}

public void CreateGroup(string groupName, int type, string groupOU)
{
groupOU = GetFullPath(groupOU);

using (DirectoryEntry parent = OpenEntry(groupOU))
{
DirectoryEntry group = parent.Children.Add(
String.Format("CN={0}", groupName),
"group"
);

using (group)
{
group.Properties["sAMAccountName"].Add(groupName);

if(type != (int)GroupType.Unknown)
group.Properties["groupType"].Add(type);

group.CommitChanges();
}
}
}

private string GetFullPath(string subPath)
{
//server from config
if (string.IsNullOrEmpty(Server))
return string.Format("LDAP://{0}", subPath);
else
//server from config
return string.Format("LDAP://{0}/{1}", Server, subPath);
}

public DirectoryEntry OpenEntry(string path)
{
if (path.StartsWith("LDAP://", StringComparison.InvariantCultureIgnoreCase) == false)
{
path = GetFullPath(path);
}

return new DirectoryEntry(
path,
Username, //from config
Password, //from config
AuthenticationTypes.Secure
);
}
I'll finish this class up next week.

September 24, 2009

Active Directory and C# II

To continue this series of posts, I'd now like to talk about groups and my group helper class. It is actually quite simple and kind of looks just like the User helper. You can download all the files from here.
using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices;
using ActiveDs;

namespace AdLib
{
public class AdGroup : IDisposable
{
private DirectoryEntry _entry;
private IADsGroup _group;

public AdGroup(DirectoryEntry entry)
{
if (entry == null)
throw new ArgumentNullException("entry");

_entry = entry;
_group = (IADsGroup)entry.NativeObject;
}

public DirectoryEntry Entry
{
get { return _entry; }
}

public IADsGroup NativeObject
{
get { return _group; }
}

#region IDisposable Members

public void Dispose()
{
_entry.Dispose();
}

void IDisposable.Dispose()
{
Dispose();
}

#endregion
}
}
Of course, we would also need to understand what a GroupType in AD really is:
using System;
using System.Collections.Generic;
using System.Text;

namespace AdLib
{
[Flags]
public enum GroupType : int
{
Unknown = 0,
LocalDistribution = 4,
LocalSecurity = (4 | -2147483648),
GlobalDistribution = 2,
GlobalSecurity = (2 | -2147483648),
UniversalDistribution = 8,
UniversalSecurity = (8 | -2147483648)
}
}
In the next post, we will talk about domains and eventually how this all fits together.

September 23, 2009

Active Directory and C#

I have done a lot of work in the last few years with AD and C#. I figured now that I shared a whole bunch of AzMan stuff, it is only logical that I share some AD helper classes as well. Some of the code can of course be written differently now in the world of .NET 3.5. However, what is written is still relevant and works well. Like the AzMan posts, this will be a bunch of posts tied together to make an application. Unlike AzMan, this will be slower since there is a A LOT more in AD than in AzMan.

One thing that I'll point out is that I rely on ActiveDS more than DirectoryEntry....so for those against this, I am sorry :)

I'd like to start off with the AdUser class that I use from my AdLibrary which you can download from here:
using System;
using System.Collections.Generic;
using System.Text;
using ActiveDs;
using System.DirectoryServices;

namespace AdLib
{
public class AdUser : IDisposable
{
private DirectoryEntry _entry;
private IADsUser _user;

public AdUser(DirectoryEntry entry)
{
if (entry == null)
throw new ArgumentNullException("entry");

_entry = entry;
_user = (IADsUser)entry.NativeObject;
}

public DirectoryEntry Entry
{
get { return _entry; }
}

public IADsUser NativeObject
{
get { return _user; }
}

#region IDisposable Members

public void Dispose()
{
_entry.Dispose();
}

void IDisposable.Dispose()
{
Dispose();
}

#endregion
}
}
To be honest, not much can be done with this. However, combined with all the other classes in the upcoming posts you will see how it all ties together.

July 25, 2009

Beta program AD Webservice and Powershell

I just came across this today...read about it here

July 16, 2009

A better Ldap Error Message

Yesterday, I showed a simple way to do Ldap login using C# for your .NET application. To expand on that a drop, I wanted to show how exactly you use that class with a custom formatted and cleaned up Error Message.

First, here is how you use the class from yesterday (pseudocode):
string user = //passed in from somewhere;
string password = //passed in from somewhere;
string domain = //get from config or passed in from somewhere;

try {
// Note: The server is not really needed in our original class...I added it just in case
LdapLogin.VerifyCredentials(adSettings.Server, user, domain, password);

//return success
}
catch {LdapException ex) {
LdapErrorMessage error = new LdapErrorMessage(ex);
//return string.Format("Error. {0} {1} {2}", ex.Message, error.Description, ex.ServerErrorMessage)
Let's focus on the LdapErrorMessage part in the catch. The typical LdapException returns things that may not be exactly what you want to see or even what the error really is. Reading the hex code that is returned back, you can actually get a better error message like "User not found" or "Not permitted to logon at this time.". Here is the class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices.Protocols;
using System.Text.RegularExpressions;
using System.Globalization;

namespace JLFramework
{
public class LdapErrorMessage
{
public string Message { get; set; }
public int Code { get; set; }
public string LdapErr { get; set; }
public string Comment { get; set; }
public int Data { get; set; }
public string Version { get; set; }

/// 80090308: LdapErr: DSID-0C09030B, comment: AcceptSecurityContext error, data 525, v893
/// 80090308: LdapErr: DSID-0C090334, comment: AcceptSecurityContext error, data 0, vece
public LdapErrorMessage(LdapException ex)
{
if (string.IsNullOrEmpty(ex.ServerErrorMessage))
return;

this.Message = ex.ServerErrorMessage;

string pattern = @"(.*): LdapErr: (.*), comment: (.*), data (.*), (.*)";
Match match = Regex.Match(ex.ServerErrorMessage, pattern, RegexOptions.IgnoreCase);

if (match.Success)
{
this.Code = ParseHex(match.Groups[1].Value);
this.LdapErr = match.Groups[2].Value;
this.Comment = match.Groups[3].Value;
this.Data = ParseHex(match.Groups[4].Value);
this.Version = match.Groups[5].Value;
}
}

public string Description
{
get
{
// http://wiki.caballe.cat/index.php/Active_Directory_LDAP_Errros
switch (this.Data)
{
case 0x525: return "User not found.";
case 0x52e: return "Invalid credentials.";
case 0x530: return "Not permitted to logon at this time.";
case 0x532: return "Password expired.";
case 0x533: return "Account disabled.";
case 0x701: return "Account expired.";
case 0x773: return "User must reset password.";

default:
return "";
}
}
}

private int ParseHex(string s)
{
int n;
if (int.TryParse(s, NumberStyles.HexNumber, null, out n))
return n;
else
return 0;
}
}
}

One note though from a security stand-point you actually probably don't want to do this for external or perhaps even internal sites. You never want to let users know the actual error since hackers can deduce from there a way to get in. For example by saying "Account expired", I know that that the user and perhaps even password is correct on a brute force attack. Instead, I should give a catch all exception of "UserName/Password combination failed". However, from a testing perspective this is always helpful.

July 15, 2009

C# Ldap Login Class

In an earlier post I mentioned that when it comes to Win32 vs Ldap speed for login, Win32 is the clear winner. However, if you don't feel comfortable using Win32 (Interop etc.) and you also don't want to use the built in provider, then this is the class for you.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices.Protocols;
using System.Net;

namespace SecurityFramework
{
public class LdapLogin
{
public static void VerifyCredentials(string ldapServer, string userName, string domain, string password)
{
if (string.IsNullOrEmpty(ldapServer))
ldapServer = null;

bool useSSL = false;
NetworkCredential credentials = new NetworkCredential(userName, password, domain);

LdapConnection authConnect = new LdapConnection(
new LdapDirectoryIdentifier(ldapServer),
null,
AuthType.Basic
);

authConnect.SessionOptions.SecureSocketLayer = useSSL;
authConnect.SessionOptions.ProtocolVersion = 3;

try
{
authConnect.Bind(credentials);
}
catch (LdapException ex)
{
throw ex;
}
}
}
}

July 10, 2009

UserName without AD domain in C#

Someone asked in one of the forums recently can you get the username for someone in AD using C# without the domain addition. So if you have a logon name of test@mydomain.com or domain.com\test...how can you get just test?

This is actually quite easy and can be done like this:
private string ExtractPureUsername(string username)
{
string[] parts = username.Split('@');
if (parts.Length == 2)
{
return parts[0];
}

parts = username.Split('\\');
if (parts.Length == 2)
{
return parts[1];
}

return username;
}
So now you can simply write:
username = ExtractPureUsername(username);
Having brought up the topic of AD and C#, I think I'll start to share some samples next week of how to work with AD using C#.

June 18, 2009

AD Login Test with Hex Error

If you ever have done work with C# and AD you probably would have come across various issues. One of the first issues usually relates to authentication. In an earlier posting, I already proved that win32 logon is better than Ldap binds and so I am going to assume that this is the approach you are all taking. Now, let's say you want the hex code error and also try different types of logon such as LOGON32_LOGON_INTERACTIVE or LOGON32_LOGON_NETWORK. This little utility will allow you to switch between the different options and easily test and get the hex code error.

Here is a snippet of the code which you can find here:
private void btnLogonUser_Click(object sender, EventArgs e)
{
IntPtr token = IntPtr.Zero;
int logonType = (int)((ComboItem)cbLogonType.Items[cbLogonType.SelectedIndex]).Value;
bool isOk = LogonUser(tbUsername.Text, tbDomain.Text, tbPassword.Text, logonType, LOGON32_PROVIDER_DEFAULT, ref token);
int err = GetLastError();
if(isOk) {
tbStatus.Text = "Status";
}
else {
tbStatus.Text = String.Format("Error: 0x{0:X} ({0})", err, err);
}
}

June 10, 2009

Active Directory Authentication in C#

There is a lot of code on the web that actually does exactly what this post discusses. Also, in later versions of .NET, Microsoft almost gave the code outright to us. However, the issue with many versions on the web and even the approach Microsoft takes (with the ActiveDirectoryMembershipProvider) is that they do LDAP binds which is slower than making the Win32 call. What is the performance difference? Well, I ran a quick test and the results were than LogonUser was about 5x faster! I have uploaded my code here which is basked off an ILogin and a Win32 Login. The basic idea being that perhaps one day we move away from Active Directory Authentication. Here is the ILogin interface:
    public interface ILogin
{
void Authenticate(string username, string password);
void Authenticate(string username, string password, string domain);
}
The Win32 example is below:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using JL.Framework.Security;
using JL.Framework.Security.Win32Authentication;

namespace Console
{
class Program
{
static void Main(string[] args)
{
string username = "joe";
string password = "mypass";
string domain = "domain"; //note this is not required as I point out it can be called from a config

ILogin login = new Win32Login();
try{
login.Authenticate(username, password, domain);
System.Console.WriteLine("Successfully authenticated!");
}
catch (AuthenticationException ex)
{
System.Console.WriteLine(ex);
}

username = Win32Login.ParseLogonName("domainA\\userA", out domain);
Debug.Assert(username == "userA");
Debug.Assert(domain == "domainA");

username = Win32Login.ParseLogonName("userB@domainB", out domain);
Debug.Assert(username == "userB");
Debug.Assert(domain == "domainB");
}
}
}
You should be able to use the code in any authentication system you may have written in .NET that talks to AD.

June 3, 2009

AzMan Test Harness

I (or someone from our operations team) often needed a way to quickly test operations in my applications. This had to be done without going directly into our main application or digging into the AzMan console. I whipped up (back in '04 but still used today) this simple windows form. I have uploaded my old test harness here . This harness is very old (written in .NET 1.1), so you may have to update some code if you are putting this into 2.0 or 3.5. The windows form when run will allow you to test:
  • Test either against XML or AD store
  • Specify the Application Name
  • Specify the OperationName
  • Test with either a User SID or a UserName
The output would then show if that SID or UserName is True/False against that operation.

June 1, 2009

Get Modified users in AD using Powershell and C#

Using c# within powershell you can easily get users that have changed in AD. Just change the date found in the whenChanged part of the query (20090601):
$strFilter = "(&(objectClass=User)(whenChanged>=20090601000000.0Z)(userAccountControl:1.2.840.113556.1.4.803:=2))"

$objDomain = New-Object System.DirectoryServices.DirectoryEntry

$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 1000
$objSearcher.Filter = $strFilter

$colProplist = "name"
foreach ($i in $colPropList){$objSearcher.PropertiesToLoad.Add($i)}

$colResults = $objSearcher.FindAll()

foreach ($objResult in $colResults)
{$objItem = $objResult.Properties; $objItem.name}
On a side note, you can also do this very easily with the free Quest AD cmdlets