1 # -*- coding: utf-8 -*-
3 # Copyright 2012 GRNET S.A. All rights reserved.
5 # Redistribution and use in source and binary forms, with or
6 # without modification, are permitted provided that the following
9 # 1. Redistributions of source code must retain the above
10 # copyright notice, this list of conditions and the following
13 # 2. Redistributions in binary form must reproduce the above
14 # copyright notice, this list of conditions and the following
15 # disclaimer in the documentation and/or other materials
16 # provided with the distribution.
18 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 # POSSIBILITY OF SUCH DAMAGE.
31 # The views and conclusions contained in the software and
32 # documentation are those of the authors and should not be
33 # interpreted as representing official policies, either expressed
34 # or implied, of GRNET S.A.
36 """This module hosts OS-specific code common for the various Microsoft
39 from image_creator.os_type import OSBase, sysprep, add_sysprep_param
40 from image_creator.util import FatalError, get_kvm_binary
41 from image_creator.winexe import WinEXE, WinexeTimeout
54 # For more info see: http://technet.microsoft.com/en-us/library/jj612867.aspx
55 KMS_CLIENT_SETUP_KEYS = {
56 "Windows 8.1 Professional": "GCRJD-8NW9H-F2CDX-CCM8D-9D6T9",
57 "Windows 8.1 Professional N": "HMCNV-VVBFX-7HMBH-CTY9B-B4FXY",
58 "Windows 8.1 Enterprise": "MHF9N-XY6XB-WVXMC-BTDCT-MKKG7",
59 "Windows 8.1 Enterprise N": "TT4HM-HN7YT-62K67-RGRQJ-JFFXW",
60 "Windows Server 2012 R2 Server Standard": "D2N9P-3P6X9-2R39C-7RTCD-MDVJX",
61 "Windows Server 2012 R2 Datacenter": "W3GGN-FT8W3-Y4M27-J84CP-Q3VJ9",
62 "Windows Server 2012 R2 Essentials": "KNC87-3J2TX-XB4WP-VCPJV-M4FWM",
63 "Windows 8 Professional": "NG4HW-VH26C-733KW-K6F98-J8CK4",
64 "Windows 8 Professional N": "XCVCF-2NXM9-723PB-MHCB7-2RYQQ",
65 "Windows 8 Enterprise": "32JNW-9KQ84-P47T8-D8GGY-CWCK7",
66 "Windows 8 Enterprise N": "JMNMF-RHW7P-DMY6X-RF3DR-X2BQT",
67 "Windows Server 2012 Core": "BN3D2-R7TKB-3YPBD-8DRP2-27GG4",
68 "Windows Server 2012 Core N": "8N2M2-HWPGY-7PGT9-HGDD8-GVGGY",
69 "Windows Server 2012 Core Single Language":
70 "2WN2H-YGCQR-KFX6K-CD6TF-84YXQ",
71 "Windows Server 2012 Core Country Specific":
72 "4K36P-JN4VD-GDC6V-KDT89-DYFKP",
73 "Windows Server 2012 Server Standard": "XC9B7-NBPP2-83J2H-RHMBY-92BT4",
74 "Windows Server 2012 Standard Core": "XC9B7-NBPP2-83J2H-RHMBY-92BT4",
75 "Windows Server 2012 MultiPoint Standard": "HM7DN-YVMH3-46JC3-XYTG7-CYQJJ",
76 "Windows Server 2012 MultiPoint Premium": "XNH6W-2V9GX-RGJ4K-Y8X6F-QGJ2G",
77 "Windows Server 2012 Datacenter": "48HP8-DN98B-MYWDG-T2DCC-8W83P",
78 "Windows Server 2012 Datacenter Core": "48HP8-DN98B-MYWDG-T2DCC-8W83P",
79 "Windows 7 Professional": "FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4",
80 "Windows 7 Professional N": "MRPKT-YTG23-K7D7T-X2JMM-QY7MG",
81 "Windows 7 Professional E": "W82YF-2Q76Y-63HXB-FGJG9-GF7QX",
82 "Windows 7 Enterprise": "33PXH-7Y6KF-2VJC9-XBBR8-HVTHH",
83 "Windows 7 Enterprise N": "YDRBP-3D83W-TY26F-D46B2-XCKRJ",
84 "Windows 7 Enterprise E": "C29WB-22CC8-VJ326-GHFJW-H9DH4",
85 "Windows Server 2008 R2 Web": "6TPJF-RBVHG-WBW2R-86QPH-6RTM4",
86 "Windows Server 2008 R2 HPC edition": "TT8MH-CG224-D3D7Q-498W2-9QCTX",
87 "Windows Server 2008 R2 Standard": "YC6KT-GKW9T-YTKYR-T4X34-R7VHC",
88 "Windows Server 2008 R2 Enterprise": "489J6-VHDMP-X63PK-3K798-CPX3Y",
89 "Windows Server 2008 R2 Datacenter": "74YFP-3QFB3-KQT8W-PMXWJ-7M648",
90 "Windows Server 2008 R2 for Itanium-based Systems":
91 "GT63C-RJFQ3-4GMB6-BRFB9-CB83V",
92 "Windows Vista Business": "YFKBB-PQJJV-G996G-VWGXY-2V3X8",
93 "Windows Vista Business N": "HMBQG-8H2RH-C77VX-27R82-VMQBT",
94 "Windows Vista Enterprise": "VKK3X-68KWM-X2YGT-QR4M6-4BWMV",
95 "Windows Vista Enterprise N": "VTC42-BM838-43QHV-84HX6-XJXKV",
96 "Windows Web Server 2008": "WYR28-R7TFJ-3X2YQ-YCY4H-M249D",
97 "Windows Server 2008 Standard": "TM24T-X9RMF-VWXK6-X8JC9-BFGM2",
98 "Windows Server 2008 Standard without Hyper-V":
99 "W7VD6-7JFBR-RX26B-YKQ3Y-6FFFJ",
100 "Windows Server 2008 Enterprise":
101 "YQGMW-MPWTJ-34KDK-48M3W-X4Q6V",
102 "Windows Server 2008 Enterprise without Hyper-V":
103 "39BXF-X8Q23-P2WWT-38T2F-G3FPG",
104 "Windows Server 2008 HPC": "RCTX3-KWVHP-BR6TB-RB6DM-6X7HP",
105 "Windows Server 2008 Datacenter": "7M67G-PC374-GR742-YH8V4-TCBY3",
106 "Windows Server 2008 Datacenter without Hyper-V":
107 "22XQ2-VRXRG-P8D42-K34TD-G3QQC",
108 "Windows Server 2008 for Itanium-Based Systems":
109 "4DWFP-JF3DJ-B7DTH-78FJB-PDRHK"}
111 _POSINT = lambda x: type(x) == int and x >= 0
114 class Windows(OSBase):
115 """OS class for Windows"""
117 'shutdown_timeout', int, 120, "Shutdown Timeout (seconds)", _POSINT)
119 'boot_timeout', int, 300, "Boot Timeout (seconds)", _POSINT)
121 'connection_retries', int, 5, "Connection Retries", _POSINT)
122 @add_sysprep_param('password', str, None, 'Image Administrator Password')
123 def __init__(self, image, **kargs):
124 super(Windows, self).__init__(image, **kargs)
126 # The commit with the following message was added in
127 # libguestfs 1.17.18 and was backported in version 1.16.11:
129 # When a Windows guest doesn't have a HKLM\SYSTEM\MountedDevices node,
130 # inspection fails. However inspection should not completely fail just
131 # because we cannot get the drive letter mapping from a guest.
133 # Since Microsoft Sysprep removes the aforementioned key, image
134 # creation for windows can only be supported if the installed guestfs
135 # version is 1.17.18 or higher
136 if self.image.check_guestfs_version(1, 17, 18) < 0 and \
137 (self.image.check_guestfs_version(1, 17, 0) >= 0 or
138 self.image.check_guestfs_version(1, 16, 11) < 0):
140 'For windows support libguestfs 1.16.11 or above is required')
142 # Check if winexe is installed
143 if not WinEXE.is_installed():
145 "For windows support `Winexe' needs to be installed")
147 device = self.image.g.part_to_dev(self.root)
149 self.last_part_num = self.image.g.part_list(device)[-1]['part_num']
150 self.last_drive = None
151 self.system_drive = None
153 for drive, part in self.image.g.inspect_get_drive_mappings(self.root):
154 if part == "%s%d" % (device, self.last_part_num):
155 self.last_drive = drive
156 if part == self.root:
157 self.system_drive = drive
159 assert self.system_drive
161 self.product_name = self.image.g.inspect_get_product_name(self.root)
162 self.syspreped = False
164 @sysprep('Disabling IPv6 privacy extensions')
165 def disable_ipv6_privacy_extensions(self):
166 """Disable IPv6 privacy extensions"""
168 self._guest_exec('netsh interface ipv6 set global '
169 'randomizeidentifiers=disabled store=persistent')
171 @sysprep('Disabling Teredo interface')
172 def disable_teredo(self):
173 """Disable Teredo interface"""
175 self._guest_exec('netsh interface teredo set state disabled')
177 @sysprep('Disabling ISATAP Adapters')
178 def disable_isatap(self):
179 """Disable ISATAP Adapters"""
181 self._guest_exec('netsh interface isa set state disabled')
183 @sysprep('Enabling ping responses')
184 def enable_pings(self):
185 """Enable ping responses"""
187 self._guest_exec('netsh firewall set icmpsetting 8')
189 @sysprep('Disabling hibernation support')
190 def disable_hibernation(self):
191 """Disable hibernation support and remove the hibernation file"""
193 self._guest_exec(r'powercfg.exe /hibernate off')
195 @sysprep('Setting the system clock to UTC')
197 """Set the hardware clock to UTC"""
199 path = r'HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
201 r'REG ADD %s /v RealTimeIsUniversal /t REG_DWORD /d 1 /f' % path)
203 @sysprep('Clearing the event logs')
204 def clear_logs(self):
205 """Clear all the event logs"""
208 r"cmd /q /c for /f %l in ('wevtutil el') do wevtutil cl %l")
210 @sysprep('Executing Sysprep on the image (may take more that 10 minutes)')
211 def microsoft_sysprep(self):
212 """Run the Microsoft System Preparation Tool. This will remove
213 system-specific data and will make the image ready to be deployed.
214 After this no other task may run.
217 self._guest_exec(r'C:\Windows\system32\sysprep\sysprep '
218 r'/quiet /generalize /oobe /shutdown')
219 self.syspreped = True
221 @sysprep('Converting the image into a KMS client', enabled=False)
222 def kms_client_setup(self):
223 """Install the appropriate KMS client setup key to the image to convert
224 it to a KMS client. Computers that are running volume licensing
225 editions of Windows 8, Windows Server 2012, Windows 7, Windows Server
226 2008 R2, Windows Vista, and Windows Server 2008 are by default KMS
227 clients with no additional configuration needed.
230 setup_key = KMS_CLIENT_SETUP_KEYS[self.product_name]
233 "Don't know the KMS client setup key for product: `%s'" %
238 r"cscript \Windows\system32\slmgr.vbs /ipk %s" % setup_key)
240 @sysprep('Shrinking the last filesystem')
242 """Shrink the last filesystem. Make sure the filesystem is defragged"""
244 # Query for the maximum number of reclaimable bytes
246 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
247 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
248 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
249 r'ECHO SHRINK QUERYMAX >> %SCRIPT% & ' +
250 r'ECHO EXIT >> %SCRIPT% & ' +
251 r'DISKPART /S %SCRIPT% & ' +
252 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
255 stdout, stderr, rc = self._guest_exec(cmd)
258 for line in stdout.splitlines():
259 # diskpart will return something like this:
261 # The maximum number of reclaimable bytes is: xxxx MB
263 if line.find('reclaimable') >= 0:
264 answer = line.split(':')[1].strip()
265 m = re.search('(\d+) MB', answer)
267 querymax = m.group(1)
270 "Unexpected output for `shrink querymax' command: %s" %
274 FatalError("Error in shrinking! "
275 "Couldn't find the max number of reclaimable bytes!")
277 querymax = int(querymax)
279 # Practically the smallest shrunken size generally is at around
280 # "used space" + (20-200 MB). Please also take into account that
281 # Windows might need about 50-100 MB free space left to boot safely.
282 # I'll give 100MB extra space just to be sure
286 self.out.warn("Not enough available space to shrink the image!")
289 self.out.output("\tReclaiming %dMB ..." % querymax)
292 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
293 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
294 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
295 'ECHO SHRINK DESIRED=%d >> %%SCRIPT%% & ' % querymax +
296 r'ECHO EXIT >> %SCRIPT% & ' +
297 r'DISKPART /S %SCRIPT% & ' +
298 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
301 stdout, stderr, rc = self._guest_exec(cmd, False)
304 FatalError("Shrinking failed. Please make sure the media is "
305 "defraged with a command like this: "
306 "`Defrag.exe /U /X /W'")
307 for line in stdout.splitlines():
308 if line.find('shrunk') >= 0:
309 self.out.output(line)
311 def do_sysprep(self):
312 """Prepare system for image creation."""
314 if getattr(self, 'syspreped', False):
315 raise FatalError("Image is already syspreped!")
317 txt = "System preparation parameter: `%s' is needed but missing!"
318 for name, param in self.needed_sysprep_params.items():
319 if name not in self.sysprep_params:
320 raise FatalError(txt % name)
322 self.mount(readonly=False)
324 disabled_uac = self._update_uac_remote_setting(1)
325 token = self._enable_os_monitor()
327 # disable the firewalls
328 firewall_states = self._update_firewalls(0, 0, 0)
330 # Delete the pagefile. It will be recreated when the system boots
331 systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
333 pagefile = "%s/pagefile.sys" % systemroot
334 self.image.g.rm_rf(self.image.g.case_sensitive_path(pagefile))
341 self.image.disable_guestfs()
346 self.out.output("Starting windows VM ...", False)
347 monitorfd, monitor = tempfile.mkstemp()
349 vm = _VM(self.image.device, monitor, self.sysprep_params)
350 self.out.success("started (console on vnc display: %d)." %
353 self.out.output("Waiting for OS to boot ...", False)
354 self._wait_vm_boot(vm, monitor, token)
355 self.out.success('done')
357 self.out.output("Checking connectivity to the VM ...", False)
358 self._check_connectivity()
359 self.out.success('done')
361 self.out.output("Disabling automatic logon ...", False)
362 self._disable_autologon()
363 self.out.success('done')
365 self.out.output('Preparing system for image creation:')
367 tasks = self.list_syspreps()
368 enabled = [task for task in tasks if task.enabled]
371 # Make sure shrink runs in the end, before ms sysprep
372 enabled = [task for task in enabled if
373 self.sysprep_info(task).name != 'shrink']
375 if len(enabled) != size:
376 enabled.append(self.shrink)
378 # Make sure the ms sysprep is the last task to run if it is enabled
379 enabled = [task for task in enabled if
380 self.sysprep_info(task).name != 'microsoft-sysprep']
382 ms_sysprep_enabled = False
383 if len(enabled) != size:
384 enabled.append(self.microsoft_sysprep)
385 ms_sysprep_enabled = True
390 self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False)
392 setattr(task.im_func, 'executed', True)
394 self.out.output("Sending shut down command ...", False)
395 if not ms_sysprep_enabled:
397 self.out.success("done")
399 self.out.output("Waiting for windows to shut down ...", False)
400 vm.wait(self.sysprep_params['shutdown_timeout'])
401 self.out.success("done")
403 if monitor is not None:
408 self.out.output("Destroying windows VM ...", False)
410 self.out.success("done")
412 self.image.enable_guestfs()
414 self.mount(readonly=False)
417 self._update_uac_remote_setting(0)
419 self._update_firewalls(*firewall_states)
424 """Shuts down the windows VM"""
425 self._guest_exec(r'shutdown /s /t 5')
427 def _wait_vm_boot(self, vm, fname, msg):
428 """Wait until a message appears on a file or the vm process dies"""
430 for _ in range(self.sysprep_params['boot_timeout']):
432 with open(fname) as f:
434 if line.startswith(msg):
437 raise FatalError("Windows VM died unexpectedly!")
439 raise FatalError("Windows VM booting timed out!")
441 def _disable_autologon(self):
442 """Disable automatic logon on the windows image"""
445 r'"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"'
447 self._guest_exec('REG DELETE %s /v DefaultUserName /f' % winlogon)
448 self._guest_exec('REG DELETE %s /v DefaultPassword /f' % winlogon)
449 self._guest_exec('REG DELETE %s /v AutoAdminLogon /f' % winlogon)
451 def _registry_file_path(self, regfile):
452 """Retrieves the case sensitive path to a registry file"""
454 systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
455 path = "%s/system32/config/%s" % (systemroot, regfile)
457 path = self.image.g.case_sensitive_path(path)
458 except RuntimeError as error:
459 raise FatalError("Unable to retrieve registry file: %s. Reason: %s"
460 % (regfile, str(error)))
463 def _enable_os_monitor(self):
464 """Add a script in the registry that will send a random string to the
465 first serial port when the windows image finishes booting.
468 token = "".join(random.choice(string.ascii_letters) for x in range(16))
470 path = self._registry_file_path('SOFTWARE')
471 softwarefd, software = tempfile.mkstemp()
474 self.image.g.download(path, software)
476 h = hivex.Hivex(software, write=True)
478 # Enable automatic logon.
479 # This is needed because we need to execute a script that we add in
480 # the RunOnce registry entry and those programs only get executed
481 # when a user logs on. There is a RunServicesOnce registry entry
482 # whose keys get executed in the background when the logon dialog
483 # box first appears, but they seem to only work with services and
484 # not arbitrary command line expressions :-(
486 # Instructions on how to turn on automatic logon in Windows can be
487 # found here: http://support.microsoft.com/kb/324737
489 # Warning: Registry change will not work if the “Logon Banner” is
490 # defined on the server either by a Group Policy object (GPO) or by
494 for child in ('Microsoft', 'Windows NT', 'CurrentVersion',
496 winlogon = h.node_get_child(winlogon, child)
500 {'key': 'DefaultUserName', 't': 1,
501 'value': "Administrator".encode('utf-16le')})
504 {'key': 'DefaultPassword', 't': 1,
505 'value': self.sysprep_params['password'].encode('utf-16le')})
508 {'key': 'AutoAdminLogon', 't': 1,
509 'value': "1".encode('utf-16le')})
512 for child in ('Microsoft', 'Windows', 'CurrentVersion'):
513 key = h.node_get_child(key, child)
515 runonce = h.node_get_child(key, "RunOnce")
517 runonce = h.node_add_child(key, "RunOnce")
520 r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe '
521 r'-ExecutionPolicy RemoteSigned '
522 r'"&{$port=new-Object System.IO.Ports.SerialPort COM1,9600,'
523 r'None,8,one;$port.open();$port.WriteLine(\"' + token + r'\");'
524 r'$port.Close()}"').encode('utf-16le')
526 h.node_set_value(runonce,
527 {'key': "BootMonitor", 't': 1, 'value': value})
530 r'REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion'
531 r'\policies\system /v LocalAccountTokenFilterPolicy'
532 r' /t REG_DWORD /d 1 /f').encode('utf-16le')
534 h.node_set_value(runonce,
535 {'key': "UpdateRegistry", 't': 1, 'value': value})
539 self.image.g.upload(software, path)
545 def _update_firewalls(self, domain, public, standard):
546 """Enables or disables the firewall for the Domain, the Public and the
547 Standard profile. Returns a triplete with the old values.
549 1 will enable a firewall and 0 will disable it
552 if domain not in (0, 1):
553 raise ValueError("Valid values for domain parameter are 0 and 1")
555 if public not in (0, 1):
556 raise ValueError("Valid values for public parameter are 0 and 1")
558 if standard not in (0, 1):
559 raise ValueError("Valid values for standard parameter are 0 and 1")
561 path = self._registry_file_path("SYSTEM")
562 systemfd, system = tempfile.mkstemp()
565 self.image.g.download(path, system)
567 h = hivex.Hivex(system, write=True)
569 select = h.node_get_child(h.root(), 'Select')
570 current_value = h.node_get_value(select, 'Current')
572 # expecting a little endian dword
573 assert h.value_type(current_value)[1] == 4
574 current = "%03d" % h.value_dword(current_value)
576 firewall_policy = h.root()
577 for child in ('ControlSet%s' % current, 'services', 'SharedAccess',
578 'Parameters', 'FirewallPolicy'):
579 firewall_policy = h.node_get_child(firewall_policy, child)
582 new_values = [domain, public, standard]
583 for profile in ('Domain', 'Public', 'Standard'):
584 node = h.node_get_child(firewall_policy, '%sProfile' % profile)
586 old_value = h.node_get_value(node, 'EnableFirewall')
588 # expecting a little endian dword
589 assert h.value_type(old_value)[1] == 4
590 old_values.append(h.value_dword(old_value))
593 node, {'key': 'EnableFirewall', 't': 4L,
594 'value': struct.pack("<I", new_values.pop(0))})
597 self.image.g.upload(system, path)
604 def _update_uac_remote_setting(self, value):
605 """Updates the registry key value:
606 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
607 \System]"LocalAccountTokenFilterPolicy"
609 value = 1 will disable the UAC remote restrictions
610 value = 0 will enable the UAC remote restrictions
612 For more info see here: http://support.microsoft.com/kb/951016
615 True if the key is changed
616 False if the key is unchanged
619 if value not in (0, 1):
620 raise ValueError("Valid values for value parameter are 0 and 1")
622 path = self._registry_file_path('SOFTWARE')
623 softwarefd, software = tempfile.mkstemp()
626 self.image.g.download(path, software)
628 h = hivex.Hivex(software, write=True)
631 for child in ('Microsoft', 'Windows', 'CurrentVersion', 'Policies',
633 key = h.node_get_child(key, child)
636 for val in h.node_values(key):
637 if h.value_key(val) == "LocalAccountTokenFilterPolicy":
640 if policy is not None:
641 dword = h.value_dword(policy)
647 new_value = {'key': "LocalAccountTokenFilterPolicy", 't': 4L,
648 'value': struct.pack("<I", value)}
650 h.node_set_value(key, new_value)
653 self.image.g.upload(software, path)
660 def _do_collect_metadata(self):
661 """Collect metadata about the OS"""
662 super(Windows, self)._do_collect_metadata()
663 self.meta["USERS"] = " ".join(self._get_users())
665 def _get_users(self):
666 """Returns a list of users found in the images"""
667 samfd, sam = tempfile.mkstemp()
670 self.image.g.download(self._registry_file_path('SAM'), sam)
674 # Navigate to /SAM/Domains/Account/Users
675 users_node = h.root()
676 for child in ('SAM', 'Domains', 'Account', 'Users'):
677 users_node = h.node_get_child(users_node, child)
679 # Navigate to /SAM/Domains/Account/Users/Names
680 names_node = h.node_get_child(users_node, 'Names')
682 # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\%RID%
683 # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\Names\%Username%
685 # The RID (relative identifier) of each user is stored as the type!
686 # (not the value) of the default key of the node under Names whose
687 # name is the user's username. Under the RID node, there in a F
688 # value that contains information about this user account.
690 # See sam.h of the chntpw project on how to translate the F value
691 # of an account in the registry. Bytes 56 & 57 are the account type
692 # and status flags. The first bit is the 'account disabled' bit
693 disabled = lambda f: int(f[56].encode('hex'), 16) & 0x01
696 for user_node in h.node_children(names_node):
697 username = h.node_name(user_node)
698 rid = h.value_type(h.node_get_value(user_node, ""))[0]
699 # if RID is 500 (=0x1f4), the corresponding node name under
700 # Users is '000001F4'
701 key = ("%8.x" % rid).replace(' ', '0').upper()
702 rid_node = h.node_get_child(users_node, key)
703 f_value = h.value_value(h.node_get_value(rid_node, 'F'))[1]
705 if disabled(f_value):
706 self.out.warn("Found disabled `%s' account!" % username)
709 users.append(username)
714 # Filter out the guest account
717 def _check_connectivity(self):
718 """Check if winexe works on the Windows VM"""
720 retries = self.sysprep_params['connection_retries']
721 # If the connection_retries parameter is set to 0 disable the
726 passwd = self.sysprep_params['password']
727 winexe = WinEXE('Administrator', passwd, 'localhost')
728 winexe.uninstall().debug(9)
730 for i in range(retries):
731 (stdout, stderr, rc) = winexe.run('cmd /C')
734 log = tempfile.NamedTemporaryFile(delete=False)
736 log.file.write(stdout)
739 self.out.output("failed! See: `%s' for the full output" % log.name)
741 self.out.output("retrying ...", False)
743 raise FatalError("Connection to the Windows VM failed after %d retries"
746 def _guest_exec(self, command, fatal=True):
747 """Execute a command on a windows VM"""
749 passwd = self.sysprep_params['password']
751 winexe = WinEXE('Administrator', passwd, 'localhost')
752 winexe.runas('Administrator', passwd).uninstall()
755 (stdout, stderr, rc) = winexe.run(command)
756 except WinexeTimeout:
757 FatalError("Command: `%s' timeout out." % command)
759 if rc != 0 and fatal:
760 reason = stderr if len(stderr) else stdout
761 self.out.output("Command: `%s' failed (rc=%d). Reason: %s" %
762 (command, rc, reason))
763 raise FatalError("Command: `%s' failed (rc=%d). Reason: %s" %
764 (command, rc, reason))
766 return (stdout, stderr, rc)
770 """Windows Virtual Machine"""
771 def __init__(self, disk, serial, params):
772 """Create _VM instance
775 serial: File to save the output of the serial port
783 """creates a random mac address"""
784 mac = [0x00, 0x16, 0x3e,
785 random.randint(0x00, 0x7f),
786 random.randint(0x00, 0xff),
787 random.randint(0x00, 0xff)]
789 return ':'.join(['%02x' % x for x in mac])
791 # Use ganeti's VNC port range for a random vnc port
792 self.display = random.randint(11000, 14999) - 5900
794 kvm, needed_args = get_kvm_binary()
797 FatalError("Can't find the kvm binary")
800 args.extend(needed_args)
803 '-smp', '1', '-m', '1024', '-drive',
804 'file=%s,format=raw,cache=unsafe,if=virtio' % self.disk,
805 '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
806 '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(),
807 '-vnc', ':%d' % self.display, '-serial', 'file:%s' % self.serial,
808 '-monitor', 'stdio'])
810 self.process = subprocess.Popen(args, stdin=subprocess.PIPE,
811 stdout=subprocess.PIPE)
814 """Check if the VM is still alive"""
815 return self.process.poll() is None
820 if not self.isalive():
823 def handler(signum, frame):
824 self.process.terminate()
829 raise FatalError("VM destroy timed-out")
831 signal.signal(signal.SIGALRM, handler)
833 signal.alarm(self.params['shutdown_timeout'])
834 self.process.communicate(input="system_powerdown\n")
837 def wait(self, timeout=0):
838 """Wait for the VM to terminate"""
840 def handler(signum, frame):
842 raise FatalError("VM wait timed-out.")
844 signal.signal(signal.SIGALRM, handler)
846 signal.alarm(timeout)
847 stdout, stderr = self.process.communicate()
850 return (stdout, stderr, self.process.poll())
852 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :