这与this question类似,但不是它的一个骗局-然而,当它寻求有关手动将服务器加入域的信息(并正确地被重定向)时,我正在寻找一些代码的帮助,这些代码以编程方式将计算机加入域。
场景是我们有一个启动服务,它实例化amazonec2server2008r1vms,可以选择通过用户数据流传入一个机器名。一个进程被烘焙到我们的映像中,在启动时检查用户数据中的名称-如果不存在任何名称,则虚拟机将保留在我们的云域之外,但如果该名称存在,则机器将按指定重命名并自动加入域。
问题在于-如果我在实例启动后的任何时候手动运行此进程,它的工作方式与所描述的完全相同;计算机名称已更改,并且虚拟机已加入域(我们强制重新启动以实现此目的)。
但是,当作为计划任务运行(启动时触发)时,机器重命名按预期进行,但随后对JoinDomainOrWorkgroup(见下文)的调用将采用ec2给vm的旧随机机器名,而不是刚刚分配的新名称。
这将导致wmi返回代码8525,我们在广告存储库中得到一个断开连接的错误命名条目(该随机名称),并且计算机未加入域。然后,vm重新启动,第二次通过启动过程(由于用户数据中有内容,但计算机还不在域中,因此异常触发)执行所有相同的步骤并成功。
看起来机器名是在第一次传递中设置的,但不是“最终确定的”,而且JoinDomainOrWorkgroup仍然可以看到原始名称。在第二次传递中,机器名已经正确设置,因此JoinDomainOrWorkgroup按预期工作。我认为问题的关键在于,进程在启动时会以这种方式运行,但在已经启动的vm上手动运行时运行得很好。
我试过在rename和join步骤之间插入一个延迟,以防在幕后完成rename之前调用JoinDomainOrWorkgroup,但这并没有起到作用——而且我也没想到会这样,因为手动运行时整个过程工作得很好。所以这可能是在启动期间机器状态的细微差别和代码中的一些愚蠢的东西的结合。
也许在System.Environment.MachineName方法中使用SetDomainMembership是不可取的?但它仍然失败,即使我像对SetMachineName那样将新名称作为字符串传入。所以我被难住了。
以下是重命名计算机的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新手,而且这大部分都是从我在interwebs上找到的示例中抄袭而来;如果有更聪明/更整洁的方法来实现这一点,那么请务必演示。如果你能同时解决问题,加分!

最佳答案

好的,给你。
首先,系统属性中字段的顺序有点误导-您首先看到的是计算机名,下面是域/工作组。这下意识地影响了我的思维,意味着我的代码通过尝试先设置名称,然后将机器加入到域中,复制了这个顺序。虽然这在某些情况下确实有效,但并不一致或可靠。所以这里最大的教训是…
先加入域-然后更改
机器名。
是的,事实上就是这样。经过无数次的测试迭代,我终于意识到,如果我这样尝试,它可能会工作得更好。我在第一次通过时就被名称的更改绊倒了,但很快意识到它仍在使用本地系统凭据-但现在计算机已加入到域中,因此它需要与用于加入域本身的域凭据相同的域凭据。稍后我们将快速调整代码,现在我们有了一个一致可靠的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;
  }
}

10-08 04:48