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

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

问题描述

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

这种情况是我们有一个启动器服务来实例化亚马逊EC2 Server2008R1虚拟机,可选择通过用户数据流传递机器名称。我们的图像中会检查一个进程,以检查用户数据的启动名称 - 如果没有,则VM保留在我们的Cloud域之外,但如果名称存在,则该机器将按照指定重新命名并自动加入该域名。



以下是问题 - 如果在实例启动后的任何时候手动运行此过程,它的工作原理与描述一致;机器名称已更改,虚拟机已加入域(我们强制重新启动使其发生)。



但是,当作为计划任务运行时(触发在启动时)机器重命名按预期发生,但随后调用 JoinDomainOrWorkgroup (见下文)将EC2提供给VM的旧随机机器名称替代新的名称刚被分配。



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



看起来机器名称设置在第一遍但不是finalized,而 JoinDomainOrWorkgroup 仍然看到原始名称。在第二遍,机器名称已经正确设置,因此 JoinDomainOrWorkgroup 按预期工作。为什么进程在启动过程中表现如此,但是在已经启动的虚拟机上手动运行时可以完美运行,我认为问题的结果。



尝试在重命名和连接步骤之间插入延迟,以防在重命名在幕后确定之后发生调用 JoinDomainOrWorkgroup ,但这没有帮助 - 而且我没有真的希望它,因为整个过程在手动运行时完美运行。所以这可能是在启动过程中机器状态的细微差别和代码中的愚蠢的组合。



可能使用 System.Environment.MachineName SetDomainMembership 方法是不合适的?但是,即使我像 SetMachineName 一样以字符串形式传递新名称,它仍然失败。所以我被困了。



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

  ///< summary> 
///设置机器名
///< / summary>
public static bool SetMachineName(string newName)
{
_lh.Log(LogHandler.LogType.Debug,string.Format(将机器名称设置为{0}...了newName));

//调用WMI使用(ManagementObject wmiObject = new ManagementObject(Win32_ComputerSystem.Name ='+ System.Environment.MachineName +')来填充机器名称
) ))
{
ManagementBaseObject inputArgs = wmiObject.GetMethodParameters(Rename);
inputArgs [Name] = newName;

//设置名称
ManagementBaseObject outParams = wmiObject.InvokeMethod(Rename,inputArgs,null);

//很奇怪的WMI shennanigans得到一个返回码(没有更好的方法来做这个)
uint ret =(uint)(outParams.Properties [ReturnValue]。值);
if(ret == 0)
{
//它工作
返回true;
}
else
{
//它没有工作
_lh.Log(LogHandler.LogType.Fatal,string.Format(Unable to change Machine Name从{0}到{1},System.Environment.MachineName,newName));
返回false;
}
}
}

这里是WMI代码将它加入域:

  ///< summary> 
///设置域成员
///< / summary>
public static bool SetDomainMembership()
{
_lh.Log(LogHandler.LogType.Debug,string.Format(将域{}的域成员设置为{1}。 ..,System.Environment.MachineName,_targetDomain));

//调用WMI加入域
使用(ManagementObject wmiObject = new ManagementObject(new ManagementPath(Win32_ComputerSystem.Name ='+ System.Environment.MachineName +')) )
{
try
{
//获取方法的参数
ManagementBaseObject inParams = wmiObject.GetMethodParameters(JoinDomainOrWorkgroup);

inParams [Name] =*****;
inParams [Password] =*****;
inParams [UserName] =*****;
inParams [FJoinOptions] = 3; // Magic number: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]。
// if(ret!= 0)
// {
// // Nope
// _lh.Log(LogHandler.LogType.Fatal,string.Format( JoinDomainOrWorkgroup失败,返回码为:'{0}',outParams [ReturnValue]));
// return false;
//}

return true;
}
catch(ManagementException e)
{
//它没有工作
_lh.Log(LogHandler.LogType.Fatal,string.Format(Unable加入域{0},_targetDomain),e);
返回false;
}
}
}

- 愚蠢 - 我是WMI的新手,这主要是从互联网上发现的例子中得到的;如果有一个更聪明/更干净的方式来做到这一点,一切手段证明。如果您可以同时解决问题,请点击奖励积分!

解决方案

b
$ b

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


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

是的,这其实是全部的。经过无数次的测试迭代,它终于醒悟了我,如果我这样做,它可能会更好的工作。我在第一次通过时更改了名称,但很快就意识到它仍然使用本地系统凭据 - 但是现在这台机器已经加入了域,所以它需要与使用的域名相同的域凭据加入域名本身。稍后进行代码调整,我们现在有一个可靠的WMI例程,加入域名然后更改名称。



可能不是最整洁的实施(随意评论改进),但它的工作。享受。

  ///< summary> 
///加入域并设置机器名
///< / summary>
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 +')))
{
try
{
//获取方法的参数
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]));
返回false;
}
}
catch(ManagementException e)
{
//加入域不起作用
_lh.Log(LogHandler.LogType.Fatal ,string.Format(无法加入域{0},_targetDomain),e);
返回false;
}

//加入域工作 - 现在更改名称
ManagementBaseObject inputArgs = wmiObject.GetMethodParameters(Rename);
inputArgs [Name] = newName;
inputArgs [Password] =domain_account_password;
inputArgs [UserName] =domain_account;

//设置名称
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)
{
//名称更改不起作用
_lh.Log(LogHandler.LogType.Fatal,string.Format(Unable to change Machine Name from{0}to{1},Environment.MachineName,newName));
返回false;
}

//所有ok
返回true;
}
}


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天全站免登陆