2

I've been rewriting my company's domain join script, and I'm using the "JoinDomainOrWorkgroup" method in C# to join the computer to the domain:

void Join(string newPCName, string destinationOU)
    {
        // Define constants used in the method.
        int JOIN_DOMAIN = 1;
        int ACCT_CREATE = 2;
        int ACCT_DELETE = 4;
        int WIN9X_UPGRADE = 16;
        int DOMAIN_JOIN_IF_JOINED = 32;
        int JOIN_UNSECURE = 64;
        int MACHINE_PASSWORD_PASSED = 128;
        int DEFERRED_SPN_SET = 256;
        int INSTALL_INVOCATION = 262144;

        string domain = "MyDomain.com";
        string password = passwordBox.Text;
        string username = usernameBox.Text;

        // Here we will set the parameters that we like using the logical OR operator.
        // If you want to create the account if it doesn't exist you should add " | ACCT_CREATE "
        int parameters = JOIN_DOMAIN | ACCT_CREATE;

        // The arguments are passed as an array of string objects in a specific order
        object[] methodArgs = { domain, password, username, destinationOU, parameters };

        // Here we construct the ManagementObject and set Options
        ManagementObject computerSystem = new ManagementObject("Win32_ComputerSystem.Name='" + Environment.MachineName + "'");
        computerSystem.Scope.Options.Authentication = System.Management.AuthenticationLevel.PacketPrivacy;
        computerSystem.Scope.Options.Impersonation = ImpersonationLevel.Impersonate;
        computerSystem.Scope.Options.EnablePrivileges = true;

        // Here we invoke the method JoinDomainOrWorkgroup passing the parameters as the second parameter
        object Oresult = computerSystem.InvokeMethod("JoinDomainOrWorkgroup", methodArgs);

        // The result is returned as an object of type int, so we need to cast.
        int result = (int)Convert.ToInt32(Oresult);

        // If the result is 0 then the computer is joined.
        if (result == 0)
        {
            MessageBox.Show("Joined Successfully!");
            this.Close();
            return;
        }
        else
        {
            // Here are the list of possible errors
            string strErrorDescription = " ";
            switch (result)
            {
                case 5:
                    strErrorDescription = "Access is denied";
                    break;
                case 87:
                    strErrorDescription = "The parameter is incorrect";
                    break;
                case 110:
                    strErrorDescription = "The system cannot open the specified object";
                    break;
                case 1323:
                    strErrorDescription = "Unable to update the password";
                    break;
                case 1326:
                    strErrorDescription = "Logon failure: unknown username or bad password";
                    break;
                case 1355:
                    strErrorDescription = "The specified domain either does not exist or could not be contacted";
                    break;
                case 2224:
                    strErrorDescription = "The account already exists";
                    break;
                case 2691:
                    strErrorDescription = "The machine is already joined to the domain";
                    break;
                case 2692:
                    strErrorDescription = "The machine is not currently joined to a domain";
                    break;
            }
            MessageBox.Show(strErrorDescription);
            return;
        }
    }

It works great! The only problem is I need it to use the new name, not the current machine name when joining the domain, and can't figure out how to do it without a reboot between changing the PC name programmatically, and then running this code.

We've been using the following PowerShell command to join the domain, which has allowed us to join the domain using a new name, then restarting and setting the new name in one reboot:

Add-Computer -NewName $ComputerName.ToUpper() -DomainName "MyDomain.com" -Credential $cred -OUPath $Target -ErrorAction Continue

Is there a way to achieve this in C#? I've tried changing the line:

ManagementObject computerSystem = new ManagementObject("Win32_ComputerSystem.Name='" + Environment.MachineName + "'");

to:

ManagementObject computerSystem = new ManagementObject("Win32_ComputerSystem.Name='" + newPCName + "'");

but it just throws an error if "newPCName" doesn't match the current PC name.

Does anyone have any ideas on how to reference either the "pending" PC name, or join the domain without referencing the current machine name?

Thanks a ton!

  • Did you try Rename first then Join? https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/rename-method-in-class-win32-computersystem – smoore4 Jul 12 '19 at 18:02
  • Just figured it out, needed to join first and then rename. Thanks though! – justin_hoch Jul 12 '19 at 18:15

2 Answers2

0

I finally managed to get it to work; I needed to restructure my code to add the computer to the domain first, then change the name of the computer.

Pasting my code here to help others who might have the same problem:

public static bool JoinAndSetName(string newName, string target, string username, string password)
    {
        // Get WMI object for this machine
        using (ManagementObject computerSystem = new ManagementObject("Win32_ComputerSystem.Name='" + Environment.MachineName + "'"))
        {
            try
            {
                object[] methodArgs = { "MyDomain.com", password, username, target, 3 };
                computerSystem.Scope.Options.Authentication = System.Management.AuthenticationLevel.PacketPrivacy;
                computerSystem.Scope.Options.Impersonation = ImpersonationLevel.Impersonate;
                computerSystem.Scope.Options.EnablePrivileges = true;
                object joinParams = computerSystem.InvokeMethod("JoinDomainOrWorkgroup", methodArgs);                 
            }
            catch (ManagementException e)
            {
                MessageBox.Show("Join to domain didn't work");
                return false;
            }

            // Join to domain worked - now change name
            ManagementBaseObject inputArgs = computerSystem.GetMethodParameters("Rename");
            inputArgs["Name"] = newName;
            inputArgs["Password"] = password;
            inputArgs["UserName"] = username;

            // Set the name
            ManagementBaseObject nameParams = computerSystem.InvokeMethod("Rename", inputArgs, null);

            if ((uint)(nameParams.Properties["ReturnValue"].Value) != 0)
            {
                MessageBox.Show("Name change didn't work");
                return false;
            }

            // All ok
            return true;
        }
    }

Definitely want to credit This post; I modified it to include specifying an OU Path and adding authentication privacy.

0

I know this is a little bit older question, but here what I ended up doing to rename the computer and join the domain using new computer name:

  1. Add below code in static class or somewhere you want
public enum COMPUTER_NAME_FORMAT
{
        ComputerNameNetBIOS,
        ComputerNameDnsHostname,
        ComputerNameDnsDomain,
        ComputerNameDnsFullyQualified,
        ComputerNamePhysicalNetBIOS,
        ComputerNamePhysicalDnsHostname,
        ComputerNamePhysicalDnsDomain,
        ComputerNamePhysicalDnsFullyQualified,
}
[Flags]
public enum DomainJoinOptions
{
        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 = 0x00000100,
        NETSETUP_JOIN_WITH_NEW_NAME = 0x00000400
}

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool SetComputerNameEx(COMPUTER_NAME_FORMAT NameType, string lpBuffer);

[DllImport("netapi32.dll", CharSet = CharSet.Unicode)]
public static extern int NetJoinDomain(
          string lpServer,
          string lpDomain,
          string lpAccountOU,
          string lpAccount,
          string lpPassword,
          DomainJoinOptions NameType
);

in the above code important is CharSet = CharSet.Unicode for NetJoinDomain method.

  1. First call SetComputerNameEx as:
SetComputerNameEx(COMPUTER_NAME_FORMAT.ComputerNamePhysicalDnsHostname,"Name of the computer");
  1. Without rebooting now call the domain join as:
var joinOptions = DomainJoinOptions.NETSETUP_JOIN_DOMAIN | DomainJoinOptions.NETSETUP_DOMAIN_JOIN_IF_JOINED | DomainJoinOptions.NETSETUP_ACCT_CREATE | DomainJoinOptions.NETSETUP_JOIN_WITH_NEW_NAME;

var domain = "mydomain.internal";
var result = WinApi.NetJoinDomain(
    lpServer: null,
    lpDomain: domain,
    lpAccountOU: "OU of the computer or null",
    lpAccount: $@"{domain}\{AdminUsername}",
    lpPassword: AdminPassword,
    NameType: joinOptions
);

if (result == 0)
{
    Log.Information("Domain successfully joined");
    return;
}

Log.Error(messageTemplate: "Domain join failed with error {@code}", propertyValue: result);
var strErrorDescription = string.Empty;
switch (result)
{
     case 5:
          strErrorDescription = "Access is denied";
          break;
     case 87:
          strErrorDescription = "The parameter is incorrect";
          break;
     case 110:
          strErrorDescription = "The system cannot open the specified object";
          break;
     case 1323:
          strErrorDescription = "Unable to update the password";
          break;
     case 1326:
          strErrorDescription = "Logon failure: unknown username or bad password";
          break;
     case 1355:
          strErrorDescription = "The specified domain either does not exist or could not be contacted";
          break;
     case 2224:
          strErrorDescription = "The account already exists";
          break;
     case 2691:
          strErrorDescription = "The machine is already joined to the domain";
          break;
     case 2692:
          strErrorDescription = "The machine is not currently joined to a domain";
          break;
     default:
          Log.Error($"Unhandled result received: {result}");
          break;
}
throw new DomainJoinException(code: result.ToString(), message: strErrorDescription);

in the above code important is DomainJoinOptions.NETSETUP_JOIN_WITH_NEW_NAME as option and null as lpServer.

  1. Finally call reboot on the computer.
Pratik Gaikwad
  • 1,526
  • 2
  • 21
  • 44