How to check password complexity using NetValidatePasswordPolicy

The NetValidatePasswordPolicy function allows an application to verify that passwords meet the complexity requirement.
Here is an important remark from Microsoft :
“The NetValidatePasswordPolicy function does not validate passwords in Active Directory accounts and cannot be used for this purpose. The only policy that this function checks a password against in Active Directory accounts is the password complexity (the password strength).”

The following code checks if the password meets complexity requirement using NetValidatePasswordPolicy:

#include <Windows.h>
#include <Lm.h>
#include <Dsgetdc.h>
#include <stdio.h>

WCHAR *DisplayErrorText(DWORD dwLastError)
{
    HMODULE hModule = NULL;
    LPWSTR MessageBuffer = NULL;
    DWORD dwBufferLength;

    DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_IGNORE_INSERTS |
        FORMAT_MESSAGE_FROM_SYSTEM ;

    if(dwLastError >= NERR_BASE && dwLastError <= MAX_NERR) {
        hModule = LoadLibraryEx(
            TEXT("netmsg.dll"),
            NULL,
            LOAD_LIBRARY_AS_DATAFILE
            );

        if(hModule != NULL)
            dwFormatFlags |= FORMAT_MESSAGE_FROM_HMODULE;
    }

    FormatMessageW(
        dwFormatFlags,
        hModule,
        dwLastError,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPWSTR) &MessageBuffer,
        0,
        NULL
        );

    if(hModule != NULL)
        FreeLibrary(hModule);

	return MessageBuffer;
}


int wmain(int argc, wchar_t * argv[])
{
      NET_API_STATUS status;
      NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG InputArg = {0};
	  DWORD res = 0;
	  wchar_t* wzServer = NULL;
      NET_VALIDATE_OUTPUT_ARG* pOutputArg = NULL;

	  if(argc < 2)
	  {
		  wprintf(L"Usage: MyNetValidatePasswordPolicy password\r\n");
		  return -1;
	  }

	  PDOMAIN_CONTROLLER_INFO  DcInfo;// Get address of nearest DC as cached by OS at boot time.
      if (!DsGetDcName(NULL, NULL, NULL, NULL,
                    DS_DIRECTORY_SERVICE_REQUIRED | DS_RETURN_FLAT_NAME,
                    &DcInfo))
      {
		  wzServer = DcInfo->DomainControllerName;
	  }
	  else
		  wzServer = _wgetenv(L"LOGONSERVER");

      InputArg.ClearPassword = argv[1];
      InputArg.PasswordMatch = TRUE;
      status = NetValidatePasswordPolicy(wzServer, NULL, NetValidatePasswordChange, &InputArg, (void**)&pOutputArg);
	  if(status != NERR_Success)
	  {
		  wprintf(L"Error NetValidatePasswordPolicy %s\r\n", DisplayErrorText(status));
	  }
	  else
	  {
		  if(pOutputArg->ValidationStatus == 0)
			  wprintf(L"Password validated for a password change operation\r\n");
		  else
			  wprintf(L"Password validation failed for a password change operation: %s\r\n", DisplayErrorText(pOutputArg->ValidationStatus));
	  }
      NetValidatePasswordPolicyFree((void**)&pOutputArg);
	  return 0;
} 

How to test if user logged in with cached credentials using LsaGetLogonSessionData function in C++

In some case it will be useful to check if users are logged in with cached credentials (for example to notify users when they have been using cached credentials to log into their laptop for too long).

The following code gets user’s last logon information with the use of LSA (Local Security Authority) API LsaGetLogonSessionData.
When cached credentials are used the LOGON_CACHED_ACCOUNT flag is set (pLogonSessionData->UserFlags & LOGON_CACHED_ACCOUNT)

#include "stdafx.h"
#include <Windows.h>
#include <Ntsecapi.h>
#include <ntstatus.h> 
#include <malloc.h>
#include <strsafe.h>

void PrintLogonType (SECURITY_LOGON_TYPE type)
{
    if (type < Interactive || type > CachedUnlock)
        _tprintf (TEXT("LogonType: UndefinedLogonType\n"));
    else {
        static LPTSTR szTypes[] = {
            TEXT("Interactive"),
            TEXT("Network"), 
            TEXT("Batch"), 
            TEXT("Service"), 
            TEXT("Proxy"),
            TEXT("Unlock"), 
            TEXT("NetworkCleartext"),
            TEXT("NewCredentials"), 
            TEXT("RemoteInteractive"),
            TEXT("CachedInteractive"),
            TEXT("CachedRemoteInteractive"),
            TEXT("CachedUnlock")
        };
        _tprintf (TEXT("LogonType: %s\n"), szTypes[(int)type-Interactive]);
    }
}


void PrintUnicodeString (LPCTSTR pszPrefix, LSA_UNICODE_STRING lsaString)
{
    if (lsaString.MaximumLength >= lsaString.Length + sizeof(WCHAR) &&
        lsaString.Buffer[lsaString.Length/sizeof(WCHAR)] == L'\0')
        _tprintf (TEXT("%s: %ls\n"), pszPrefix, lsaString.Buffer);
    else if (lsaString.Length <= STRSAFE_MAX_CCH * sizeof(TCHAR)) {
        LPWSTR sz = (LPWSTR) _alloca (lsaString.Length + sizeof(WCHAR));
        StringCbCopyNW (sz, lsaString.Length + sizeof(WCHAR), lsaString.Buffer, lsaString.Length);
        _tprintf (TEXT("%s: %ls\n"), pszPrefix, sz);
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE hThread = NULL;
    DWORD dwSize;
    TOKEN_STATISTICS ts;
	LUID luid;

    if(!OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hThread))
	{
		wprintf(L"Error OpenProcessToken rc=0x%.8X\r\n", GetLastError());
		return -1;
	}
	dwSize = sizeof(TOKEN_STATISTICS);
    if(!GetTokenInformation (hThread, TokenStatistics, &ts, sizeof(TOKEN_STATISTICS), &dwSize))
	{
		wprintf(L"Error GetTokenInformation rc=0x%.8X\r\n", GetLastError());
		if (hThread)
            CloseHandle (hThread);
		return -1;
	}
    luid = ts.AuthenticationId;
    if (hThread)
        CloseHandle (hThread);
 
    PSECURITY_LOGON_SESSION_DATA pLogonSessionData = NULL;
    NTSTATUS ntStatus = LsaGetLogonSessionData (&luid, &pLogonSessionData);
    if (ntStatus != STATUS_SUCCESS) {
		wprintf(L"Error LsaGetLogonSessionData rc=0x%.8X\r\n", ntStatus);
		return -1;
	}

    if (pLogonSessionData->UserName.Length)
        PrintUnicodeString (TEXT("UserName"), pLogonSessionData->UserName);
    if (pLogonSessionData->LogonDomain.Length)
        PrintUnicodeString (TEXT("LogonDomain"), pLogonSessionData->LogonDomain);
    if (pLogonSessionData->AuthenticationPackage.Length)
        PrintUnicodeString (TEXT("AuthenticationPackage"), pLogonSessionData->AuthenticationPackage);
    PrintLogonType ((SECURITY_LOGON_TYPE)pLogonSessionData->LogonType);
    _tprintf (TEXT("Session: %d\n"), pLogonSessionData->Session);

    if (pLogonSessionData->LogonServer.Length)
        PrintUnicodeString (TEXT("LogonServer"), pLogonSessionData->LogonServer);
    if (pLogonSessionData->DnsDomainName.Length)
        PrintUnicodeString (TEXT("DnsDomainName"), pLogonSessionData->DnsDomainName);
    if (pLogonSessionData->Upn.Length)
        PrintUnicodeString (TEXT("Upn"), pLogonSessionData->Upn);

	if(pLogonSessionData->UserFlags & LOGON_GUEST) wprintf(L"UserFlags=LOGON_GUEST\r\n");
	if(pLogonSessionData->UserFlags & LOGON_NOENCRYPTION) wprintf(L"UserFlags=LOGON_NOENCRYPTION\r\n");
	if(pLogonSessionData->UserFlags & LOGON_CACHED_ACCOUNT) wprintf(L"UserFlags=LOGON_CACHED_ACCOUNT\r\n");
	if(pLogonSessionData->UserFlags & LOGON_USED_LM_PASSWORD) wprintf(L"UserFlags=LOGON_USED_LM_PASSWORD\r\n");
	if(pLogonSessionData->UserFlags & LOGON_EXTRA_SIDS) wprintf(L"UserFlags=LOGON_EXTRA_SIDS\r\n");
	if(pLogonSessionData->UserFlags & LOGON_SUBAUTH_SESSION_KEY) wprintf(L"UserFlags=LOGON_SUBAUTH_SESSION_KEY\r\n");
	if(pLogonSessionData->UserFlags & LOGON_SERVER_TRUST_ACCOUNT) wprintf(L"UserFlags=LOGON_SERVER_TRUST_ACCOUNT\r\n");
	if(pLogonSessionData->UserFlags & LOGON_NTLMV2_ENABLED) wprintf(L"UserFlags=LOGON_NTLMV2_ENABLED\r\n");
	if(pLogonSessionData->UserFlags & LOGON_RESOURCE_GROUPS) wprintf(L"UserFlags=LOGON_RESOURCE_GROUPS\r\n");
	if(pLogonSessionData->UserFlags & LOGON_PROFILE_PATH_RETURNED) wprintf(L"UserFlags=LOGON_PROFILE_PATH_RETURNED\r\n");
	if(pLogonSessionData->UserFlags & LOGON_NT_V2) wprintf(L"UserFlags=LOGON_NT_V2\r\n");
	if(pLogonSessionData->UserFlags & LOGON_LM_V2) wprintf(L"UserFlags=LOGON_LM_V2\r\n");
	if(pLogonSessionData->UserFlags & LOGON_NTLM_V2) wprintf(L"UserFlags=LOGON_NTLM_V2\r\n");

	if(pLogonSessionData->UserFlags & LOGON_WINLOGON) wprintf(L"UserFlags=LOGON_WINLOGON\r\n");
	if(pLogonSessionData->UserFlags & LOGON_PKINIT) wprintf(L"UserFlags=LOGON_PKINIT\r\n");
	if(pLogonSessionData->UserFlags & LOGON_OPTIMIZED) wprintf(L"UserFlags=LOGON_OPTIMIZED\r\n");
	if(pLogonSessionData->UserFlags & LOGON_NO_OPTIMIZED) wprintf(L"UserFlags=LOGON_NO_OPTIMIZED\r\n");
 
    if (pLogonSessionData)
        LsaFreeReturnBuffer(pLogonSessionData);

	return 0;
}

How to export AD user attributes to a CSV file with powershell Get-ADUser?

The following command exports user attributes to a CSV file:

Exported user attributes are:

  • sAMAccountName
  • displayName
  • distinguishedName
  • employeeID
  • accountExpires
  • whenCreated
  • whenChanged
  • pwdLastSet
  • Password Never Expire
  • Account disabled
  • lastLogonTimestamp
  • lastLogon
Get-ADUser -Filter * -SearchBase "DC=fr,DC=contoso,DC=com" -Properties 
displayName,distinguishedName,sAMAccountName,employeeID,accountExpires,
whenCreated,whenChanged,pwdLastSet,userAccountControl,lastLogonTimestamp,lastLogon 
-Server FR-DC1| 
Select-Object -Property sAMAccountName,displayName,
distinguishedName,employeeID,
@{N='accountExpires';E={[DATETIME]::fromFileTime($_.accountExpires)}},
whenCreated,whenChanged,
@{n='pwdLastSet';e={[DateTime]::FromFileTime($_.pwdLastSet)}},
@{n="Password Never Expire";e={if(($_.userAccountControl[0] -band 65536) -ne 0) {"True"} else {"False"}}},
@{n="Account disabled";e={if(($_.userAccountControl[0] -band 2) -ne 0) {"True"} else {"False"}}},
@{n='lastLogonTimestamp';e={[DateTime]::FromFileTime($_.lastLogonTimestamp)}},
@{n='lastLogon';e={[DateTime]::FromFileTime($_.lastLogon)}} | 
export-csv c:\temp\user_fr.csv -encoding "unicode" 

How to get the group memberships in an access Token in C++ with GetTokenInformation and LookupAccountSid

The following example returns the group memberships in the current process’s access token

#include "stdafx.h"
#include <Windows.h>
#include <Iads.h>
#include <AdsHlp.h>

int _tmain(int argc, _TCHAR* argv[])
{
	DWORD i, dwSize;
	HANDLE hToken;
	PTOKEN_GROUPS pGroupInfo;
	PTOKEN_USER pUserInfo = NULL;
    HRESULT hr;

    hr = CoInitialize(NULL);
    if(SUCCEEDED(hr))
    {
		// Open a handle to the access token for the calling process.
		if (OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken))
		{
			dwSize = 0;
			GetTokenInformation (hToken, TokenUser, NULL, dwSize, &dwSize);
			if (pUserInfo = (PTOKEN_USER) GlobalAlloc (GPTR, dwSize))
			{
				// Call GetTokenInformation again to get the group information.
				if (!GetTokenInformation (hToken, TokenUser, pUserInfo, dwSize, &dwSize)) 
				{
					GlobalFree (pUserInfo);
					pUserInfo = NULL;
				}
			}
			dwSize = 0;
			GetTokenInformation (hToken, TokenGroups, NULL, dwSize, &dwSize);
			if (pGroupInfo = (PTOKEN_GROUPS) GlobalAlloc (GPTR, dwSize))
			{
				if (GetTokenInformation (hToken, TokenGroups, pGroupInfo, dwSize, &dwSize)) 
				{
					static WCHAR szName[1024], szDomain[1024];

					for (i=0; i < pGroupInfo->GroupCount; i++)
					{
						if (pGroupInfo->Groups[i].Attributes & SE_GROUP_ENABLED)
						{
							BOOL bEqual = FALSE;
							if (EqualDomainSid (pUserInfo->User.Sid, pGroupInfo->Groups[i].Sid, &bEqual)) 
							{
								if (bEqual)
								{
									DWORD	dwName, dwDomain;
									SID_NAME_USE SidNameUse;
									dwName = 1024;
									dwDomain = 1024;
									if (LookupAccountSid (NULL, pGroupInfo->Groups[i].Sid, szName, &dwName, szDomain, &dwDomain, &SidNameUse))
										wprintf(L"%s\n",szName);
								}
							}
						}
					}
				}
				GlobalFree (pGroupInfo);
			}
			if (pUserInfo != NULL) 
				GlobalFree (pUserInfo);
			
			//Close the access token handle
			CloseHandle (hToken);
		}
	}
    CoUninitialize();
    return 0;
}

Using DsAddressToSiteNamesEx in C# sample: How to get the site and subnet names from an IP Address of a machine

The DsAddressToSiteNamesEx function obtains the site and subnet names corresponding to the addresses specified.

Sample usage: How to get the site and subnet names from an IP Address of a machine

[DllImport("Netapi32.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "DsAddressToSiteNamesEx", CharSet = CharSet.Unicode)]
        private static extern int DsAddressToSiteNamesEx(
            [In] string computerName,
            [In] Int32 EntryCount,
            [In] SOCKET_ADDRESS[] SocketAddresses,
            [Out] out IntPtr SiteNames,
            [Out] out IntPtr SubnetNames);

[DllImport("Netapi32.dll")]
private static extern int NetApiBufferFree([In] IntPtr buffer);

private const int ERROR_SUCCESS = 0;

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct SockAddr_In
{
    public Int16 sin_family;
    public Int16 sin_port;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public byte[] sin_addr;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public byte[] sin_zero;
}

[StructLayout(LayoutKind.Sequential)]
internal struct SOCKET_ADDRESS
{
    public IntPtr lpSockaddr;
    public Int32  iSockaddrLength;
}

static void Main(string[] args)
{
	string sIP = "10.251.50.101";//@ip = 10.251.50.101
	string sDC = "dc-fr-001";//the name of a domain controller
	IntPtr pSiteNames;
	IntPtr pSubnetNames;
	SOCKET_ADDRESS[] SocketAddresses = new SOCKET_ADDRESS[1];

	String[] ipArray = sIP.Split('.');

	SockAddr_In sockAddr;
	sockAddr.sin_family = 2;
	sockAddr.sin_port = 0;
	sockAddr.sin_addr = new byte[4] { byte.Parse(ipArray[0]), byte.Parse(ipArray[1]), byte.Parse(ipArray[2]), byte.Parse(ipArray[3]) };
	sockAddr.sin_zero = new byte[8] { 0, 0, 0, 0, 0, 0, 0, 0 };

	SocketAddresses[0].iSockaddrLength = Marshal.SizeOf(sockAddr);

	IntPtr pSockAddr = Marshal.AllocHGlobal(16);
	Marshal.StructureToPtr(sockAddr, pSockAddr, true);
	SocketAddresses[0].lpSockaddr = pSockAddr;

	if (DsAddressToSiteNamesEx(
			sDC,
			1,//EntryCount
			SocketAddresses,
			out pSiteNames,
			out pSubnetNames) == ERROR_SUCCESS)
	{
		string sSite = Marshal.PtrToStringAuto(Marshal.ReadIntPtr(pSiteNames, 0));
		string sSubnet = Marshal.PtrToStringAuto(Marshal.ReadIntPtr(pSubnetNames, 0));

		Console.WriteLine("Found site: " + sSite);
		Console.WriteLine("Found subnet: " + sSubnet);
		NetApiBufferFree(pSubnetNames);
		NetApiBufferFree(pSiteNames);
	}
}

Using DsGetSiteName in C# sample: how to get the name of the site where a computer resides?

The DsGetSiteName function returns the name of the site where a computer resides. The site in which the computer resides (as reported by a domain controller) is stored in the computer registry in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ Netlogon\Parameters.

The client IP address is used by the domain controller to retrieve the corresponding subnet that it matches in the Subnets container.
Then the name of the site is retrived from the subnet-to-site mapping.

If the client’s IP address does not match a subnet range of any of the subnets stored in Active Directory, the dclocator will randomly pick a site to use.

Sample usage to get the name of the site where a computer resides:

[DllImport("netapi32.dll", CharSet = CharSet.Auto)]
private static extern int DsGetSiteName(string ComputerName, out IntPtr SiteName);

[DllImport("Netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern int NetApiBufferFree(IntPtr dwBuffer);

private const int ERROR_SUCCESS = 0;

static void Main(string[] args)
{
	IntPtr pSiteInfo;
	String sSiteName;

	if (DsGetSiteName(
			string.Empty,//ComputerName
			out pSiteInfo) == ERROR_SUCCESS)
	{
		sSiteName = Marshal.PtrToStringAuto(pSiteInfo);
		NetApiBufferFree(pSiteInfo);
		Console.WriteLine("Found workstaton's site Name: " + sSiteName);
	}
}

You can also use a command-line interface for the same purpose:

nltest /server:<HostName> /DsGetSite

How to get group members of Active Directory (Powershell script)

This powershell script allow administrator to get group members of Active Directory. It allow to show all members that is user and computers in group.

Function Get-GroupMembers
{
 <#            
	.DESCRIPTION
		Return the members of an an AD group.
	.PARAMETER GroupDN
		The distinguished name of the group to get membership from.
	.EXAMPLE
		Get-GroupMembers -GroupDN "CN=Managers,OU=Groups,DC=fr,DC=contoso,DC=com" | Format-Table -Property cn, objectSID, distinguishedName
 #>
 Param
	(
 $GroupDN = "CN=Managers,OU=Groups,DC=fr,DC=contoso,DC=com"
	)
 Begin
	{
		$Group = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$($GroupDN)")
		$UserAccounts = @()
	}
 Process
	{
		$Members = $Group.member
		
		If ($Members -ne $Null)
		{
			foreach ($User in $Members)
			{
				$UserObject = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$($User)")
				If ($UserObject.objectCategory.Value.Contains("Group"))
				{
				}
				Else
				{
					$CurrentUser = New-Object -TypeName PSObject -Property @{
						name = $UserObject.name
						cn = $UserObject.cn
						distinguishedName = $UserObject.distinguishedName
						nTSecurityDescriptor = $UserObject.nTSecurityDescriptor
						objectSID = $UserObject.objectSID
					}
				}
			$UserAccounts += $CurrentUser
			}
		}
	}
 End
	{
		Return $UserAccounts
	}
}

Global catalog and User Logon

A domain controller can locate only the objects in its domain. Locating an object in a different domain would require access to a global catalog server.
A global catalog server is a domain controller that, in addition to its full, writable domain directory partition replica, also stores a partial, read-only replica of all other domain directory partitions in the forest.
The attributes that are replicated to the global catalog are identified in the schema as the partial attribute set (PAS) and are defined by default by Microsoft.
In addition to its activities as a domain controller, the global catalog server supports the following special activities in the forest:
User logon: Domain controllers must contact a global catalog server to retrieve any SIDs of universal groups that the user is a member of.

userlogon

(1) Client logs on to the domain, which prompts
(2) a DNS query for the closest domain controllers.
(3) Client contacts the returned domain controller DCx for authentication.
(4) DCx queries DNS to find the closest global catalog server and then
(5) contacts the returned global catalog server DCy to retrieve the universal groups for the user
The global catalog stores the membership (the member attribute) of only universal groups.

Additionally, if the user specifies a logon name in the form of a UPN (which has the format sAMAccountName@DNSDomainName), the domain controller contacts a global catalog server to retrieve the domain of the user:

userlogonwithUPN

Universal and global group caching and updates: In sites where Universal Group Membership Caching is enabled, domain controllers cache group memberships and keep the cache updated by contacting a global catalog server.
Caching group membership reduces WAN traffic, which helps in sites where updating the cached group membership of security principals generates less traffic than replicating the global catalog to the site.

Using DsGetDcName in C# sample: How to get a Global Catalog

The directory service functions provide a utility for locating a domain controller (DC) in a Windows domain.
The DsGetDcName function (implemented by the Netlogon service) returns the name of a domain controller in a specified domain

Sample usage:

How to get a Global Catalog

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DomainControllerInfo
{
	public string DomainControllerName;
	public string DomainControllerAddress;
	public int DomainControllerAddressType;
	public Guid DomainGuid;
	public string DomainName;
	public string DnsForestName;
	public int Flags;
	public string DcSiteName;
	public string ClientSiteName;
}

[Flags]
private enum DSGETDCNAME_FLAGS : uint
{
	DS_FORCE_REDISCOVERY = 0x00000001,
	DS_DIRECTORY_SERVICE_REQUIRED = 0x00000010,
	DS_DIRECTORY_SERVICE_PREFERRED = 0x00000020,
	DS_GC_SERVER_REQUIRED = 0x00000040,
	DS_PDC_REQUIRED = 0x00000080,
	DS_BACKGROUND_ONLY = 0x00000100,
	DS_IP_REQUIRED = 0x00000200,
	DS_KDC_REQUIRED = 0x00000400,
	DS_TIMESERV_REQUIRED = 0x00000800,
	DS_WRITABLE_REQUIRED = 0x00001000,
	DS_GOOD_TIMESERV_PREFERRED = 0x00002000,
	DS_AVOID_SELF = 0x00004000,
	DS_ONLY_LDAP_NEEDED = 0x00008000,
	DS_IS_FLAT_NAME = 0x00010000,
	DS_IS_DNS_NAME = 0x00020000,
	DS_RETURN_DNS_NAME = 0x40000000,
	DS_RETURN_FLAT_NAME = 0x80000000
}

[DllImport("Netapi32.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "DsGetDcNameW", CharSet = CharSet.Unicode)]
private static extern int DsGetDcName(
	[In] string computerName,
	[In] string domainName,
	[In] IntPtr domainGuid,
	[In] string siteName,
	[In] DSGETDCNAME_FLAGS flags,
	[Out] out IntPtr domainControllerInfo);

[DllImport("Netapi32.dll")]
private static extern int NetApiBufferFree(
	[In] IntPtr buffer);

private const int ERROR_SUCCESS = 0;

static void Main(string[] args)
{
	IntPtr pDomainInfo;
	if (DsGetDcName(
			string.Empty,//ComputerName
			"contoso.com",//DomainName
			IntPtr.Zero,//DomainGuid
			string.Empty,//SiteName
			DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED |
			DSGETDCNAME_FLAGS.DS_GC_SERVER_REQUIRED |
			DSGETDCNAME_FLAGS.DS_IS_DNS_NAME |
			DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME,
			out pDomainInfo) == ERROR_SUCCESS)
	{
		DomainControllerInfo dci = new DomainControllerInfo();
		dci = (DomainControllerInfo)Marshal.PtrToStructure(pDomainInfo, typeof(DomainControllerInfo));
		NetApiBufferFree(pDomainInfo);
		pDomainInfo = IntPtr.Zero;
		Console.WriteLine("Found Global catalog DC: " + dci.DomainControllerName);
	}
}

How to check if a user is member of a specific group with C#

The following code will find out if a specific user is member of a specific group in the Active Directory.

static void Main(string[] args)
{
	string group = "LDAP://cn=sales,ou=groups,ou=west,dc=contoso,dc=com";
	string member = "LDAP://cn=Jim Smith,ou=users,ou=west,dc=contoso,dc=com";

	Console.WriteLine("Determining if " + member + " is a member of the " + group + " Group...");

	using (DirectoryEntry deMember = new DirectoryEntry(member),
						  deGroup = new DirectoryEntry(group))
	{
		bool isGroupMember = (bool)deGroup.Invoke("IsMember", new object[] { deMember.Path });
		Console.WriteLine(member + (isGroupMember ? " is" : " is not") + " a member of the " + group + " Group...");
	}
}