C# 如何读取Active Directory对象的用户参数属性的内容?
我需要从Active Directory对象中提取一些信息,例如配置文件路径或用户是否被锁定 我可以看到,这些信息保存在Active Directory对象的属性中,但该属性的值是一个不可理解的字符串: 我可以看出它也有同样的问题,但对于如何解析该属性,还没有明确的解决方案 如何在保持理智的同时提取所需的数据?属性值非常……特殊 虽然属性定义说该值是a,但实际情况要复杂一些:该值是混合文本和二进制数据的二进制负载,使用两种“算法”进行编码,然后转换为Unicode字符串-这就是为什么会看到垃圾字符,这些字符实际上是以字符串形式显示的纯二进制数据 互联网上有许多关于如何解码该值的文章,但大多数文章要么是错误的,要么是使用错误的程序解码该值(虽然在他们的示例中产生了正确的数据,但可能随时中断) 这类物品的例子如下:C# 如何读取Active Directory对象的用户参数属性的内容?,c#,active-directory,C#,Active Directory,我需要从Active Directory对象中提取一些信息,例如配置文件路径或用户是否被锁定 我可以看到,这些信息保存在Active Directory对象的属性中,但该属性的值是一个不可理解的字符串: 我可以看出它也有同样的问题,但对于如何解析该属性,还没有明确的解决方案 如何在保持理智的同时提取所需的数据?属性值非常……特殊 虽然属性定义说该值是a,但实际情况要复杂一些:该值是混合文本和二进制数据的二进制负载,使用两种“算法”进行编码,然后转换为Unicode字符串-这就是为什么会看到垃圾
- -解码算法错误,请参见下文了解其工作原理李>
- -使用与前一个相同的错误解码算法,并且不必要地复杂李>
- 而且-这似乎是正确的,但我在VB方面还没有足够的技能来验证代码
- 描述二进制有效载荷总体结构的文档李>
- 描述有效载荷中包含的属性结构的文件李>
- 以及文档,它解释了有效负载中包含的属性值是如何编码的
根据我的意见,
每个人都有自己的想法。
[…]
奥尼·斯佩兰扎小姐,您好 -但丁·阿利吉耶里,《神通》,地狱,第三章 (答案末尾有一个简单、完整且可验证的例子,供那些急于阅读所有内容的人参考。) 步骤1-获取属性值 您可以通过System.DirectoryServices(通常称为“S.DS”)或System.DirectoryServices.Protocols(通常称为“S.DS.p”)库获取值。如果您需要有关这些库如何工作的帮助,可以阅读和文章 如果您是.NETFramework用户,这些是您常用的GAC程序集,因此您必须像往常一样添加它们 如果您是.NET核心用户,请高兴!这两个库都已于2017-11-15在NuGet上发布(尽管是预发布),因此您现在也可以查询Active Directory了!去拿他们: Nota bene:此答案中的代码是为.NET Core 2.0目标编写的,如果您不使用.NET Core,可能需要稍微调整代码;但是,这些更改应该是小而容易的,因为库的核心版本和非核心版本非常相似 这里我们使用System.DirectoryServices.Protocols读取属性:
var ldapDirectoryIdentifier = new LdapDirectoryIdentifier(
"domain-controller.example.com",
389,
true,
false);
var networkCredential = new NetworkCredential(
"alice@example.com",
"p@sSw0rd",
"example.com");
var ldapConnection = new LdapConnection(
ldapDirectoryIdentifier,
networkCredential,
AuthType.Kerberos);
var searchRequest = new SearchRequest(
"DC=example,DC=com",
"(objectClass=user)",
SearchScope.Subtree,
"userParameters");
// WARNING
// If the parameters of either LdapDirectoryIdentifier or NetworkCredential are wrong
// (e.g. invalid credentials) you'll get an exception here.
var searchResponse = (SearchResponse) ldapConnection.SendRequest(searchRequest);
foreach (SearchResultEntry searchResultEntry in searchResponse.Entries)
{
// WARNING
// This WILL throw an exception when used on an object where the attribute is missing.
// You should really check that the attribute exists and has exactly one value.
// I skipped that for brevity, you should not.
var directoryAttribute = searchResultEntry.Attributes["userParameters"];
var attributeValue = (string) directoryAttribute[0];
}
步骤2-定义属性包含的内容
现在我们有了属性的值,我们需要一些东西来保存它包含的值
首先,我们需要一些枚举
第一个是ctxcfglags1
enum:
[Flags]
public enum CtxCfgFlags1 : uint
{
Undefined1 = 0x00000000,
Undefined2 = 0x00000001,
Undefined3 = 0x00000002,
DisableCam = 0x00000004,
WallpaperDisabled = 0x00000008,
DisableExe = 0x00000010,
DisableClip = 0x00000020,
DisableLpt = 0x00000040,
DisableCcm = 0x00000080,
DisableCdm = 0x00000100,
DisableCpm = 0x00000200,
UseDefaultGina = 0x00000400,
HomeDirectoryMapRoot = 0x00000800,
DisableEncryption = 0x00001000,
ForceClientLptDef = 0x00002000,
AutoClientLpts = 0x00004000,
AutoClientDrives = 0x00008000,
LogonDisabled = 0x00010000,
ReconnectSame = 0x00020000,
ResetBroken = 0x00040000,
PromptForPassword = 0x00080000,
InheritSecurity = 0x00100000,
InheritAutoClient = 0x00200000,
InheritMaxIdleTime = 0x00400000,
InheritMaxdisconnectionTime = 0x00800000,
InheritMaxsessionTime = 0x01000000,
InheritShadow = 0x02000000,
InheritCallbackNumber = 0x04000000,
InheritCallback = 0x08000000,
Undefined4 = 0x10000000,
Undefined5 = 0x20000000,
Undefined6 = 0x40000000,
Undefined7 = 0x80000000
}
public enum CtxShadow : uint
{
Disable = 0x00000000,
EnableInputNotify = 0x00000001,
EnableInputNoNotify = 0x00000002,
EnableNoInputNotify = 0x00000003,
EnableNoInputNoNotify = 0x00000004
}
Nota bene:中定义的值缺少几个条目,这些条目名为未定义*
;这些值未在定义中列出,但已在野外观察到,如果不定义它们,则当您通过调试器或.ToString()
查看时,您的标志枚举将中断,并且不会很好地显示出来
第二个是CtxShadow
enum:
[Flags]
public enum CtxCfgFlags1 : uint
{
Undefined1 = 0x00000000,
Undefined2 = 0x00000001,
Undefined3 = 0x00000002,
DisableCam = 0x00000004,
WallpaperDisabled = 0x00000008,
DisableExe = 0x00000010,
DisableClip = 0x00000020,
DisableLpt = 0x00000040,
DisableCcm = 0x00000080,
DisableCdm = 0x00000100,
DisableCpm = 0x00000200,
UseDefaultGina = 0x00000400,
HomeDirectoryMapRoot = 0x00000800,
DisableEncryption = 0x00001000,
ForceClientLptDef = 0x00002000,
AutoClientLpts = 0x00004000,
AutoClientDrives = 0x00008000,
LogonDisabled = 0x00010000,
ReconnectSame = 0x00020000,
ResetBroken = 0x00040000,
PromptForPassword = 0x00080000,
InheritSecurity = 0x00100000,
InheritAutoClient = 0x00200000,
InheritMaxIdleTime = 0x00400000,
InheritMaxdisconnectionTime = 0x00800000,
InheritMaxsessionTime = 0x01000000,
InheritShadow = 0x02000000,
InheritCallbackNumber = 0x04000000,
InheritCallback = 0x08000000,
Undefined4 = 0x10000000,
Undefined5 = 0x20000000,
Undefined6 = 0x40000000,
Undefined7 = 0x80000000
}
public enum CtxShadow : uint
{
Disable = 0x00000000,
EnableInputNotify = 0x00000001,
EnableInputNoNotify = 0x00000002,
EnableNoInputNotify = 0x00000003,
EnableNoInputNoNotify = 0x00000004
}
现在我们可以定义用于保存属性的类:
public class UserParameters
{
public uint? CtxCfgPresent { get; set; }
public CtxCfgFlags1? CtxCfgFlags1 { get; set; }
public uint? CtxCallBack { get; set; }
public uint? CtxKeyboardLayout { get; set; }
public byte? CtxMinEncryptionLevel { get; set; }
public uint? CtxNwLogonServer { get; set; }
public string CtxWfHomeDir { get; set; }
public string CtxWfHomeDirDrive { get; set; }
public string CtxInitialProgram { get; set; }
public uint? CtxMaxConnectionTime { get; set; }
public uint? CtxMaxDisconnectionTime { get; set; }
public uint? CtxMaxIdleTime { get; set; }
public string CtxWfProfilePath { get; set; }
public CtxShadow? CtxShadow { get; set; }
public string CtxWorkDirectory { get; set; }
public string CtxCallbackNumber { get; set; }
}
这里没有什么特别之处,这些只是文档中定义的属性
步骤3-解析有效负载
现在是时候解码包含在属性值中的有效负载了
和文档定义了有效负载的结构
有效载荷分为两个主要部分:“标题”部分和“数据”部分
“标题”部分包含
- 未记录的96字节数组“ReservedData”小节将被视为黑盒李>
- 一个2字节的Unicode字符串“签名”,应该用作“控制值”李>
- 和一个2字节的无符号整数“TSPropertyCount”,其中包含有效负载包含的属性数
- 一个2字节的无符号整数“NameLength”,包含属性名称的字节长度李>
- 一个2字节的无符号整数“ValueLength”,包含属性值的长度字节李>
- 包含属性类型的2字节无符号整数“Type”李>
- 包含属性名称的可变长度Unicode字符串“PropName”李>
- 包含属性值的可变长度双ASCII编码字节数组“PropValue”李>
for (var i = 0; i < tsPropertyCountValue; i++)
byte[] nameLength = binaryReader.ReadBytes(2);
byte[] valueLength = binaryReader.ReadBytes(2);
byte[] type = binaryReader.ReadBytes(2);
ushort nameLengthValue = BitConverter.ToUInt16(nameLength, 0);
ushort valueLengthValue = BitConverter.ToUInt16(valueLength, 0);
ushort typeValue = BitConverter.ToUInt16(type, 0);
byte[] propName = binaryReader.ReadBytes(nameLengthValue);
byte[] propValue = binaryReader.ReadBytes(valueLengthValue);
string propNameValue = Encoding.Unicode.GetString(propName);
byte[] propValueValue = GetPropValueValue(propValue);
private static byte[] GetPropValueValue(byte[] propValue)
{
// Since the encoding algorithm doubles the space used, we halve it.
var propValueValue = new byte[propValue.Length / 2];
// Parse the encoded bytes two-by-two, since the encoding algorithm transforms
// one bytes in two bytes we need to read two of them to obtain the original one.
for (var j = 0; j < propValue.Length; j = j + 2)
{
// Compute the two halves (nibbles) of the original byte from the values of the
// two encoded bytes. Each encoded bytes is actually an hexadecimal character,
// so each encoded byte can only have a value between 48 and 57 ('0' to '9')
// or between 97 and 102 ('a' to 'f'). Yes, it's an utter waste of space.
var highNibble = HexToInt(propValue[j]);
var lowNibble = HexToInt(propValue[j + 1]);
// Recreate the original byte from the two nibbles.
propValueValue[j / 2] = (byte) (highNibble << 4 | lowNibble);
}
return propValueValue;
}
private static int HexToInt(byte value)
{
if ('0' <= value && value <= '9')
{
return value - '0';
}
if ('a' <= value && value <= 'f')
{
return value - 'a' + 10;
}
if ('A' <= value && value <= 'F')
{
return value - 'A' + 10;
}
throw new Exception("Invalid character.");
}
if (string.Equals(propNameValue, nameof(UserParameters.CtxCfgPresent), StringComparison.OrdinalIgnoreCase))
{
userParameters.CtxCfgPresent = BitConverter.ToUInt32(propValueValue, 0);
}
else if (string.Equals(propNameValue, nameof(UserParameters.CtxCfgFlags1), StringComparison.OrdinalIgnoreCase))
{
userParameters.CtxCfgFlags1 = (CtxCfgFlags1) BitConverter.ToUInt32(propValueValue, 0);
}
else if (string.Equals(propNameValue, nameof(UserParameters.CtxCallBack), StringComparison.OrdinalIgnoreCase))
{
userParameters.CtxCallBack = BitConverter.ToUInt32(propValueValue, 0);
}
else if (string.Equals(propNameValue, nameof(UserParameters.CtxKeyboardLayout), StringComparison.OrdinalIgnoreCase))
{
userParameters.CtxKeyboardLayout = BitConverter.ToUInt32(propValueValue, 0);
}
else if (string.Equals(propNameValue, nameof(UserParameters.CtxNwLogonServer), StringComparison.OrdinalIgnoreCase))
{
userParameters.CtxNwLogonServer = BitConverter.ToUInt32(propValueValue, 0);
}
else if (string.Equals(propNameValue, nameof(UserParameters.CtxMaxConnectionTime), StringComparison.OrdinalIgnoreCase))
{
userParameters.CtxMaxConnectionTime = BitConverter.ToUInt32(propValueValue, 0);
}
else if (string.Equals(propNameValue, nameof(UserParameters.CtxMaxDisconnectionTime), StringComparison.OrdinalIgnoreCase))
{
userParameters.CtxMaxDisconnectionTime = BitConverter.ToUInt32(propValueValue, 0);
}
else if (string.Equals(propNameValue, nameof(UserParameters.CtxMaxIdleTime), StringComparison.OrdinalIgnoreCase))
{
userParameters.CtxMaxIdleTime = BitConverter.ToUInt32(propValueValue, 0);
}
else if (string.Equals(propNameValue, nameof(UserParameters.CtxShadow), StringComparison.OrdinalIgnoreCase))
{
userParameters.CtxShadow = (CtxShadow) BitConverter.ToUInt32(propValueValue, 0);
}
else if (string.Equals(propNameValue, nameof(UserParameters.CtxMinEncryptionLevel), StringComparison.OrdinalIgnoreCase))
{
userParameters.CtxMinEncryptionLevel = propValueValue[0];
}
else if (string.Equals(propNameValue, nameof(UserParameters.CtxWfHomeDir), StringComparison.OrdinalIgnoreCase))
{
userParameters.CtxWfHomeDir = Encoding.ASCII.GetString(propValueValue, 0, propValueValue.Length - 1);
}
else if (string.Equals(propNameValue, nameof(UserParameters.CtxWfHomeDirDrive), StringComparison.OrdinalIgnoreCase))
{
userParameters.CtxWfHomeDirDrive = Encoding.ASCII.GetString(propValueValue, 0, propValueValue.Length - 1);
}
else if (string.Equals(propNameValue, nameof(UserParameters.CtxInitialProgram), StringComparison.OrdinalIgnoreCase))
{
userParameters.CtxInitialProgram = Encoding.ASCII.GetString(propValueValue, 0, propValueValue.Length - 1);
}
else if (string.Equals(propNameValue, nameof(UserParameters.CtxWfProfilePath), StringComparison.OrdinalIgnoreCase))
{
userParameters.CtxWfProfilePath = Encoding.ASCII.GetString(propValueValue, 0, propValueValue.Length - 1);
}
else if (string.Equals(propNameValue, nameof(UserParameters.CtxWorkDirectory), StringComparison.OrdinalIgnoreCase))
{
userParameters.CtxWorkDirectory = Encoding.ASCII.GetString(propValueValue, 0, propValueValue.Length - 1);
}
else if (string.Equals(propNameValue, nameof(UserParameters.CtxCallbackNumber), StringComparison.OrdinalIgnoreCase))
{
userParameters.CtxCallbackNumber = Encoding.ASCII.GetString(propValueValue, 0, propValueValue.Length - 1);
}
else
{
throw new Exception("Unsupported property.");
}