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

2023-09-11 08:06:08 作者:他伤你最深却偏得你心

这是类似的,但不是欺骗的,this问题 - 但是,当它试图手动加入一个服务器到域信息(并正确地重定向)我在寻找帮助一些code以编程方式连接一台机器到域

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.

的情况是,我们有一个实例化的Amazon EC2 Server2008R1的虚拟机,可选地传递一个机器名称在通过用户数据流的发射服务。一种是烤到我们的图像,检查用户数据在启动时的名称 - 如果没有为present则VM仍然是我们的云域之外,但如果名称是present那么这台机器被重命名为指定和自动加入域

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).

然而,作为计划任务运行时(在启动时触发)机重命名情况不如预期,但后续调用了JoinDomainOrWorkgroup (见下文)拿起向上的通过EC2来代替它刚刚被赋予新名字给虚拟机随机旧机器名。

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.

这导致的一个WMI返回code 8525 ,我们得到的AD库断开连接名不副实的条目(即随机名)和机器没有加入到域。虚拟机然后重新启动,并第二次通过启动过程(异常引发的,因为在用户数据内容,但该机尚未在域)执行所有相同的步骤和成功。

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.

它看起来像机器名被设置在第一遍而不是完成,而了JoinDomainOrWorkgroup 仍然可以看到原来的名称。在二传,计算机名称已正确设置,并让了JoinDomainOrWorkgroup 按预期工作。相当为什么这个过程的行为这种方式启动过程中,但在已经启动的虚拟机手动运行时完美的作品,是我认为问题的症结。

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.

我试着插入重命名之间的延迟和连接的情况下步骤调用了JoinDomainOrWorkgroup 正在发生之前重命名定稿幕后,但是这不是招'牛逼帮助 - 我真的不指望它,因为手动运行时的整个过程完美。所以,这可能是机器状态的细微差别,并启动一些愚蠢的code时的组合。

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.

也许使用 System.Environment.MachineName SetDomainMembership 的方法是不可取的?但是,即使我在通过新的字符串名字,因为我做的 SetMachineName STIL失败。所以,我很为难。

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.

这里的WMI code,它重命名机器:

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

和这里的WMI code,它加入到域:

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

道歉,如果这code看起来头脑麻木愚蠢的 - 我是新来的WMI,这主要是从我的interwebs找到例子cribbed;如果有一个更聪明/整洁的方式做到这一点,然后通过各种手段证明。如果你能在同一时间治愈的问题,加分!

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!

推荐答案

好了,在这儿呢。

首先,在系统属性中的字段的顺序是有点误导 - 你看到机器的名头,和域/工作组下面的。这种下意识地影响了我的思想,我的意思code复制的顺序试图首先设置的名称,然后将计算机加入到域。虽然这样做在某些情况下工作,它不是一致或可靠。所以,最大的教训在这里学到的是...

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.

是的,这实际上是所有有给它。经过无数次的反复试验,终于醒悟过来了,它可能会更好地工作,如果我尝试过解决这个方法。我绊倒-上的名字我第一遍的变化,但很快就意识到,这是仍然使用本地系统凭据 - 但现在,该机也加入到这一点的领域,它需要在同一个域中的凭据,用于加入域本身。 code-调整的速度快一点后,我们现在有一个一致的,可靠的WMI例程加入到域,然后更改名称。

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;
  }
}
 
精彩推荐