代码之家  ›  专栏  ›  技术社区  ›  Eight-Bit Guru

以编程方式将Windows计算机加入到AD域

  •  7
  • Eight-Bit Guru  · 技术社区  · 15 年前

    这类似于,但不是欺骗, this question -然而,当它寻求有关手动将服务器加入域的信息(并正确地被重定向)时,我正在寻找一些代码的帮助,这些代码以编程方式将计算机加入域。

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

    问题在于-如果我在实例启动后的任何时候手动运行此进程,它的工作方式与所描述的完全相同;计算机名称已更改,并且虚拟机已加入域(我们强制重新启动以实现此目的)。

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

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

    看起来机器名是在第一次通过时设置的,但不是“最终确定的”,并且 加入域或工作组 仍然看到原来的名字。在第二遍中,机器名已经正确设置,因此 加入域或工作组 按预期工作。我认为问题的关键在于,进程在启动时会以这种方式运行,但在已经启动的VM上手动运行时运行得很好。

    我尝试在重命名和连接步骤之间插入一个延迟,以防调用 加入域或工作组 在幕后完成重命名之前就已经发生了,但这并没有起到任何作用——我真的没想到会这样,因为手动运行时整个过程工作得很好。所以这可能是在启动期间机器状态的细微差别和代码中的一些愚蠢的东西的结合。

    也许是用 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上找到的示例中抄袭而来;如果有更聪明/更整洁的方法来实现这一点,那么请务必演示。如果你能同时解决问题,加分!

    1 回复  |  直到 8 年前
        1
  •  8
  •   Eight-Bit Guru    15 年前

    好的,给你。

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

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

    是的,事实上就是这样。经过无数次的测试迭代,我终于意识到,如果我这样尝试,它可能会工作得更好。我在第一次通过时就被名称的更改绊倒了,但很快意识到它仍在使用本地系统凭据-但现在计算机已加入到域中,因此它需要与用于加入域本身的域凭据相同的域凭据。稍后我们将快速调整代码,现在我们有了一个一致可靠的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;
      }
    }
    
        2
  •  1
  •   Johny Wave    6 年前

    好吧,如果有人需要的话,在这么多年之后更新一下。

    WMI不再包含JoinDomain,只包含工作组(WIN 10,Build 1909)。您可以使用netapi32.dll

    更多信息请点击此处:

    https://docs.microsoft.com/en-us/windows/win32/api/lmjoin/nf-lmjoin-netjoindomain

    小快速示例:

     public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    
        [DllImport("netapi32.dll", CharSet = CharSet.Unicode)]
        static extern uint NetJoinDomain(
              string lpServer,
      string lpDomain,
      string lpAccountOU,
      string lpAccount,
      string lpPassword,
          JoinOptions NameType);
    
        [Flags]
        enum JoinOptions
        {
            NETSETUP_JOIN_DOMAIN = 0x00000001,
            NETSETUP_ACCT_CREATE = 0x00000002,
            NETSETUP_ACCT_DELETE = 0x00000004,
            NETSETUP_WIN9X_UPGRADE = 0x00000010,
            NETSETUP_DOMAIN_JOIN_IF_JOINED = 0x00000020,
            NETSETUP_JOIN_UNSECURE = 0x00000040,
            NETSETUP_MACHINE_PWD_PASSED = 0x00000080,
            NETSETUP_DEFER_SPN_SET = 0x10000000
        }
    
        public static uint domainjoin(string server, string domain, string OU, string account, string password)
        {
            try
            {
                uint value1 = NetJoinDomain(server, domain, OU, account, password, (JoinOptions.NETSETUP_JOIN_DOMAIN | JoinOptions.NETSETUP_DOMAIN_JOIN_IF_JOINED | JoinOptions.NETSETUP_ACCT_CREATE));
                return value1;
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
                return 11;
            }
        }
    
    
        private void Button_Click(object sender, RoutedEventArgs e)
        {
    
            var succes = domainjoin(null, "mydomain.local", null, "administrator", "UltraSecretPasword");
            MessageBox.Show(succes.ToString());
    
        }
    }