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 device = self.image.g.part_to_dev(self.root)
144 self.last_part_num = self.image.g.part_list(device)[-1]['part_num']
145 self.last_drive = None
146 self.system_drive = None
148 for drive, part in self.image.g.inspect_get_drive_mappings(self.root):
149 if part == "%s%d" % (device, self.last_part_num):
150 self.last_drive = drive
151 if part == self.root:
152 self.system_drive = drive
154 assert self.system_drive
156 self.product_name = self.image.g.inspect_get_product_name(self.root)
157 self.syspreped = False
159 @sysprep('Disabling IPv6 privacy extensions')
160 def disable_ipv6_privacy_extensions(self):
161 """Disable IPv6 privacy extensions"""
163 self._guest_exec('netsh interface ipv6 set global '
164 'randomizeidentifiers=disabled store=persistent')
166 @sysprep('Disabling Teredo interface')
167 def disable_teredo(self):
168 """Disable Teredo interface"""
170 self._guest_exec('netsh interface teredo set state disabled')
172 @sysprep('Disabling ISATAP Adapters')
173 def disable_isatap(self):
174 """Disable ISATAP Adapters"""
176 self._guest_exec('netsh interface isa set state disabled')
178 @sysprep('Enabling ping responses')
179 def enable_pings(self):
180 """Enable ping responses"""
182 self._guest_exec('netsh firewall set icmpsetting 8')
184 @sysprep('Disabling hibernation support')
185 def disable_hibernation(self):
186 """Disable hibernation support and remove the hibernation file"""
188 self._guest_exec(r'powercfg.exe /hibernate off')
190 @sysprep('Setting the system clock to UTC')
192 """Set the hardware clock to UTC"""
194 path = r'HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
196 r'REG ADD %s /v RealTimeIsUniversal /t REG_DWORD /d 1 /f' % path)
198 @sysprep('Clearing the event logs')
199 def clear_logs(self):
200 """Clear all the event logs"""
203 r"cmd /q /c for /f %l in ('wevtutil el') do wevtutil cl %l")
205 @sysprep('Executing Sysprep on the image (may take more that 10 minutes)')
206 def microsoft_sysprep(self):
207 """Run the Microsoft System Preparation Tool. This will remove
208 system-specific data and will make the image ready to be deployed.
209 After this no other task may run.
212 self._guest_exec(r'C:\Windows\system32\sysprep\sysprep '
213 r'/quiet /generalize /oobe /shutdown')
214 self.syspreped = True
216 @sysprep('Converting the image into a KMS client', enabled=False)
217 def kms_client_setup(self):
218 """Install the appropriate KMS client setup key to the image to convert
219 it to a KMS client. Computers that are running volume licensing
220 editions of Windows 8, Windows Server 2012, Windows 7, Windows Server
221 2008 R2, Windows Vista, and Windows Server 2008 are by default KMS
222 clients with no additional configuration needed.
225 setup_key = KMS_CLIENT_SETUP_KEYS[self.product_name]
228 "Don't know the KMS client setup key for product: `%s'" %
233 r"cscript \Windows\system32\slmgr.vbs /ipk %s" % setup_key)
235 @sysprep('Shrinking the last filesystem')
237 """Shrink the last filesystem. Make sure the filesystem is defragged"""
239 # Query for the maximum number of reclaimable bytes
241 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
242 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
243 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
244 r'ECHO SHRINK QUERYMAX >> %SCRIPT% & ' +
245 r'ECHO EXIT >> %SCRIPT% & ' +
246 r'DISKPART /S %SCRIPT% & ' +
247 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
250 stdout, stderr, rc = self._guest_exec(cmd)
253 for line in stdout.splitlines():
254 # diskpart will return something like this:
256 # The maximum number of reclaimable bytes is: xxxx MB
258 if line.find('reclaimable') >= 0:
259 answer = line.split(':')[1].strip()
260 m = re.search('(\d+) MB', answer)
262 querymax = m.group(1)
265 "Unexpected output for `shrink querymax' command: %s" %
269 FatalError("Error in shrinking! "
270 "Couldn't find the max number of reclaimable bytes!")
272 querymax = int(querymax)
274 # Practically the smallest shrunken size generally is at around
275 # "used space" + (20-200 MB). Please also take into account that
276 # Windows might need about 50-100 MB free space left to boot safely.
277 # I'll give 100MB extra space just to be sure
281 self.out.warn("Not enough available space to shrink the image!")
284 self.out.output("\tReclaiming %dMB ..." % querymax)
287 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
288 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
289 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
290 'ECHO SHRINK DESIRED=%d >> %%SCRIPT%% & ' % querymax +
291 r'ECHO EXIT >> %SCRIPT% & ' +
292 r'DISKPART /S %SCRIPT% & ' +
293 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
296 stdout, stderr, rc = self._guest_exec(cmd, False)
299 FatalError("Shrinking failed. Please make sure the media is "
300 "defraged with a command like this: "
301 "`Defrag.exe /U /X /W'")
302 for line in stdout.splitlines():
303 if line.find('shrunk') >= 0:
304 self.out.output(line)
306 def do_sysprep(self):
307 """Prepare system for image creation."""
309 if getattr(self, 'syspreped', False):
310 raise FatalError("Image is already syspreped!")
312 txt = "System preparation parameter: `%s' is needed but missing!"
313 for name, param in self.needed_sysprep_params.items():
314 if name not in self.sysprep_params:
315 raise FatalError(txt % name)
317 self.mount(readonly=False)
319 disabled_uac = self._update_uac_remote_setting(1)
320 token = self._enable_os_monitor()
322 # disable the firewalls
323 firewall_states = self._update_firewalls(0, 0, 0)
325 # Delete the pagefile. It will be recreated when the system boots
326 systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
328 pagefile = "%s/pagefile.sys" % systemroot
329 self.image.g.rm_rf(self.image.g.case_sensitive_path(pagefile))
336 self.image.disable_guestfs()
341 self.out.output("Starting windows VM ...", False)
342 monitorfd, monitor = tempfile.mkstemp()
344 vm = _VM(self.image.device, monitor, self.sysprep_params)
345 self.out.success("started (console on vnc display: %d)." %
348 self.out.output("Waiting for OS to boot ...", False)
349 self._wait_vm_boot(vm, monitor, token)
350 self.out.success('done')
352 self.out.output("Checking connectivity to the VM ...", False)
353 self._check_connectivity()
354 self.out.success('done')
356 self.out.output("Disabling automatic logon ...", False)
357 self._disable_autologon()
358 self.out.success('done')
360 self.out.output('Preparing system for image creation:')
362 tasks = self.list_syspreps()
363 enabled = [task for task in tasks if task.enabled]
366 # Make sure shrink runs in the end, before ms sysprep
367 enabled = [task for task in enabled if
368 self.sysprep_info(task).name != 'shrink']
370 if len(enabled) != size:
371 enabled.append(self.shrink)
373 # Make sure the ms sysprep is the last task to run if it is enabled
374 enabled = [task for task in enabled if
375 self.sysprep_info(task).name != 'microsoft-sysprep']
377 ms_sysprep_enabled = False
378 if len(enabled) != size:
379 enabled.append(self.microsoft_sysprep)
380 ms_sysprep_enabled = True
385 self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False)
387 setattr(task.im_func, 'executed', True)
389 self.out.output("Sending shut down command ...", False)
390 if not ms_sysprep_enabled:
392 self.out.success("done")
394 self.out.output("Waiting for windows to shut down ...", False)
395 vm.wait(self.sysprep_params['shutdown_timeout'])
396 self.out.success("done")
398 if monitor is not None:
403 self.out.output("Destroying windows VM ...", False)
405 self.out.success("done")
407 self.image.enable_guestfs()
409 self.mount(readonly=False)
412 self._update_uac_remote_setting(0)
414 self._update_firewalls(*firewall_states)
419 """Shuts down the windows VM"""
420 self._guest_exec(r'shutdown /s /t 5')
422 def _wait_vm_boot(self, vm, fname, msg):
423 """Wait until a message appears on a file or the vm process dies"""
425 for _ in range(self.sysprep_params['boot_timeout']):
427 with open(fname) as f:
429 if line.startswith(msg):
432 raise FatalError("Windows VM died unexpectedly!")
434 raise FatalError("Windows VM booting timed out!")
436 def _disable_autologon(self):
437 """Disable automatic logon on the windows image"""
440 r'"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"'
442 self._guest_exec('REG DELETE %s /v DefaultUserName /f' % winlogon)
443 self._guest_exec('REG DELETE %s /v DefaultPassword /f' % winlogon)
444 self._guest_exec('REG DELETE %s /v AutoAdminLogon /f' % winlogon)
446 def _registry_file_path(self, regfile):
447 """Retrieves the case sensitive path to a registry file"""
449 systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
450 path = "%s/system32/config/%s" % (systemroot, regfile)
452 path = self.image.g.case_sensitive_path(path)
453 except RuntimeError as error:
454 raise FatalError("Unable to retrieve registry file: %s. Reason: %s"
455 % (regfile, str(error)))
458 def _enable_os_monitor(self):
459 """Add a script in the registry that will send a random string to the
460 first serial port when the windows image finishes booting.
463 token = "".join(random.choice(string.ascii_letters) for x in range(16))
465 path = self._registry_file_path('SOFTWARE')
466 softwarefd, software = tempfile.mkstemp()
469 self.image.g.download(path, software)
471 h = hivex.Hivex(software, write=True)
473 # Enable automatic logon.
474 # This is needed because we need to execute a script that we add in
475 # the RunOnce registry entry and those programs only get executed
476 # when a user logs on. There is a RunServicesOnce registry entry
477 # whose keys get executed in the background when the logon dialog
478 # box first appears, but they seem to only work with services and
479 # not arbitrary command line expressions :-(
481 # Instructions on how to turn on automatic logon in Windows can be
482 # found here: http://support.microsoft.com/kb/324737
484 # Warning: Registry change will not work if the “Logon Banner” is
485 # defined on the server either by a Group Policy object (GPO) or by
489 for child in ('Microsoft', 'Windows NT', 'CurrentVersion',
491 winlogon = h.node_get_child(winlogon, child)
495 {'key': 'DefaultUserName', 't': 1,
496 'value': "Administrator".encode('utf-16le')})
499 {'key': 'DefaultPassword', 't': 1,
500 'value': self.sysprep_params['password'].encode('utf-16le')})
503 {'key': 'AutoAdminLogon', 't': 1,
504 'value': "1".encode('utf-16le')})
507 for child in ('Microsoft', 'Windows', 'CurrentVersion'):
508 key = h.node_get_child(key, child)
510 runonce = h.node_get_child(key, "RunOnce")
512 runonce = h.node_add_child(key, "RunOnce")
515 r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe '
516 r'-ExecutionPolicy RemoteSigned '
517 r'"&{$port=new-Object System.IO.Ports.SerialPort COM1,9600,'
518 r'None,8,one;$port.open();$port.WriteLine(\"' + token + r'\");'
519 r'$port.Close()}"').encode('utf-16le')
521 h.node_set_value(runonce,
522 {'key': "BootMonitor", 't': 1, 'value': value})
525 r'REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion'
526 r'\policies\system /v LocalAccountTokenFilterPolicy'
527 r' /t REG_DWORD /d 1 /f').encode('utf-16le')
529 h.node_set_value(runonce,
530 {'key': "UpdateRegistry", 't': 1, 'value': value})
534 self.image.g.upload(software, path)
540 def _update_firewalls(self, domain, public, standard):
541 """Enables or disables the firewall for the Domain, the Public and the
542 Standard profile. Returns a triplete with the old values.
544 1 will enable a firewall and 0 will disable it
547 if domain not in (0, 1):
548 raise ValueError("Valid values for domain parameter are 0 and 1")
550 if public not in (0, 1):
551 raise ValueError("Valid values for public parameter are 0 and 1")
553 if standard not in (0, 1):
554 raise ValueError("Valid values for standard parameter are 0 and 1")
556 path = self._registry_file_path("SYSTEM")
557 systemfd, system = tempfile.mkstemp()
560 self.image.g.download(path, system)
562 h = hivex.Hivex(system, write=True)
564 select = h.node_get_child(h.root(), 'Select')
565 current_value = h.node_get_value(select, 'Current')
567 # expecting a little endian dword
568 assert h.value_type(current_value)[1] == 4
569 current = "%03d" % h.value_dword(current_value)
571 firewall_policy = h.root()
572 for child in ('ControlSet%s' % current, 'services', 'SharedAccess',
573 'Parameters', 'FirewallPolicy'):
574 firewall_policy = h.node_get_child(firewall_policy, child)
577 new_values = [domain, public, standard]
578 for profile in ('Domain', 'Public', 'Standard'):
579 node = h.node_get_child(firewall_policy, '%sProfile' % profile)
581 old_value = h.node_get_value(node, 'EnableFirewall')
583 # expecting a little endian dword
584 assert h.value_type(old_value)[1] == 4
585 old_values.append(h.value_dword(old_value))
588 node, {'key': 'EnableFirewall', 't': 4L,
589 'value': struct.pack("<I", new_values.pop(0))})
592 self.image.g.upload(system, path)
599 def _update_uac_remote_setting(self, value):
600 """Updates the registry key value:
601 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
602 \System]"LocalAccountTokenFilterPolicy"
604 value = 1 will disable the UAC remote restrictions
605 value = 0 will enable the UAC remote restrictions
607 For more info see here: http://support.microsoft.com/kb/951016
610 True if the key is changed
611 False if the key is unchanged
614 if value not in (0, 1):
615 raise ValueError("Valid values for value parameter are 0 and 1")
617 path = self._registry_file_path('SOFTWARE')
618 softwarefd, software = tempfile.mkstemp()
621 self.image.g.download(path, software)
623 h = hivex.Hivex(software, write=True)
626 for child in ('Microsoft', 'Windows', 'CurrentVersion', 'Policies',
628 key = h.node_get_child(key, child)
631 for val in h.node_values(key):
632 if h.value_key(val) == "LocalAccountTokenFilterPolicy":
635 if policy is not None:
636 dword = h.value_dword(policy)
642 new_value = {'key': "LocalAccountTokenFilterPolicy", 't': 4L,
643 'value': struct.pack("<I", value)}
645 h.node_set_value(key, new_value)
648 self.image.g.upload(software, path)
655 def _do_collect_metadata(self):
656 """Collect metadata about the OS"""
657 super(Windows, self)._do_collect_metadata()
658 self.meta["USERS"] = " ".join(self._get_users())
660 def _get_users(self):
661 """Returns a list of users found in the images"""
662 samfd, sam = tempfile.mkstemp()
665 self.image.g.download(self._registry_file_path('SAM'), sam)
669 # Navigate to /SAM/Domains/Account/Users
670 users_node = h.root()
671 for child in ('SAM', 'Domains', 'Account', 'Users'):
672 users_node = h.node_get_child(users_node, child)
674 # Navigate to /SAM/Domains/Account/Users/Names
675 names_node = h.node_get_child(users_node, 'Names')
677 # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\%RID%
678 # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\Names\%Username%
680 # The RID (relative identifier) of each user is stored as the type!
681 # (not the value) of the default key of the node under Names whose
682 # name is the user's username. Under the RID node, there in a F
683 # value that contains information about this user account.
685 # See sam.h of the chntpw project on how to translate the F value
686 # of an account in the registry. Bytes 56 & 57 are the account type
687 # and status flags. The first bit is the 'account disabled' bit
688 disabled = lambda f: int(f[56].encode('hex'), 16) & 0x01
691 for user_node in h.node_children(names_node):
692 username = h.node_name(user_node)
693 rid = h.value_type(h.node_get_value(user_node, ""))[0]
694 # if RID is 500 (=0x1f4), the corresponding node name under
695 # Users is '000001F4'
696 key = ("%8.x" % rid).replace(' ', '0').upper()
697 rid_node = h.node_get_child(users_node, key)
698 f_value = h.value_value(h.node_get_value(rid_node, 'F'))[1]
700 if disabled(f_value):
701 self.out.warn("Found disabled `%s' account!" % username)
704 users.append(username)
709 # Filter out the guest account
712 def _check_connectivity(self):
713 """Check if winexe works on the Windows VM"""
715 retries = self.sysprep_params['connection_retries']
716 # If the connection_retries parameter is set to 0 disable the
721 passwd = self.sysprep_params['password']
722 winexe = WinEXE('Administrator', passwd, 'localhost')
723 winexe.uninstall().debug(9)
725 for i in range(retries):
726 (stdout, stderr, rc) = winexe.run('cmd /C')
729 log = tempfile.NamedTemporaryFile(delete=False)
731 log.file.write(stdout)
734 self.out.output("failed! See: `%s' for the full output" % log.name)
736 self.out.output("retrying ...", False)
738 raise FatalError("Connection to the Windows VM failed after %d retries"
741 def _guest_exec(self, command, fatal=True):
742 """Execute a command on a windows VM"""
744 passwd = self.sysprep_params['password']
746 winexe = WinEXE('Administrator', passwd, 'localhost')
747 winexe.runas('Administrator', passwd).uninstall()
750 (stdout, stderr, rc) = winexe.run(command)
751 except WinexeTimeout:
752 FatalError("Command: `%s' timeout out." % command)
754 if rc != 0 and fatal:
755 reason = stderr if len(stderr) else stdout
756 self.out.output("Command: `%s' failed (rc=%d). Reason: %s" %
757 (command, rc, reason))
758 raise FatalError("Command: `%s' failed (rc=%d). Reason: %s" %
759 (command, rc, reason))
761 return (stdout, stderr, rc)
765 """Windows Virtual Machine"""
766 def __init__(self, disk, serial, params):
767 """Create _VM instance
770 serial: File to save the output of the serial port
778 """creates a random mac address"""
779 mac = [0x00, 0x16, 0x3e,
780 random.randint(0x00, 0x7f),
781 random.randint(0x00, 0xff),
782 random.randint(0x00, 0xff)]
784 return ':'.join(['%02x' % x for x in mac])
786 # Use ganeti's VNC port range for a random vnc port
787 self.display = random.randint(11000, 14999) - 5900
789 kvm, needed_args = get_kvm_binary()
792 FatalError("Can't find the kvm binary")
795 args.extend(needed_args)
798 '-smp', '1', '-m', '1024', '-drive',
799 'file=%s,format=raw,cache=unsafe,if=virtio' % self.disk,
800 '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
801 '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(),
802 '-vnc', ':%d' % self.display, '-serial', 'file:%s' % self.serial,
803 '-monitor', 'stdio'])
805 self.process = subprocess.Popen(args, stdin=subprocess.PIPE,
806 stdout=subprocess.PIPE)
809 """Check if the VM is still alive"""
810 return self.process.poll() is None
815 if not self.isalive():
818 def handler(signum, frame):
819 self.process.terminate()
824 raise FatalError("VM destroy timed-out")
826 signal.signal(signal.SIGALRM, handler)
828 signal.alarm(self.params['shutdown_timeout'])
829 self.process.communicate(input="system_powerdown\n")
832 def wait(self, timeout=0):
833 """Wait for the VM to terminate"""
835 def handler(signum, frame):
837 raise FatalError("VM wait timed-out.")
839 signal.signal(signal.SIGALRM, handler)
841 signal.alarm(timeout)
842 stdout, stderr = self.process.communicate()
845 return (stdout, stderr, self.process.poll())
847 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :