2010-11-15 10 views
7

Ceci est similaire à, mais pas dupe de, this question - cependant, où il cherchait des informations sur joindre manuellement un serveur à un domaine (et a été redirigé à juste titre) je cherche de l'aide avec certains code qui joint par programme une machine à un domaine.Joindre par programme Windows machine au domaine AD

Le scénario est que nous avons un service de lancement qui instancie les machines virtuelles Amazon EC2 Server2008R1, en transmettant éventuellement un nom de machine via le flux de données utilisateur. Un processus est cuit dans nos images qui vérifie les données utilisateur pour un nom au démarrage - Si aucune n'est présente, la VM reste en dehors de notre domaine Cloud, mais si le nom est présent, la machine est renommée et auto-jointe à le domaine.

Voici le problème - si je lance ce processus manuellement à tout moment après le démarrage de l'instance, cela fonctionne exactement comme décrit; le nom de la machine est changé, et la machine virtuelle est jointe au domaine (nous forçons un redémarrage pour que cela se produise). Cependant, lors de l'exécution en tant que tâche planifiée (déclenchée au démarrage), le renommage de l'ordinateur se produit comme prévu, mais l'appel suivant à JoinDomainOrWorkgroup (voir ci-dessous) récupère l'ancien nom de machine aléatoire donné à la machine virtuelle par EC2 au lieu de le nouveau nom qui vient d'être attribué. Il en résulte un code de retour WMI , nous obtenons une entrée mal nommée déconnectée dans le référentiel AD (de ce nom randomisé) et la machine n'est pas jointe au domaine. La machine virtuelle redémarre ensuite, et une seconde passe à travers le processus de démarrage (anormalement déclenchée car il y a du contenu dans les données utilisateur mais la machine n'est pas encore dans le domaine) exécute toutes les mêmes étapes et réussit.

Il semble que le nom de la machine soit défini lors de la première passe, mais pas 'finalisé', et JoinDomainOrWorkgroup voit toujours le nom d'origine. Lors de la deuxième passe, le nom de la machine est déjà correctement défini et JoinDomainOrWorkgroup fonctionne comme prévu. Tout à fait pourquoi le processus se comporte de cette façon au démarrage, mais fonctionne parfaitement lorsqu'il est exécuté manuellement sur une machine virtuelle déjà démarrée, je pense que c'est le nœud du problème.

J'ai essayé d'insérer un délai entre les étapes de renommage et de jointure au cas où l'appel à JoinDomainOrWorkgroup se produisait avant que le renommage soit finalisé dans les coulisses, mais cela n'a pas aidé - et je ne m'attendais pas vraiment à ça , puisque tout le processus fonctionne parfaitement lorsqu'il est exécuté manuellement. Donc, c'est probablement une combinaison d'une différence subtile dans l'état de la machine pendant le démarrage et quelque chose de stupide dans le code.

Peut-être que l'utilisation de System.Environment.MachineName dans la méthode SetDomainMembership est déconseillée? Mais il échoue même si je passe le nouveau nom en tant que chaîne comme je le fais pour SetMachineName. Donc je suis perplexe.

Voici le code WMI qui renomme la 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; 
    } 
    } 
} 

Et voici le code WMI qu'il se joint au domaine:

/// <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; 
    } 
    } 
} 

Toutes mes excuses si ce code semble abrutissantes stupide - je Je suis nouveau à WMI, et ceci est en grande partie criblé des exemples que j'ai trouvés sur les interwebs; S'il y a une façon plus intelligente/plus pratique de le faire, alors démontrez par tous les moyens. Si vous pouvez résoudre le problème en même temps, des points bonus!

+0

Informations complémentaires: l'appel à 'fonctionne SetMachineName', mais le nom ne change pas instantanément -' ipconfig' affiche toujours l'ancien nom, et regardant Propriétés système affiche l'ancien nom suivi de « (va changer à XXXXXXX après le redémarrage) ". Si 'SetDomainMembership' obtient un chemin de gestion vers System.Environment.MachineName, il s'agit toujours de l'ancien nom et il est incorrect (ce qui conduit à une entrée AD rompue et une jointure ayant échoué). Si je passe plutôt le nouveau nom en paramètre, l'appel WMI échoue avec une exception 'Not Found', vraisemblablement parce qu'il n'y a pas encore de machine avec ce nouveau nom fermement défini. –

Répondre

5

OK, le voilà.Tout d'abord, l'ordre des champs dans les propriétés du système est un peu trompeur - vous voyez d'abord le nom de la machine, et le domaine/groupe de travail ci-dessous. Cela a inconsciemment affecté ma pensée, et signifiait que mon code copiait cet ordre en essayant de définir le nom en premier, puis de rejoindre la machine au domaine. Bien que cela fonctionne dans certaines circonstances, ce n'est pas cohérent ou fiable. Donc la plus grande leçon apprise ici est ...

Rejoindre le domaine en premier - puis changer le nom de la machine.

Oui, c'est tout ce qu'il y a à faire. Après de nombreuses itérations de test, je me suis finalement rendu compte que cela pourrait fonctionner mieux si je l'essayais de cette façon. J'ai trébuché sur le changement de nom lors de mon premier passage, mais j'ai rapidement réalisé qu'il utilisait toujours les informations d'identification du système local - mais maintenant que la machine était jointe au domaine, elle avait besoin des mêmes informations de domaine que celles utilisées pour rejoindre le domaine lui-même. Une modification rapide du code plus tard, et nous avons maintenant une routine WMI toujours fiable qui rejoint le domaine, puis change le nom.

Ce n'est peut-être pas la meilleure implémentation (n'hésitez pas à commenter les améliorations) mais cela fonctionne. Prendre plaisir.

/// <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; 
    } 
} 
Questions connexes