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);