How to convert an ADsPath to an NT name

May 3, 2010 at 8:39 AM

Hi all

Does anybody out there know how to convert an ADsPath (as returned by the DirectoryObjectPickerDialog in the DirectoryObject.Path property) into the DOMAIN\USERNAME format?

The problem is that depending on the object selected in the picker, the format of the ADsPath differs. I've seen stuff such as:

LDAP://internal.mycompany.corp/CN=Miller\, Frank,OU=Users,OU=Winterthur,OU=EMEA,DC=internal,DC=mycompany,DC=corp
GC://mycompany.corp:3142/CN=Domain Users,CN=Users,DC=integration,DC=mycompany,DC=corp
WinNT://INTERNAL/MYCOMPUTER/LocalAccount
... (is there more?) ...

Now, the .NET types System.Security.Principal.WindowsIdentity and System.Security.Principal.WindowsPrincipal both work with the DOMAIN\USERNAME format (for example, see the WindowsPrincipal.IsInRole method).

I've started to write my own ADsPath parsing routines, but this feels too hackish to me. There must be a more official solution to this. I was hoping to use the ActiveDs.NameTranslate but it does not recognize the formats listed above (at least not directly).

Help is appreciated very much!

May 4, 2010 at 1:07 PM

For the time being, I've solved the problem with the following approach. I'm creating instances of System.DirectoryServices.DirectoryEntry based on the ADsPath which is returned by the object picker (DirectoryObject.Path). Then, I recursively traverse the parent entries until I find a domainDNS class node --> its name is the domain name. The full code looks like this:

        /// <summary>
        /// Retrieve NT user/group name and domain of an ADsPath.
        /// </summary>
        private static string GetNtName(string adsPath)
        {
            string name = null;
            DirectoryEntry entry = new DirectoryEntry(adsPath);
            DirectoryEntry parent = entry;
            while ((parent = parent.Parent) != null)
            {
                if (string.Equals(parent.SchemaClassName, "domainDNS", StringComparison.InvariantCultureIgnoreCase)) // the entry point to a domain objects
                {
                    name = (string)entry.Properties["sAMAccountName"].Value;
                    break;
                }

                else if (string.Equals(parent.SchemaClassName, "computer", StringComparison.InvariantCultureIgnoreCase)) // the entry point to a local objects
                {
                    name = entry.Name;
                    break;
                }
            }
            if (parent == null)
            {
                throw new SecuritySetupException(string.Format("Failed to retrieve domain for object '{0}'", adsPath));
            }
            Debug.Assert(name != null);

            string domain = ((string)parent.Properties["name"].Value).ToUpperInvariant();
            return string.Format(@"{0}\{1}", domain, name);
        }


I call this method in the following way:

foreach (DirectoryObject selectedObject in selectedObjects)
{
   string ntName = GetNtName(selectedObject.Path);
   // ...

 

Maybe this is useful to somebody who wants to achieve a similar thing. Please let me know what you think about this approach and if there are any objections to it...

May 6, 2010 at 1:18 PM
Edited May 7, 2010 at 7:19 AM

I insertd the following class:

using System;

namespace CubicOrange.Windows.Forms.ActiveDirectory
{
	/// <summary>
	/// Indicates the ADsPaths provider type of the DirectoryObjectPickerDialog.
	/// This provider affects the contents of the ADPath returned
	/// </summary>
	[Flags]
	public enum ADsPathsProviders
	{
		/// <summary>
		/// The ADsPaths are converted to use the WinNT provider.
		/// 
		/// The ADsPath string for the ADSI WinNT provider can be one of the following forms:
		/// 
		/// <code>
		/// WinNT:
		/// WinNT://<domain name>
		/// WinNT://<domain name>/<server>
		/// WinNT://<domain name>/<path>
		/// WinNT://<domain name>/<object name>
		/// WinNT://<domain name>/<object name>,<object class>
		/// WinNT://<server>
		/// WinNT://<server>/<object name>
		/// WinNT://<server>/<object name>,<object class>
		/// </code>
		/// 
		/// The domain name can be either a NETBIOS name or a DNS name.
		/// The server is the name of a specific server within the domain.
		/// The path is the path of on object, such as "printserver1/printer2".
		/// The object name is the name of a specific object.
		/// The object class is the class name of the named object. One example of this usage would be "WinNT://MyServer/JeffSmith,user". Specifying a class name can improve the performance of the bind operation.
		/// </summary>
		WinNT = 0x00000002,

		/// <summary>
		/// The ADsPaths are converted to use the LDAP provider.
		/// 
		/// The Microsoft LDAP provider ADsPath requires the following format.
		/// 
		/// <code>
		/// LDAP://HostName[:PortNumber][/DistinguishedName]
		/// </code>
		/// 
		/// Further info, see <see cref="http://msdn.microsoft.com/en-us/library/aa746384(v=VS.85).aspx"/>.
		/// </summary>
		LDAP = 0x00000004,

		/// <summary>
		/// The ADsPaths for objects selected from this scope are converted to use the GC provider.
		/// </summary>
		GC = 0x00000008,

		/// <summary>
		/// The ADsPaths having an objectSid attribute are converted to the form 
		/// 
		/// <code>
		/// LDAP://<SID=x>
		/// </code>
		/// 
		/// where x represents the hexadecimal digits of the objectSid attribute value.
		/// </summary>
		SIDPath = 0x00000010,

		/// <summary>
		/// The ADsPaths for down-level, well-known SID objects are an empty string unless this flag is specified (For example; DSOP_DOWNLEVEL_FILTER_INTERACTIVE). If this flag is specified, the paths have the form: 
		/// 
		/// <code>
		/// WinNT://NT AUTHORITY/Interactive
		/// </code>
		/// 
		/// or:
		/// 
		/// <code>
		/// WinNT://Creator owner
		/// </code>
		/// </summary>
		DownlevelBuildinPath = 0x00000020
	}

}

Next, i added this property to DirectoryObjectpickerDialog.cs

		private ADsPathsProviders providers;

...

		/// 
		/// Gets or sets the providers affecting the ADPath returned in objects.
		/// 
		public ADsPathsProviders Providers
		{
			get { return providers; }
			set { providers = value; }
		}

Also added the this function

		// Convert ADsPathsProviders to DSOP_SCOPE_INIT_INFO_FLAGS

		private uint GetProviderFlags ()
		{
			uint scope = 0;
			if ((providers & ADsPathsProviders.WinNT) == ADsPathsProviders.WinNT)
				scope |= DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_WANT_PROVIDER_WINNT;

			if ((providers & ADsPathsProviders.LDAP) == ADsPathsProviders.LDAP)
				scope |= DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_WANT_PROVIDER_LDAP;

			if ((providers & ADsPathsProviders.GC) == ADsPathsProviders.GC)
				scope |= DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_WANT_PROVIDER_GC;

			if ((providers & ADsPathsProviders.SIDPath) == ADsPathsProviders.SIDPath)
				scope |= DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_WANT_SID_PATH;

			if ((providers & ADsPathsProviders.DownlevelBuildinPath) == ADsPathsProviders.DownlevelBuildinPath)
				scope |= DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_WANT_DOWNLEVEL_BUILTIN_PATH;

			return scope;
		}

And finally changed in Initialize method (bolded lines):

			uint defaultFilter = GetDefaultFilter ();
			uint upLevelFilter = GetUpLevelFilter ();
			uint downLevelFilter = GetDownLevelFilter ();
			uint wantProviderFlags = GetProviderFlags ();
			// Internall, use one scope for the default (starting) locations.
			uint startingScope = GetStartingScope ();
			if (startingScope > 0)
			{
				DSOP_SCOPE_INIT_INFO startingScopeInfo = new DSOP_SCOPE_INIT_INFO ();
				startingScopeInfo.cbSize = (uint)Marshal.SizeOf (typeof (DSOP_SCOPE_INIT_INFO));
				startingScopeInfo.flType = startingScope;
				startingScopeInfo.flScope = DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_STARTING_SCOPE | defaultFilter | wantProviderFlags;
				startingScopeInfo.FilterFlags.Uplevel.flBothModes = upLevelFilter;
				startingScopeInfo.FilterFlags.flDownlevel = downLevelFilter;
				startingScopeInfo.pwzADsPath = null;
				startingScopeInfo.pwzDcName = null;
				startingScopeInfo.hr = 0;
				scopeInitInfoList.Add (startingScopeInfo);
			}

			// And another scope for all other locations (AllowedLocation values not in DefaultLocation)
			uint otherScope = GetOtherScope ();
			if (otherScope > 0)
			{
				DSOP_SCOPE_INIT_INFO otherScopeInfo = new DSOP_SCOPE_INIT_INFO ();
				otherScopeInfo.cbSize = (uint)Marshal.SizeOf (typeof (DSOP_SCOPE_INIT_INFO));
				otherScopeInfo.flType = otherScope;
				otherScopeInfo.flScope = defaultFilter | wantProviderFlags;
				otherScopeInfo.FilterFlags.Uplevel.flBothModes = upLevelFilter;
				otherScopeInfo.FilterFlags.flDownlevel = downLevelFilter;
				otherScopeInfo.pwzADsPath = null;
				otherScopeInfo.pwzDcName = null;
				otherScopeInfo.hr = 0;
				scopeInitInfoList.Add (otherScopeInfo);
			}

This allows you to use provicer WinNT which fullfils your wish.

 What you will have to do is to remove the WinNT:// and replace / with \

 

 

May 11, 2010 at 7:35 AM

Thanks for sharing!

Sound like a general enhancement that could be considered for a future release of the Active Directory Common Dialogs...