Программно присоединить Windows-машину к домену AD - PullRequest
7 голосов
/ 15 ноября 2010

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

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

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

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

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

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

1 Ответ

7 голосов
/ 17 ноября 2010

ОК, вот оно.

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

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

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