Программное присоединение компьютера Windows к домену AD

Это похоже, но не на обман, этот вопрос - однако, где он искал информацию о ручном присоединении сервера к домену (и был правильно перенаправлен), я ищу помощь с некоторым кодом, который программно присоединяет машину к домену.

Сценарий заключается в том, что у нас есть служба запуска, которая создает экземпляры виртуальных машин Amazon EC2 Server2008R1, при необходимости передавая имя машины через поток пользовательских данных. В наши образы встроен процесс, который проверяет пользовательские данные на наличие имени при загрузке. Если его нет, виртуальная машина остается за пределами нашего облачного домена, но если имя присутствует, то машина переименовывается, как указано, и автоматически присоединяется к домен.

Вот в чем проблема - если я запускаю этот процесс вручную в любое время после запуска экземпляра, он работает точно так, как описано; имя машины изменено, и виртуальная машина присоединена к домену (мы принудительно перезапускаем, чтобы это произошло).

Однако при запуске в качестве запланированной задачи (запускаемой при запуске) переименование машины происходит, как и ожидалось, но последующий вызов JoinDomainOrWorkgroup (см. ниже) выбирает старое рандомизированное имя машины, присвоенное виртуальной машине EC2, вместо нового имени только что был назначен.

Это приводит к коду возврата WMI 8525, мы получаем отсоединенную запись с неверным именем в репозитории AD (с этим рандомизированным именем), и машина не присоединена к домену. Затем виртуальная машина перезапускается, и второй проход через процесс запуска (ненормально запущенный, потому что в User-Data есть содержимое, но машина еще не находится в домене) выполняет все те же шаги и завершается успешно.

Похоже, что имя машины задано в первом проходе, но не «доработано», и JoinDomainOrWorkgroup по-прежнему видит исходное имя. При втором проходе имя машины уже задано правильно, поэтому JoinDomainOrWorkgroup работает как положено. Я думаю, что суть проблемы заключается в том, почему процесс ведет себя таким образом во время запуска, но отлично работает при ручном запуске на уже запущенной виртуальной машине.

Я попытался вставить задержку между шагами переименования и соединения на случай, если вызов 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, и это в значительной степени взято из примеров, которые я нашел в Интернете; если есть более умный/аккуратный способ сделать это, то обязательно продемонстрируйте. Если вы можете решить проблему в то же время, бонусные баллы!


person Eight-Bit Guru    schedule 15.11.2010    source источник
comment
Дополнительная информация: вызов SetMachineName работает, но имя не меняется мгновенно - ipconfig по-прежнему показывает старое имя, а просмотр свойств системы показывает старое имя, за которым следует (изменится на XXXXXXX после перезапуска). Если SetDomainMembership получает путь управления к System.Environment.MachineName, это по-прежнему старое и неверное имя (что приводит к повреждению записи AD и неудачному соединению). Если вместо этого я передам новое имя в качестве параметра, вызов WMI завершится ошибкой с исключением «Не найдено», предположительно потому, что еще нет машины с этим новым именем.   -  person Eight-Bit Guru    schedule 16.11.2010


Ответы (2)


Хорошо, вот оно.

Во-первых, порядок полей в свойствах системы немного вводит в заблуждение — сначала вы видите имя машины, а ниже — домен/рабочая группа. Это подсознательно повлияло на мое мышление и означало, что мой код скопировал этот порядок, пытаясь сначала установить имя, а затем присоединить машину к домену. Хотя это работает при некоторых обстоятельствах, это не является последовательным или надежным. Итак, главный урок, который мы здесь извлекли...

Сначала присоединитесь к домену, а затем измените имя машины.

Да, собственно, это все. После многочисленных тестовых итераций меня, наконец, осенило, что это могло бы работать лучше, если бы я попробовал это таким образом. Я споткнулся о смене имени при первом проходе, но быстро понял, что он все еще использует учетные данные локальной системы, но теперь, когда машина была присоединена к домену, ей нужны были те же учетные данные домена, которые использовались. чтобы присоединиться к самому домену. Небольшая корректировка кода позже, и теперь у нас есть постоянно надежная процедура 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;
  }
}
person Eight-Bit Guru    schedule 17.11.2010

Хорошо, небольшое обновление после всех этих лет, если оно кому-то понадобится.

WMI больше не содержит JoinDomain только рабочую группу (WIN 10, сборка 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());

    }
}
person Johny Wave    schedule 26.11.2019