以编程方式将Windows机器加入AD域

这与此问题类似,但不是这个问题 - 然而,它需要手动将服务器连接到域的信息(并且被正确重定向),我正在寻求一些代码的帮助,这些代码以编程方式将一台机器连接到一个域。

该方案是我们有一个启动器服务,用于实例化Amazon EC2 Server2008R1 VM,可选地通过用户数据流传递机器名称。 我们的映像中包含一个进程,用于在启动时检查User-Data上的名称 - 如果没有任何文件存在,则虚拟机将保持在我们的Cloud域之外,但如果名称存在,则该计算机将按指定进行重命名并自动加入该域名。

问题出在这里 - 如果我在实例启动后的任何时候手动运行这个过程,它就会像上面描述的那样工作。 机器名称被更改,并且虚拟机加入到域中(我们强制重新启动以实现此目的)。

但是,作为计划任务运行时(启动时触发),计算机重命名按预期发生,但随后对JoinDomainOrWorkgroup (参见下文)的调用将拾取由EC2给出的旧随机机器名称,而不是新名称刚被分配。

这会导致WMI返回代码为8525 ,我们会在AD存储库(该随机名称)中得到一个未连接的错误名称条目,并且该计算机未加入域。 虚拟机然后重新启动,第二次通过启动过程(由于用户数据中有内容,但机器尚未在域中而异常触发)执行所有相同的步骤并成功。

它看起来像机器名称在第一遍中设置,但没有“完成”,并且JoinDomainOrWorkgroup仍然看到原始名称。 第二遍,机器名称已经正确设置,所以JoinDomainOrWorkgroup按预期工作。 很明显,为什么这个过程在启动过程中以这种方式运行,但在已经启动的虚拟机上手动运行时完美工作,我认为是问题的症结所在。

我尝试在重命名和连接步骤之间插入一个延迟,以防在重命名在幕后完成之前发生对JoinDomainOrWorkgroup的调用,但这并没有起到什么作用 - 而且我也没有真正期待它,因为整体过程在手动运行时完美运行。 所以它可能是启动时机器状态的微妙差异和代码中的一些愚蠢的组合。

也许在SetDomainMembership方法中使用System.Environment.MachineName是不可取的? 但是,即使我像SetMachineName一样将新名称作为字符串传递,stil也会失败。 所以我很难过。

以下是重命名机器的WMI代码:

/// <summary>
/// Set Machine Name
/// </summary>
public static bool SetMachineName(string newName)
{
  _lh.Log(LogHandler.LogType.Debug, string.Format("Setting Machine Name to '{0}'...", newName));

  // Invoke WMI to populate the machine name
  using (ManagementObject wmiObject = new ManagementObject(new ManagementPath("Win32_ComputerSystem.Name='" + System.Environment.MachineName + "'")))
  {
    ManagementBaseObject inputArgs = wmiObject.GetMethodParameters("Rename");
    inputArgs["Name"] = newName;

    // Set the name
    ManagementBaseObject outParams = wmiObject.InvokeMethod("Rename", inputArgs, null);

    // Weird WMI shennanigans to get a return code (is there no better way to do this??)
    uint ret = (uint)(outParams.Properties["ReturnValue"].Value);
    if (ret == 0)
    {
      // It worked
      return true;
    }
    else
    {
      // It didn't work
      _lh.Log(LogHandler.LogType.Fatal, string.Format("Unable to change Machine Name from '{0}' to '{1}'", System.Environment.MachineName, newName));
      return false;
    }
  }
}

以下是将它连接到域的WMI代码:

/// <summary>
/// Set domain membership
/// </summary>
public static bool SetDomainMembership()
{
  _lh.Log(LogHandler.LogType.Debug, string.Format("Setting domain membership of '{0}' to '{1}'...", System.Environment.MachineName, _targetDomain));

  // Invoke WMI to join the domain
  using (ManagementObject wmiObject = new ManagementObject(new ManagementPath("Win32_ComputerSystem.Name='" + System.Environment.MachineName + "'")))
  {
    try
    {
      // Obtain in-parameters for the method
      ManagementBaseObject inParams = wmiObject.GetMethodParameters("JoinDomainOrWorkgroup");

      inParams["Name"] = "*****";
      inParams["Password"] = "*****";
      inParams["UserName"] = "*****";
      inParams["FJoinOptions"] = 3; // Magic number: 3 = join to domain and create computer account

      // Execute the method and obtain the return values.
      ManagementBaseObject outParams = wmiObject.InvokeMethod("JoinDomainOrWorkgroup", inParams, null);
      _lh.Log(LogHandler.LogType.Debug, string.Format("JoinDomainOrWorkgroup return code: '{0}'", outParams["ReturnValue"]));

      // Did it work?  ** disabled so we restart later even if it fails
      //uint ret = (uint)(outParams.Properties["ReturnValue"].Value);
      //if (ret != 0)
      //{
      //  // Nope
      //  _lh.Log(LogHandler.LogType.Fatal, string.Format("JoinDomainOrWorkgroup failed with return code: '{0}'", outParams["ReturnValue"]));
      //  return false;
      //}

      return true;
    }
    catch (ManagementException e)
    {
      // It didn't work
      _lh.Log(LogHandler.LogType.Fatal, string.Format("Unable to join domain '{0}'", _targetDomain), e);
      return false;
    }
  }
}

道歉,如果这段代码看起来愚蠢愚蠢 - 我是WMI的新手,这在很大程度上是从我在互联网上找到的例子中分裂出来的; 如果有一个更聪明/更好的方法来做到这一点,那么通过一切手段证明。 如果你能在同一时间解决问题,奖励积分!


好的,就在这里。

首先,系统属性中字段的顺序有点误导 - 您首先看到机器名称,而在下面看到域名/工作组。 这下意识地影响了我的想法,并且意味着我的代码通过尝试先设置名称来复制该顺序,然后将该计算机加入域。 虽然这在某些情况下有效,但并不一致或可靠。 所以这里学到的最大教训是......

首先加入域 - 然后更改机器名称。

是的,这其实就是它的全部。 经过多次测试迭代后,我终于明白,如​​果我尝试这种方法,效果可能会更好。 我在第一次通过时更改了名称,但很快意识到它仍在使用本地系统凭据 - 但现在该机器已加入域,此时它需要与使用的相同的域凭证加入域本身。 稍后调整一小段代码,现在我们有一个可靠的WMI例程,它可以加入域,然后更改名称。

它可能不是最好的实现(随意评论改进),但它的工作原理。 请享用。

/// <summary>
/// Join domain and set Machine Name
/// </summary>
public static bool JoinAndSetName(string newName)
{
  _lh.Log(LogHandler.LogType.Debug, string.Format("Joining domain and changing Machine Name from '{0}' to '{1}'...", Environment.MachineName, newName));

  // Get WMI object for this machine
  using (ManagementObject wmiObject = new ManagementObject(new ManagementPath("Win32_ComputerSystem.Name='" + Environment.MachineName + "'")))
  {
    try
    {
      // Obtain in-parameters for the method
      ManagementBaseObject inParams = wmiObject.GetMethodParameters("JoinDomainOrWorkgroup");
      inParams["Name"] = "domain_name";
      inParams["Password"] = "domain_account_password";
      inParams["UserName"] = "domain_account";
      inParams["FJoinOptions"] = 3; // Magic number: 3 = join to domain and create computer account

      _lh.Log(LogHandler.LogType.Debug, string.Format("Joining machine to domain under name '{0}'...", inParams["Name"]));

      // Execute the method and obtain the return values.
      ManagementBaseObject joinParams = wmiObject.InvokeMethod("JoinDomainOrWorkgroup", inParams, null);

      _lh.Log(LogHandler.LogType.Debug, string.Format("JoinDomainOrWorkgroup return code: '{0}'", joinParams["ReturnValue"]));

      // Did it work?
      if ((uint)(joinParams.Properties["ReturnValue"].Value) != 0)
      {
        // Join to domain didn't work
        _lh.Log(LogHandler.LogType.Fatal, string.Format("JoinDomainOrWorkgroup failed with return code: '{0}'", joinParams["ReturnValue"]));
        return false;
      }
    }
    catch (ManagementException e)
    {
      // Join to domain didn't work
      _lh.Log(LogHandler.LogType.Fatal, string.Format("Unable to join domain '{0}'", _targetDomain), e);
      return false;
    }

    // Join to domain worked - now change name
    ManagementBaseObject inputArgs = wmiObject.GetMethodParameters("Rename");
    inputArgs["Name"] = newName;
    inputArgs["Password"] = "domain_account_password";
    inputArgs["UserName"] = "domain_account";

    // Set the name
    ManagementBaseObject nameParams = wmiObject.InvokeMethod("Rename", inputArgs, null);
    _lh.Log(LogHandler.LogType.Debug, string.Format("Machine Rename return code: '{0}'", nameParams["ReturnValue"]));

    if ((uint)(nameParams.Properties["ReturnValue"].Value) != 0)
    {
      // Name change didn't work
      _lh.Log(LogHandler.LogType.Fatal, string.Format("Unable to change Machine Name from '{0}' to '{1}'", Environment.MachineName, newName));
      return false;
    }

    // All ok
    return true;
  }
}
链接地址: http://www.djcxy.com/p/19197.html

上一篇: Programmatically join Windows machine to AD domain

下一篇: How to delete all files and folders in a directory?