Author: arkurth
Date: Fri May  5 21:35:19 2017
New Revision: 1794108

URL: http://svn.apache.org/viewvc?rev=1794108&view=rev
Log:
VCL-867
Updated AD code to use user service principal formatted names when 
authenticating. It was failing if the username was longer than 15 characters.

Added experimental Windows.pm::ad_join_wmic subroutine. It joins a computer to 
the domain using wmic.exe instead of constructing a PowerShell script.

Modified:
    vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm

Modified: vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm
URL: 
http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm?rev=1794108&r1=1794107&r2=1794108&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm Fri May  5 21:35:19 
2017
@@ -13751,11 +13751,13 @@ sub ad_join {
                $domain_computer_command_section = "-OUPath 
\"$computer_ou_dn\"";
        }
        
+       my $domain_user_string = "$domain_username\@$domain_dns_name";
+       
        notify($ERRORS{'DEBUG'}, 0, "attempting to join $computer_name to AD\n" 
.
-               "domain DNS name: $domain_dns_name\n" .
-               "domain user: $domain_username\n" .
-               "domain password: $domain_password\n" .
-               "domain computer OU DN: " . ($computer_ou_dn ? $computer_ou_dn 
: '<not configured>')
+               "domain DNS name    : $domain_dns_name\n" .
+               "domain user string : $domain_user_string\n" .
+               "domain password    : $domain_password\n" .
+               "domain computer OU : " . ($computer_ou_dn ? $computer_ou_dn : 
'<not configured>')
        );
        
        # Perform preparation tasks
@@ -13763,7 +13765,7 @@ sub ad_join {
        
        # Assemble the PowerShell script
        my $ad_powershell_script = <<EOF;
-\$ps_credential = New-Object 
System.Management.Automation.PsCredential("$domain_dns_name\\$domain_username", 
(ConvertTo-SecureString "$domain_password" -AsPlainText -Force))
+\$ps_credential = New-Object 
System.Management.Automation.PsCredential("$domain_user_string", 
(ConvertTo-SecureString "$domain_password" -AsPlainText -Force))
 Add-Computer -DomainName "$domain_dns_name" -Credential \$ps_credential 
$domain_computer_command_section -Verbose -ErrorAction Stop
 EOF
 
@@ -13863,6 +13865,181 @@ EOF
 
 #//////////////////////////////////////////////////////////////////////////////
 
+=head2 ad_join_wmic
+
+ Parameters  : none
+ Returns     : boolean
+ Description : Joins the computer to the Active Directory domain configured for
+               the image using the wmic.exe utility as opposed to a PowerShell
+               script.
+
+=cut
+
+sub ad_join_wmic {
+       my $self = shift;       
+       if (ref($self) !~ /windows/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return;
+       }
+       
+       # Calculate how long the tasks take
+       my $start_time = time;
+       my $rename_computer_reboot_duration = 0;
+       my $ad_join_reboot_duration = 0;
+       
+       my $computer_name       = $self->data->get_computer_node_name();
+       my $image_name  = $self->data->get_image_name();
+       
+       my $system32_path = $self->get_system32_path() || return;
+       
+       my $domain_dns_name = $self->data->get_image_domain_dns_name();
+       my $domain_username = $self->data->get_image_domain_username();
+       my $domain_password = $self->data->get_image_domain_password();
+       my $computer_ou_dn = $self->get_ad_computer_ou_dn();
+       
+       if (!defined($domain_dns_name)) {
+               notify($ERRORS{'WARNING'}, 0, "unable to add $computer_name to 
AD, image $image_name is not assigned to a domain");
+               return;
+       }
+       elsif (!defined($domain_username)) {
+               notify($ERRORS{'WARNING'}, 0, "unable to add $computer_name to 
AD, user name is not configured for $domain_dns_name domain");
+               return;
+       }
+       elsif (!defined($domain_password)) {
+               notify($ERRORS{'WARNING'}, 0, "unable to add $computer_name to 
AD, password is not configured for $domain_dns_name domain");
+               return;
+       }
+       
+       if ($computer_ou_dn) {
+               # OU=testOU,DC=domain,DC=Domain,DC=com --> OU=testOU; 
DC=domain; DC=Domain; DC=com
+               $computer_ou_dn =~ s/\s*,\s*/; /g;
+       }
+       
+       my $domain_password_escaped = quotemeta($domain_password);
+       
+       # Check if the computer needs to be renamed
+       my $current_computer_hostname = $self->get_current_computer_hostname() 
|| '<unknown>';
+       if (lc($current_computer_hostname) ne lc($computer_name)) {
+               notify($ERRORS{'DEBUG'}, 0, "$computer_name needs to be 
renamed, current hostname: '$current_computer_hostname'");
+               
+               if (!$self->set_computer_hostname()) {
+                       notify($ERRORS{'WARNING'}, 0, "failed to join 
$computer_name to Active Directory domain, PowerShell version does NOT support 
Rename-Computer, failed to rename using traditional method");
+                       return;
+               }
+                       
+               my $rename_computer_reboot_start = time;
+               if (!$self->reboot(300, 3, 1)) {
+                       notify($ERRORS{'WARNING'}, 0, "failed to join 
$computer_name to Active Directory domain, failed to reboot computer after it 
was renamed");
+                       return;
+               }
+               $rename_computer_reboot_duration = (time - 
$rename_computer_reboot_start);
+       }
+       
+       my $error_messages = {
+               5    => 'Access is denied',
+               53   => 'Network path not found',
+               87   => 'The parameter is incorrect',
+               110  => 'The system cannot open the specified object',
+               1323 => 'Unable to update the password',
+               1332 =>'No mapping between account names and security IDs was 
done',
+               1326 => 'Logon failure: unknown username or bad password',
+               1355 => 'The specified domain either does not exist or could 
not be contacted',
+               1909 => 'User account locked out',
+               2224 => 'The account already exists',
+               2691 => 'The machine is already joined to the domain',
+               2692 => 'The machine is not currently joined to a domain',
+               2695 => 'The specified workgroup name is invalid',
+               2697 => 'The specified computer account could not be found',
+               8206 => 'The directory service is busy',
+       };
+
+       # Perform preparation tasks
+       $self->ad_join_prepare() || return;
+       
+       # NETSETUP_JOIN_DOMAIN                      1 (0x00000001) - Join 
domain. If not specified, joins workgroup.
+       # NETSETUP_ACCT_CREATE                      2 (0x00000002) - Create 
domain computer account
+       # NETSETUP_DOMAIN_JOIN_IF_JOINED           32 (0x00000020) - Join 
domain if computer is already joined to a domain.
+       # NETSETUP_DEFER_SPN_SET                  256 (0x00000100) - Don't 
update service principal name (SPN) and the DnsHostName properties on the 
computer. They should be updated in a subsequent call to Rename
+       # NETSETUP_JOIN_WITH_NEW_NAME             512 (0x00000400) - 
+       my $join_options = 0;
+       $join_options += 1;
+       $join_options += 2;
+       #$join_options += 32;
+       #$join_options += 256;
+       #$join_options += 512;
+       
+       # Assemble the join command
+       #    Name - domain or workgroup to join
+       #    Password
+       #    UserName - NetBIOS name Domain\sAMAccountName or user principal 
name: username@domain.
+       #    AccountOU - format: "OU=testOU; DC=domain; DC=Domain; DC=com"
+       #    FJoinOptions - join option bit flags, (0) Default. No join options.
+       my $join_command = 'echo | cmd.exe /c "';
+       $join_command .= "$system32_path/Wbem/wmic.exe /INTERACTIVE:OFF 
ComputerSystem WHERE Name=\\\"%COMPUTERNAME%\\\" Call JoinDomainOrWorkgroup";
+       $join_command .= " Name=$domain_dns_name";
+       $join_command .= " UserName=\\\"$domain_username\@$domain_dns_name\\\"";
+       $join_command .= " Password=\"$domain_password_escaped\"";
+       $join_command .= " AccountOU=\\\"$computer_ou_dn\\\"" if 
($computer_ou_dn);
+       $join_command .= " FjoinOptions=$join_options";
+       $join_command .= '"';
+       
+       notify($ERRORS{'DEBUG'}, 0, "attempting to join $computer_name to 
Active Directory domain $domain_dns_name using wmic.exe, 
command:\n$join_command");
+       my ($join_exit_status, $join_output) = $self->execute($join_command);
+       if (!defined($join_output)) {
+               notify($ERRORS{'DEBUG'}, 0, "failed to execute command to join 
$computer_name to Active Directory domain $domain_dns_name using wmic.exe");
+               return;
+       }
+       # Executing 
(\\WIN7\ROOT\CIMV2:Win32_ComputerSystem.Name="WIN7")->JoinDomainOrWorkgroup()
+       # Method execution successful.
+       # Out Parameters:
+       # instance of __PARAMETERS
+       # {
+       #    ReturnValue = 0;
+       # };
+       my $join_output_string = join("\n", @$join_output);
+       my ($join_return_value) = $join_output_string =~ /ReturnValue = (\d+);/;
+       if (!defined($join_return_value)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to join $computer_name to 
Active Directory domain $domain_dns_name using wmic.exe, output does not 
contain 'ReturnValue =':\n$join_output_string");
+               return;
+       }
+       elsif ($join_return_value == 0) {
+               notify($ERRORS{'OK'}, 0, "joined $computer_name to Active 
Directory domain $domain_dns_name using wmic.exe");
+       }
+       elsif (my $error_message = $error_messages->{$join_return_value}) {
+               notify($ERRORS{'WARNING'}, 0, "failed to join $computer_name to 
Active Directory domain $domain_dns_name using wmic.exe\nreason: 
$error_message\noutput:\n$join_output_string");
+               return;
+       }
+       else {
+               notify($ERRORS{'WARNING'}, 0, "failed to join $computer_name to 
Active Directory domain $domain_dns_name using wmic.exe for an unknown reason, 
to troubleshoot, search the web for 'wmic.exe ComputerSystem 
JoinDomainOrWorkgroup ReturnValue $join_return_value', 
output:\n$join_output_string");
+               return;
+       }
+       
+       # Reboot, computer should be joined to AD with the correct hostname
+       # If computer had to be rebooted to be renamed, certain tasks in 
reboot() don't need to be performed again
+       # Set reboot()'s last $pre_configure flag accordingly
+       my $ad_join_reboot_pre_configure = ($rename_computer_reboot_duration ? 
0 : 1);
+       
+       my $ad_join_reboot_start = time;
+       if (!$self->reboot(300, 3, 1, $ad_join_reboot_pre_configure)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to join $computer_name to 
Active Directory domain, failed to reboot computer after it joined the domain");
+               return;
+       }
+       $ad_join_reboot_duration = (time - $ad_join_reboot_start);
+       
+       my $total_duration = (time - $start_time);
+       my $other_tasks_duration = ($total_duration - 
$rename_computer_reboot_duration - $ad_join_reboot_duration);
+       
+       notify($ERRORS{'DEBUG'}, 0, "successfully joined $computer_name to 
Active Directory domain: $domain_dns_name, time statistics:\n" .
+               "computer rename reboot : $rename_computer_reboot_duration 
seconds\n" .
+               "AD join reboot         : $ad_join_reboot_duration seconds\n" .
+               "other tasks            : $other_tasks_duration seconds\n" .
+               "total                  : $total_duration seconds"
+       );
+       return 1;
+}
+
+#//////////////////////////////////////////////////////////////////////////////
+
 =head2 ad_unjoin
 
  Parameters  : none
@@ -13970,7 +14147,7 @@ sub ad_unjoin {
        #       # Assemble the PowerShell script
        #       my $ad_powershell_script = <<EOF;
        #\$Host.UI.RawUI.BufferSize = New-Object 
Management.Automation.Host.Size(5000, 500)
-       #\$ps_credential = New-Object 
System.Management.Automation.PsCredential("$domain_dns_name\\$domain_username", 
(ConvertTo-SecureString "$domain_password" -AsPlainText -Force))
+       #\$ps_credential = New-Object 
System.Management.Automation.PsCredential("$domain_username\@$domain_dns_name", 
(ConvertTo-SecureString "$domain_password" -AsPlainText -Force))
        #try {
        #   Add-Computer -WorkgroupName VCL -Credential \$ps_credential 
-ErrorAction Stop
        #}
@@ -14172,7 +14349,7 @@ sub ad_search {
 $Host.UI.RawUI.BufferSize = New-Object Management.Automation.Host.Size(5000, 
500)
 
 $domain_dns_name = '[domain_dns_name]'
-$domain_username = '[domain_username]'
+$domain_username = '[domain_username]@[domain_dns_name]'
 $domain_password = '[domain_password]'
 $ldap_filter = '[ldap_filter]'
 $delete = '[delete]'
@@ -14189,14 +14366,12 @@ catch {
    else {
       $exception_message = $_.Exception.Message
    }
-   Write-Host "ERROR: failed to connect to $domain_dns_name domain, error: 
$exception_message"
+   Write-Host "ERROR: failed to connect to $domain_dns_name domain, username: 
$domain_username, password: $domain_password, error: $exception_message"
    exit
 }
 
-
 $searcher = New-Object 
System.DirectoryServices.DirectorySearcher($domain.GetDirectoryEntry())
 $searcher.filter = "$ldap_filter"
-
 try {
    $results = $searcher.FindAll()
    # Try to output the results to catch this exception:
@@ -14221,7 +14396,6 @@ Write-Host "delete true : $delete"
    }
 }
 
-
 ForEach($result in $results) {
    $entry = $result.GetDirectoryEntry();
    $dn = $entry.distinguishedName
@@ -14242,16 +14416,16 @@ ForEach($result in $results) {
 }
 EOF
        
-       $powershell_script_contents =~ s/\[domain_dns_name\]/$domain_dns_name/;
-       $powershell_script_contents =~ s/\[domain_username\]/$domain_username/;
-       $powershell_script_contents =~ s/\[domain_password\]/$domain_password/;
+       $powershell_script_contents =~ s/\[domain_dns_name\]/$domain_dns_name/g;
+       $powershell_script_contents =~ s/\[domain_username\]/$domain_username/g;
+       $powershell_script_contents =~ s/\[domain_password\]/$domain_password/g;
        $powershell_script_contents =~ s/\[ldap_filter\]/$ldap_filter/;
        
        if ($operation eq 'delete') {
-               $powershell_script_contents =~ s/\[delete\]/1/;
+               $powershell_script_contents =~ s/\[delete\]/1/g;
        }
        else {
-               $powershell_script_contents =~ s/\[delete\]/0/;
+               $powershell_script_contents =~ s/\[delete\]/0/g;
        }
        
        my ($exit_status, $output);


Reply via email to