Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/292.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何读取Active Directory对象的用户参数属性的内容?_C#_Active Directory - Fatal编程技术网

C# 如何读取Active Directory对象的用户参数属性的内容?

C# 如何读取Active Directory对象的用户参数属性的内容?,c#,active-directory,C#,Active Directory,我需要从Active Directory对象中提取一些信息,例如配置文件路径或用户是否被锁定 我可以看到,这些信息保存在Active Directory对象的属性中,但该属性的值是一个不可理解的字符串: 我可以看出它也有同样的问题,但对于如何解析该属性,还没有明确的解决方案 如何在保持理智的同时提取所需的数据?属性值非常……特殊 虽然属性定义说该值是a,但实际情况要复杂一些:该值是混合文本和二进制数据的二进制负载,使用两种“算法”进行编码,然后转换为Unicode字符串-这就是为什么会看到垃圾

我需要从Active Directory对象中提取一些信息,例如配置文件路径或用户是否被锁定

我可以看到,这些信息保存在Active Directory对象的属性中,但该属性的值是一个不可理解的字符串:

我可以看出它也有同样的问题,但对于如何解析该属性,还没有明确的解决方案

如何在保持理智的同时提取所需的数据?

属性值非常……特殊

虽然属性定义说该值是a,但实际情况要复杂一些:该值是混合文本和二进制数据的二进制负载,使用两种“算法”进行编码,然后转换为Unicode字符串-这就是为什么会看到垃圾字符,这些字符实际上是以字符串形式显示的纯二进制数据

互联网上有许多关于如何解码该值的文章,但大多数文章要么是错误的,要么是使用错误的程序解码该值(虽然在他们的示例中产生了正确的数据,但可能随时中断)

这类物品的例子如下:

  • -解码算法错误,请参见下文了解其工作原理
  • -使用与前一个相同的错误解码算法,并且不必要地复杂
  • 而且-这似乎是正确的,但我在VB方面还没有足够的技能来验证代码
文档中定义了数据的编码方式

特别是您正在寻找的:

  • 描述二进制有效载荷总体结构的文档
  • 描述有效载荷中包含的属性结构的文件
  • 以及文档,它解释了有效负载中包含的属性值是如何编码的
或者,您可以查看有关如何解码属性值的(真正)简明示例

如果你仍然和我在一起,没有失去理智,没有传唤Cthulhu,也没有放火,微软内部的任何人都认为这样做是个好主意,让我们继续编写代码来解析和提取数据

从我的角度看,这是一个多伦特之城,
根据我的意见,
每个人都有自己的想法。
[…]
奥尼·斯佩兰扎小姐,您好

-但丁·阿利吉耶里,《神通》,地狱,第三章

(答案末尾有一个简单、完整且可验证的例子,供那些急于阅读所有内容的人参考。)

步骤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”
“PropName”和“PropValue”字段的长度必须分别从“NameLength”和“ValueLength”字段中获取

等等。“双ASCII编码”?那是什么

每个属性都可以有一个值e
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.");
}