sub run_sysprep()

in managementnode/lib/VCL/Module/OS/Windows/Version_6.pm [1642:1900]


sub run_sysprep {
	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;
	}

	my $computer_node_name = $self->data->get_computer_node_name();
	my $system32_path = $self->get_system32_path() || return;
	my $node_configuration_directory = $self->get_node_configuration_directory();
	
	my $time_zone_name = $self->get_time_zone_name();
	if (!$time_zone_name) {
		notify($ERRORS{'WARNING'}, 0, "time zone name could not be retrieved");
		return;
	}
	
	my $product_key = $self->get_kms_client_product_key();
	if (!$product_key) {
		notify($ERRORS{'WARNING'}, 0, "KMS client product key could not be retrieved");
		return;
	}
	
	# Set the processorArchitecture to either amd64 or x86 in the XML depending on whether or not the OS is 64-bit
	my $architecture = $self->is_64_bit() ? 'amd64' : 'x86';
	
	my $unattend_xml_file_path = "$system32_path/sysprep/Unattend.xml";
	
	my $unattend_xml_contents = <<EOF;
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
	<settings pass="generalize">
		<component name="Microsoft-Windows-PnpSysprep" processorArchitecture="$architecture" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
			<PersistAllDeviceInstalls>false</PersistAllDeviceInstalls>
			<DoNotCleanUpNonPresentDevices>false</DoNotCleanUpNonPresentDevices>
		</component>
		<component name="Microsoft-Windows-Security-SPP" processorArchitecture="$architecture" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
			<SkipRearm>1</SkipRearm>
		</component>
	</settings>
	<settings pass="specialize">
		<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="$architecture" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
			<Display>
				<ColorDepth>32</ColorDepth>
				<DPI>120</DPI>
				<HorizontalResolution>1024</HorizontalResolution>
				<VerticalResolution>768</VerticalResolution>
				<RefreshRate>72</RefreshRate>
			</Display>
			<ComputerName>*</ComputerName>
			<TimeZone>$time_zone_name</TimeZone>
			<ProductKey>$product_key</ProductKey>
		</component>
		<component name="Microsoft-Windows-Deployment" processorArchitecture="$architecture" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
			<RunSynchronous>
				<RunSynchronousCommand wcm:action="add">
					<Path>C:\\Cygwin\\home\\root\\VCL\\Scripts\\sysprep_cmdlines.cmd &gt; C:\\cygwin\\home\\root\\VCL\\Logs\\sysprep_cmdlines.log 2&gt;&amp;1</Path>
					<Order>1</Order>
				</RunSynchronousCommand>
			</RunSynchronous>
		</component>
		<component name="Microsoft-Windows-Security-SPP-UX" processorArchitecture="$architecture" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
			<SkipAutoActivation>true</SkipAutoActivation>
		</component>
		<component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="$architecture" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
			<Identification>
				<JoinWorkgroup>VCL</JoinWorkgroup>
			</Identification>
		</component>
	</settings>
	<settings pass="auditSystem">
		<component name="Microsoft-Windows-PnpCustomizationsNonWinPE" processorArchitecture="$architecture" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
			<DriverPaths>
				<PathAndCredentials wcm:action="add" wcm:keyValue="1">
					<Path>C:\\Cygwin\\home\\root\\VCL\\Drivers</Path>
				</PathAndCredentials>
			</DriverPaths>
		</component>
	</settings>
	<settings pass="oobeSystem">
		<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="$architecture" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
			<OOBE>
				<HideEULAPage>true</HideEULAPage>
				<NetworkLocation>Work</NetworkLocation>
				<ProtectYourPC>3</ProtectYourPC>
			</OOBE>
			<UserAccounts>
				<AdministratorPassword>
					<Value>$WINDOWS_ROOT_PASSWORD</Value>
					<PlainText>true</PlainText>
				</AdministratorPassword>
				<LocalAccounts>
					<LocalAccount wcm:action="add">
						<Password>
							<Value>$WINDOWS_ROOT_PASSWORD</Value>
							<PlainText>true</PlainText>
						</Password>
						<Group>Administrators</Group>
						<Name>root</Name>
						<DisplayName>root</DisplayName>
						<Description>VCL root account</Description>
					</LocalAccount>
				</LocalAccounts>
			</UserAccounts>
		</component>
		<component name="Microsoft-Windows-International-Core" processorArchitecture="$architecture" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
			<InputLocale>en-US</InputLocale>
			<SystemLocale>en-US</SystemLocale>
			<UILanguage>en-US</UILanguage>
			<UserLocale>en-US</UserLocale>
		</component>
	</settings>
</unattend>
EOF

	notify($ERRORS{'DEBUG'}, 0, "'$unattend_xml_file_path' contents:\n$unattend_xml_contents");
	if (!$self->create_text_file($unattend_xml_file_path, $unattend_xml_contents)) {
		return;
	}
	
	# Delete existing Panther directory, contains Sysprep log files
	$self->delete_file('C:/Windows/Panther');
	
	# Delete existing sysprep/Panther directory, contains Sysprep log files
	$self->delete_file("$system32_path/sysprep/Panther");
	
	# Delete existing setupapi files
	$self->delete_file('C:/Windows/inf/setupapi*');
	
	# Delete existing INFCACHE files
	$self->delete_file('C:/Windows/inf/INFCACHE*');
	
	# Delete existing INFCACHE files
	$self->delete_file('C:/Windows/inf/oem*.inf');
	
	# Delete existing Sysprep_succeeded.tag file
	$self->delete_file("$system32_path/sysprep/Sysprep*.tag");
	
	# Delete existing MSDTC.LOG file
	$self->delete_file("$system32_path/MsDtc/MSTTC.LOG");
	
	# Delete existing VCL log files
	$self->delete_file("C:/Cygwin/home/root/VCL/Logs/*");
	
	# Delete legacy Sysprep directory
	$self->delete_file("C:/Cygwin/home/root/VCL/Utilities/Sysprep");
	
	# Grant permissions to the SYSTEM user - this is needed or else Sysprep fails
	$self->execute("cmd.exe /c \"$system32_path/icacls.exe $node_configuration_directory /grant SYSTEM:(OI)(CI)(F) /C\"");
	
	# Uninstall and reinstall MsDTC
	my $msdtc_command = "$system32_path/msdtc.exe -uninstall ; $system32_path/msdtc.exe -install";
	my ($msdtc_status, $msdtc_output) = $self->execute($msdtc_command);
	if (defined($msdtc_status) && $msdtc_status == 0) {
		notify($ERRORS{'DEBUG'}, 0, "reinstalled MsDtc");
	}
	elsif (defined($msdtc_status)) {
		notify($ERRORS{'WARNING'}, 0, "failed to reinstall MsDtc, exit status: $msdtc_status, output:\n@{$msdtc_output}");
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to reinstall MsDtc");
	}
	
	# Get the node drivers directory and convert it to DOS format
	my $drivers_directory = "$node_configuration_directory/Drivers";
	$drivers_directory =~ s/\//\\\\/g;
	
	# Set the Installation Sources registry key
	# Must use reg_add because the type is REG_MULTI_SZ
	my $setup_key = 'HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup';
	if ($self->reg_add($setup_key, 'Installation Sources', 'REG_MULTI_SZ', $drivers_directory)) {
		notify($ERRORS{'DEBUG'}, 0, "added Installation Sources registry key");
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to add Installation Sources registry key");
	}
	
	# Set the DevicePath registry key
	# This is used to locate device drivers
	if (!$self->set_device_path_key()) {
		notify($ERRORS{'WARNING'}, 0, "failed to set the DevicePath registry key");
		return;
	}
	
	# Reset the Windows setup registry keys
	# If Sysprep fails it will set keys which make running Sysprep again impossible
	# These keys never get reset, Microsoft instructs you to reinstall the OS
	# Clearing out these keys before running Sysprep allows it to be run again
	# Also enable verbose Sysprep logging
	my $registry_string .= <<"EOF";
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup]
"LogLevel"=dword:0000FFFF

[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State]
"ImageState"="IMAGE_STATE_COMPLETE"

[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\Sysprep\\Generalize]
"{82468857-ad9b-1a37-533f-7db889fff253}"=-

[-HKEY_LOCAL_MACHINE\\SYSTEM\\Setup\\Status]
EOF

	# Import the string into the registry
	if ($self->import_registry_string($registry_string)) {
		notify($ERRORS{'OK'}, 0, "reset Windows setup state in the registry");
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to reset the Windows setup state in the registry");
		return 0;
	}
	
	# Kill the screen saver process, it occasionally prevents reboots and shutdowns from working
	$self->kill_process('logon.scr');
	
	# Run Sysprep.exe, use cygstart to lauch the .exe and return immediately
	my $sysprep_command = "/bin/cygstart.exe \$SYSTEMROOT/system32/cmd.exe /c \"";
	
	# Run Sysprep.exe
	$sysprep_command .= "$system32_path/sysprep/sysprep.exe /generalize /oobe /shutdown /quiet /unattend:\$SYSTEMROOT/System32/sysprep/Unattend.xml";
	
	$sysprep_command .= "\"";
	
	# Run Sysprep.exe, use cygstart to lauch the .exe and return immediately
	my ($sysprep_status, $sysprep_output) = $self->execute($sysprep_command);
	if (defined($sysprep_status) && $sysprep_status == 0) {
		notify($ERRORS{'OK'}, 0, "initiated Sysprep.exe, waiting for $computer_node_name to become unresponsive");
	}
	elsif (defined($sysprep_status)) {
		notify($ERRORS{'OK'}, 0, "failed to initiate Sysprep.exe, exit status: $sysprep_status, output:\n@{$sysprep_output}");
		return 0;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to initiate Sysprep.exe");
		return 0;
	}
	
	# Wait maximum of 30 minutes for the computer to become unresponsive
	if (!$self->wait_for_no_ping(1800)) {
		# Computer never stopped responding to ping
		notify($ERRORS{'WARNING'}, 0, "$computer_node_name never became unresponsive to ping");
		return;
	}
	
	# Wait maximum of 15 minutes for computer to power off
	my $power_off = $self->provisioner->wait_for_power_off(900);
	if (!defined($power_off)) {
		# wait_for_power_off result will be undefined if the provisioning module doesn't implement a power_status subroutine
		notify($ERRORS{'OK'}, 0, "unable to determine power status of $computer_node_name from provisioning module, sleeping 5 minutes to allow computer time to shutdown");
		sleep 300;
	}
	elsif (!$power_off) {
		notify($ERRORS{'WARNING'}, 0, "$computer_node_name never powered off");
		return;
	}
	
	return 1;
}