以编程方式将 Windows 机器加入 AD 域 [英] Programmatically join Windows machine to AD domain

查看:31
本文介绍了以编程方式将 Windows 机器加入 AD 域的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这类似于这个问题 - 但是,在它寻求有关手动将服务器加入域的信息(并被正确重定向)的地方,我正在寻求一些以编程方式将机器加入域的代码的帮助.>

这个场景是我们有一个启动器服务,它实例化 Amazon EC2 Server2008R1 虚拟机,可选择通过用户数据流传入机器名称.一个进程被烘焙到我们的映像中,在启动时检查用户数据的名称 - 如果不存在,则 VM 保留在我们的云域之外,但如果存在名称,则机器将按指定重命名并自动加入域.

问题来了——如果我在实例启动后的任何时间手动运行这个过程,它的工作原理和描述的完全一样;机器名称已更改,并且 VM 已加入域(我们强制重新启动以实现此目的).

然而,当作为计划任务运行(在启动时触发)时,机器重命名按预期发生,但随后对 JoinDomainOrWorkgroup 的调用(见下文)选择旧的随机机器名称给EC2 的 VM,而不是刚刚分配的新名称.

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

看起来机器名称是在第一遍中设置的,但没有最终确定",并且 JoinDomainOrWorkgroup 仍然看到原始名称.在第二遍时,机器名称已经正确设置,因此 JoinDomainOrWorkgroup 可以按预期工作.我认为这个过程在启动时会以这种方式运行,但在已启动的 VM 上手动运行时却能完美运行,这正是我认为问题的关键所在.

我尝试在重命名和加入步骤之间插入延迟,以防在重命名在幕后完成之前调用 JoinDomainOrWorkgroup 发生,但这并没有帮助 - 而我没有不是真的期望它,因为整个过程在手动运行时可以完美运行.所以这可能是启动期间机器状态的细微差异和代码中一些愚蠢的组合.

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

这是重命名机器的 WMI 代码:

////<摘要>///设置机器名称///</总结>public static bool SetMachineName(string newName){_lh.Log(LogHandler.LogType.Debug, string.Format("设置机器名称为'{0}'...", newName));//调用 WMI 来填充机器名称使用 (ManagementObject wmiObject = new ManagementObject(new ManagementPath("Win32_ComputerSystem.Name='" + System.Environment.MachineName + "'"))){ManagementBaseObject inputArgs = wmiObject.GetMethodParameters("重命名");inputArgs["Name"] = newName;//设置名称ManagementBaseObject outParams = wmiObject.InvokeMethod("重命名", inputArgs, null);//奇怪的 WMI 恶作剧以获取返回码(有没有更好的方法来做到这一点??)uint ret = (uint)(outParams.Properties["ReturnValue"].Value);如果 (ret == 0){//有效返回真;}别的{//它没有工作_lh.Log(LogHandler.LogType.Fatal, string.Format("无法将机器名称从'{0}'更改为'{1}'", System.Environment.MachineName, newName));返回假;}}}

这是将其加入域的 WMI 代码:

////<摘要>///设置域成员///</总结>public static bool SetDomainMembership(){_lh.Log(LogHandler.LogType.Debug, string.Format("将 '{0}' 的域成员资格设置为 '{1}'...", System.Environment.MachineName, _targetDomain));//调用WMI加入域使用 (ManagementObject wmiObject = new ManagementObject(new ManagementPath("Win32_ComputerSystem.Name='" + System.Environment.MachineName + "'"))){尝试{//获取方法的参数ManagementBaseObject inParams = wmiObject.GetMethodParameters("JoinDomainOrWorkgroup");inParams["Name"] = "*****";inParams["密码"] = "*****";inParams["用户名"] = "*****";inParams["FJoinOptions"] = 3;//幻数:3 = 加入域并创建计算机帐户//执行该方法并获取返回值.ManagementBaseObject outParams = wmiObject.InvokeMethod("JoinDomainOrWorkgroup", inParams, null);_lh.Log(LogHandler.LogType.Debug, string.Format("JoinDomainOrWorkgroup return code: '{0}'", outParams["ReturnValue"]));//成功了吗?** 禁用,因此即使失败我们稍后重新启动//uint ret = (uint)(outParams.Properties["ReturnValue"].Value);//如果(返回!= 0)//{////不//_lh.Log(LogHandler.LogType.Fatal, string.Format("JoinDomainOrWorkgroup failed with return code: '{0}'", outParams["ReturnValue"]));//返回假;//}返回真;}catch (ManagementException e){//它没有工作_lh.Log(LogHandler.LogType.Fatal, string.Format("无法加入域 '{0}'", _targetDomain), e);返回假;}}}

抱歉,如果这段代码看起来很愚蠢——我是 WMI 的新手,这主要是从我在互联网上找到的例子中抄袭而来的;如果有更聪明/更整洁的方法来做到这一点,那么一定要演示.如果能同时解决问题,加分!

解决方案

好的,就是这样.

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

<块引用>

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

是的,这就是它的全部内容.经过无数次的测试迭代,我终于意识到如果我这样尝试它可能会更好.我在第一遍更改名称时绊倒了,但很快意识到它仍在使用本地系统凭据 - 但现在机器已加入域,它需要与使用的域凭据相同的域凭据加入域本身.稍后进行一些快速的代码调整,我们现在有一个始终可靠的 WMI 例程,它加入域然后更改名称.

这可能不是最简洁的实现(请随时对改进发表评论),但它确实有效.享受.

////<摘要>///加入域并设置机器名称///</总结>public static bool JoinAndSetName(string newName){_lh.Log(LogHandler.LogType.Debug, string.Format("加入域并将机器名称从'{0}'更改为'{1}'...", Environment.MachineName, newName));//获取这台机器的 WMI 对象使用 (ManagementObject wmiObject = new ManagementObject(new ManagementPath("Win32_ComputerSystem.Name='" + Environment.MachineName + "'"))){尝试{//获取方法的参数ManagementBaseObject inParams = wmiObject.GetMethodParameters("JoinDomainOrWorkgroup");inParams["Name"] = "domain_name";inParams["Password"] = "domain_account_password";inParams["UserName"] = "domain_account";inParams["FJoinOptions"] = 3;//幻数:3 = 加入域并创建计算机帐户_lh.Log(LogHandler.LogType.Debug, string.Format("将机器加入名称为'{0}'的域...", inParams["Name"]));//执行该方法并获取返回值.ManagementBaseObject joinParams = wmiObject.InvokeMethod("JoinDomainOrWorkgroup", inParams, null);_lh.Log(LogHandler.LogType.Debug, string.Format("JoinDomainOrWorkgroup return code: '{0}'", joinParams["ReturnValue"]));//成功了吗?if ((uint)(joinParams.Properties["ReturnValue"].Value) != 0){//加入域无效_lh.Log(LogHandler.LogType.Fatal, string.Format("JoinDomainOrWorkgroup failed with return code: '{0}'", joinParams["ReturnValue"]));返回假;}}catch (ManagementException e){//加入域无效_lh.Log(LogHandler.LogType.Fatal, string.Format("无法加入域 '{0}'", _targetDomain), e);返回假;}//加入域工作 - 现在更改名称ManagementBaseObject inputArgs = wmiObject.GetMethodParameters("重命名");inputArgs["Name"] = newName;inputArgs["Password"] = "domain_account_password";inputArgs["UserName"] = "domain_account";//设置名称ManagementBaseObject nameParams = wmiObject.InvokeMethod("重命名", inputArgs, null);_lh.Log(LogHandler.LogType.Debug, string.Format("Machine Rename return code: '{0}'", nameParams["ReturnValue"]));if ((uint)(nameParams.Properties["ReturnValue"].Value) != 0){//改名无效_lh.Log(LogHandler.LogType.Fatal, string.Format("无法将机器名称从'{0}'更改为'{1}'", Environment.MachineName, newName));返回假;}//一切都好返回真;}}

This is similar to, but not a dupe of, this question - however, where it sought information on manually joining a server to a domain (and was rightly redirected) I am looking for help with some code that programmatically joins a machine to a domain.

The scenario is that we have a launcher service that instantiates Amazon EC2 Server2008R1 VMs, optionally passing a Machine Name in through the User-Data stream. A process is baked into our images that checks User-Data for a name on bootup - If none is present then the VM remains outside of our Cloud domain, but if the name is present then the machine is renamed as specified and auto-joined to the domain.

Here's the problem - if I run this process manually any time after instance start, it works exactly as described; the machine name is changed, and the VM is joined to the domain (we force a restart to make this happen).

However, when running as a Scheduled Task (triggered on startup) the machine rename happens as expected, but the subsequent call to JoinDomainOrWorkgroup (see below) picks-up the old randomised machine name given to the VM by EC2 instead of the new name it has just been assigned.

This results in a WMI return code of 8525, we get a disconnected misnamed entry in the AD repository (of that randomised name) and the machine is not joined to the domain. The VM then restarts, and a second pass through the startup process (abnormally triggered because there is content in User-Data but the machine is not yet in the domain) executes all the same steps and succeeds.

It looks like the machine name is set in the first pass but not 'finalised', and JoinDomainOrWorkgroup still sees the original name. On the second pass, the machine name is already set properly, and so JoinDomainOrWorkgroup works as expected. Quite why the process behaves this way during startup, but works perfectly when run manually on an already-started VM, is I think the nub of the problem.

I've tried inserting a delay between the rename and join steps in case the call to JoinDomainOrWorkgroup was happening before the rename was finalised behind the scenes, but this hasn't helped - and I didn't really expect it to, since the whole process works perfectly when run manually. So it's probably a combination of a subtle difference in machine state during bootup and something silly in the code.

Maybe using System.Environment.MachineName in the SetDomainMembership method is inadvisable? But it stil fails even if I pass the new name in as a string as I do for SetMachineName. So I'm stumped.

Here's the WMI code that renames the machine:

/// <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;
    }
  }
}

And here's the WMI code that joins it to the domain:

/// <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;
    }
  }
}

Apologies if this code looks mind-numbingly stupid - I'm new to WMI, and this is largely cribbed from examples I've found on the interwebs; if there's a smarter/neater way to do this then by all means demonstrate. If you can cure the problem at the same time, bonus points!

解决方案

OK, here it is.

Firstly, the order of the fields in System Properties is a little misleading - you see Machine Name first, and Domain/Workgroup below that. This subconsciously affected my thinking, and meant my code copied that ordering by trying to set the name first, and then join the machine to the domain. Whilst this does work under some circumstances, it's not consistent or reliable. So the biggest lesson learned here is...

Join the domain first - then change the machine name.

Yep, that's actually all there is to it. After numerous test iterations, it finally dawned on me that it might work better if I tried it this way around. I tripped-up on the change of name on my first pass, but quickly realised that it was still using the local system credentials - but now that the machine was joined to the domain at this point, it needed the same domain credentials as were used to join the domain itself. A fast bit of code-tweaking later, and we now have a consistently-reliable WMI routine that joins the domain and then changes the name.

It might not be the neatest implementation (feel free to comment on improvements) but it works. Enjoy.

/// <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;
  }
}

这篇关于以编程方式将 Windows 机器加入 AD 域的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆