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
53 # For more info see: http://technet.microsoft.com/en-us/library/jj612867.aspx
54 KMS_CLIENT_SETUP_KEYS = {
55 "Windows 8.1 Professional": "GCRJD-8NW9H-F2CDX-CCM8D-9D6T9",
56 "Windows 8.1 Professional N": "HMCNV-VVBFX-7HMBH-CTY9B-B4FXY",
57 "Windows 8.1 Enterprise": "MHF9N-XY6XB-WVXMC-BTDCT-MKKG7",
58 "Windows 8.1 Enterprise N": "TT4HM-HN7YT-62K67-RGRQJ-JFFXW",
59 "Windows Server 2012 R2 Server Standard": "D2N9P-3P6X9-2R39C-7RTCD-MDVJX",
60 "Windows Server 2012 R2 Datacenter": "W3GGN-FT8W3-Y4M27-J84CP-Q3VJ9",
61 "Windows Server 2012 R2 Essentials": "KNC87-3J2TX-XB4WP-VCPJV-M4FWM",
62 "Windows 8 Professional": "NG4HW-VH26C-733KW-K6F98-J8CK4",
63 "Windows 8 Professional N": "XCVCF-2NXM9-723PB-MHCB7-2RYQQ",
64 "Windows 8 Enterprise": "32JNW-9KQ84-P47T8-D8GGY-CWCK7",
65 "Windows 8 Enterprise N": "JMNMF-RHW7P-DMY6X-RF3DR-X2BQT",
66 "Windows Server 2012 Core": "BN3D2-R7TKB-3YPBD-8DRP2-27GG4",
67 "Windows Server 2012 Core N": "8N2M2-HWPGY-7PGT9-HGDD8-GVGGY",
68 "Windows Server 2012 Core Single Language":
69 "2WN2H-YGCQR-KFX6K-CD6TF-84YXQ",
70 "Windows Server 2012 Core Country Specific":
71 "4K36P-JN4VD-GDC6V-KDT89-DYFKP",
72 "Windows Server 2012 Server Standard": "XC9B7-NBPP2-83J2H-RHMBY-92BT4",
73 "Windows Server 2012 Standard Core": "XC9B7-NBPP2-83J2H-RHMBY-92BT4",
74 "Windows Server 2012 MultiPoint Standard": "HM7DN-YVMH3-46JC3-XYTG7-CYQJJ",
75 "Windows Server 2012 MultiPoint Premium": "XNH6W-2V9GX-RGJ4K-Y8X6F-QGJ2G",
76 "Windows Server 2012 Datacenter": "48HP8-DN98B-MYWDG-T2DCC-8W83P",
77 "Windows Server 2012 Datacenter Core": "48HP8-DN98B-MYWDG-T2DCC-8W83P",
78 "Windows 7 Professional": "FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4",
79 "Windows 7 Professional N": "MRPKT-YTG23-K7D7T-X2JMM-QY7MG",
80 "Windows 7 Professional E": "W82YF-2Q76Y-63HXB-FGJG9-GF7QX",
81 "Windows 7 Enterprise": "33PXH-7Y6KF-2VJC9-XBBR8-HVTHH",
82 "Windows 7 Enterprise N": "YDRBP-3D83W-TY26F-D46B2-XCKRJ",
83 "Windows 7 Enterprise E": "C29WB-22CC8-VJ326-GHFJW-H9DH4",
84 "Windows Server 2008 R2 Web": "6TPJF-RBVHG-WBW2R-86QPH-6RTM4",
85 "Windows Server 2008 R2 HPC edition": "TT8MH-CG224-D3D7Q-498W2-9QCTX",
86 "Windows Server 2008 R2 Standard": "YC6KT-GKW9T-YTKYR-T4X34-R7VHC",
87 "Windows Server 2008 R2 Enterprise": "489J6-VHDMP-X63PK-3K798-CPX3Y",
88 "Windows Server 2008 R2 Datacenter": "74YFP-3QFB3-KQT8W-PMXWJ-7M648",
89 "Windows Server 2008 R2 for Itanium-based Systems":
90 "GT63C-RJFQ3-4GMB6-BRFB9-CB83V",
91 "Windows Vista Business": "YFKBB-PQJJV-G996G-VWGXY-2V3X8",
92 "Windows Vista Business N": "HMBQG-8H2RH-C77VX-27R82-VMQBT",
93 "Windows Vista Enterprise": "VKK3X-68KWM-X2YGT-QR4M6-4BWMV",
94 "Windows Vista Enterprise N": "VTC42-BM838-43QHV-84HX6-XJXKV",
95 "Windows Web Server 2008": "WYR28-R7TFJ-3X2YQ-YCY4H-M249D",
96 "Windows Server 2008 Standard": "TM24T-X9RMF-VWXK6-X8JC9-BFGM2",
97 "Windows Server 2008 Standard without Hyper-V":
98 "W7VD6-7JFBR-RX26B-YKQ3Y-6FFFJ",
99 "Windows Server 2008 Enterprise":
100 "YQGMW-MPWTJ-34KDK-48M3W-X4Q6V",
101 "Windows Server 2008 Enterprise without Hyper-V":
102 "39BXF-X8Q23-P2WWT-38T2F-G3FPG",
103 "Windows Server 2008 HPC": "RCTX3-KWVHP-BR6TB-RB6DM-6X7HP",
104 "Windows Server 2008 Datacenter": "7M67G-PC374-GR742-YH8V4-TCBY3",
105 "Windows Server 2008 Datacenter without Hyper-V":
106 "22XQ2-VRXRG-P8D42-K34TD-G3QQC",
107 "Windows Server 2008 for Itanium-Based Systems":
108 "4DWFP-JF3DJ-B7DTH-78FJB-PDRHK"}
110 _POSINT = lambda x: type(x) == int and x >= 0
113 class Windows(OSBase):
114 """OS class for Windows"""
116 'shutdown_timeout', int, 120, "Shutdown Timeout (seconds)", _POSINT)
118 'boot_timeout', int, 300, "Boot Timeout (seconds)", _POSINT)
120 'connection_retries', int, 5, "Connection Retries", _POSINT)
121 @add_sysprep_param('password', str, None, 'Image Administrator Password')
122 def __init__(self, image, **kargs):
123 super(Windows, self).__init__(image, **kargs)
125 # The commit with the following message was added in
126 # libguestfs 1.17.18 and was backported in version 1.16.11:
128 # When a Windows guest doesn't have a HKLM\SYSTEM\MountedDevices node,
129 # inspection fails. However inspection should not completely fail just
130 # because we cannot get the drive letter mapping from a guest.
132 # Since Microsoft Sysprep removes the aforementioned key, image
133 # creation for windows can only be supported if the installed guestfs
134 # version is 1.17.18 or higher
135 if self.image.check_guestfs_version(1, 17, 18) < 0 and \
136 (self.image.check_guestfs_version(1, 17, 0) >= 0 or
137 self.image.check_guestfs_version(1, 16, 11) < 0):
139 'For windows support libguestfs 1.16.11 or above is required')
141 device = self.image.g.part_to_dev(self.root)
143 self.last_part_num = self.image.g.part_list(device)[-1]['part_num']
144 self.last_drive = None
145 self.system_drive = None
147 for drive, part in self.image.g.inspect_get_drive_mappings(self.root):
148 if part == "%s%d" % (device, self.last_part_num):
149 self.last_drive = drive
150 if part == self.root:
151 self.system_drive = drive
153 assert self.system_drive
155 self.product_name = self.image.g.inspect_get_product_name(self.root)
156 self.syspreped = False
158 @sysprep('Disabling IPv6 privacy extensions')
159 def disable_ipv6_privacy_extensions(self):
160 """Disable IPv6 privacy extensions"""
162 self._guest_exec('netsh interface ipv6 set global '
163 'randomizeidentifiers=disabled store=persistent')
165 @sysprep('Disabling Teredo interface')
166 def disable_teredo(self):
167 """Disable Teredo interface"""
169 self._guest_exec('netsh interface teredo set state disabled')
171 @sysprep('Disabling ISATAP Adapters')
172 def disable_isatap(self):
173 """Disable ISATAP Adapters"""
175 self._guest_exec('netsh interface isa set state disabled')
177 @sysprep('Enabling ping responses')
178 def enable_pings(self):
179 """Enable ping responses"""
181 self._guest_exec('netsh firewall set icmpsetting 8')
183 @sysprep('Disabling hibernation support')
184 def disable_hibernation(self):
185 """Disable hibernation support and remove the hibernation file"""
187 self._guest_exec(r'powercfg.exe /hibernate off')
189 @sysprep('Setting the system clock to UTC')
191 """Set the hardware clock to UTC"""
193 path = r'HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
195 r'REG ADD %s /v RealTimeIsUniversal /t REG_DWORD /d 1 /f' % path)
197 @sysprep('Clearing the event logs')
198 def clear_logs(self):
199 """Clear all the event logs"""
202 r"cmd /q /c for /f %l in ('wevtutil el') do wevtutil cl %l")
204 @sysprep('Executing Sysprep on the image (may take more that 10 minutes)')
205 def microsoft_sysprep(self):
206 """Run the Microsoft System Preparation Tool. This will remove
207 system-specific data and will make the image ready to be deployed.
208 After this no other task may run.
211 self._guest_exec(r'C:\Windows\system32\sysprep\sysprep '
212 r'/quiet /generalize /oobe /shutdown')
213 self.syspreped = True
215 @sysprep('Converting the image into a KMS client', enabled=False)
216 def kms_client_setup(self):
217 """Install the appropriate KMS client setup key to the image to convert
218 it to a KMS client. Computers that are running volume licensing
219 editions of Windows 8, Windows Server 2012, Windows 7, Windows Server
220 2008 R2, Windows Vista, and Windows Server 2008 are by default KMS
221 clients with no additional configuration needed.
224 setup_key = KMS_CLIENT_SETUP_KEYS[self.product_name]
227 "Don't know the KMS client setup key for product: `%s'" %
232 r"cscript \Windows\system32\slmgr.vbs /ipk %s" % setup_key)
234 @sysprep('Shrinking the last filesystem')
236 """Shrink the last filesystem. Make sure the filesystem is defragged"""
238 # Query for the maximum number of reclaimable bytes
240 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
241 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
242 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
243 r'ECHO SHRINK QUERYMAX >> %SCRIPT% & ' +
244 r'ECHO EXIT >> %SCRIPT% & ' +
245 r'DISKPART /S %SCRIPT% & ' +
246 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
249 stdout, stderr, rc = self._guest_exec(cmd)
252 for line in stdout.splitlines():
253 # diskpart will return something like this:
255 # The maximum number of reclaimable bytes is: xxxx MB
257 if line.find('reclaimable') >= 0:
258 querymax = line.split(':')[1].split()[0].strip()
259 assert querymax.isdigit(), \
260 "Number of reclaimable bytes not a number"
263 FatalError("Error in shrinking! "
264 "Couldn't find the max number of reclaimable bytes!")
266 querymax = int(querymax)
268 # Practically the smallest shrunken size generally is at around
269 # "used space" + (20-200 MB). Please also take into account that
270 # Windows might need about 50-100 MB free space left to boot safely.
271 # I'll give 100MB extra space just to be sure
275 self.out.warn("Not enough available space to shrink the image!")
278 self.out.output("\tReclaiming %dMB ..." % querymax)
281 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
282 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
283 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
284 'ECHO SHRINK DESIRED=%d >> %%SCRIPT%% & ' % querymax +
285 r'ECHO EXIT >> %SCRIPT% & ' +
286 r'DISKPART /S %SCRIPT% & ' +
287 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
290 stdout, stderr, rc = self._guest_exec(cmd, False)
293 FatalError("Shrinking failed. Please make sure the media is "
294 "defraged with a command like this: "
295 "`Defrag.exe /U /X /W'")
296 for line in stdout.splitlines():
297 if line.find('shrunk') >= 0:
298 self.out.output(line)
300 def do_sysprep(self):
301 """Prepare system for image creation."""
303 if getattr(self, 'syspreped', False):
304 raise FatalError("Image is already syspreped!")
306 txt = "System preparation parameter: `%s' is needed but missing!"
307 for name, param in self.needed_sysprep_params.items():
308 if name not in self.sysprep_params:
309 raise FatalError(txt % name)
311 self.mount(readonly=False)
313 disabled_uac = self._update_uac_remote_setting(1)
314 token = self._enable_os_monitor()
316 # disable the firewalls
317 firewall_states = self._update_firewalls(0, 0, 0)
319 # Delete the pagefile. It will be recreated when the system boots
320 systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
322 pagefile = "%s/pagefile.sys" % systemroot
323 self.image.g.rm_rf(self.image.g.case_sensitive_path(pagefile))
330 self.image.disable_guestfs()
335 self.out.output("Starting windows VM ...", False)
336 monitorfd, monitor = tempfile.mkstemp()
338 vm = _VM(self.image.device, monitor, self.sysprep_params)
339 self.out.success("started (console on vnc display: %d)." %
342 self.out.output("Waiting for OS to boot ...", False)
343 self._wait_vm_boot(vm, monitor, token)
344 self.out.success('done')
346 self.out.output("Checking connectivity to the VM ...", False)
347 self._check_connectivity()
348 self.out.success('done')
350 self.out.output("Disabling automatic logon ...", False)
351 self._disable_autologon()
352 self.out.success('done')
354 self.out.output('Preparing system for image creation:')
356 tasks = self.list_syspreps()
357 enabled = [task for task in tasks if task.enabled]
360 # Make sure shrink runs in the end, before ms sysprep
361 enabled = [task for task in enabled if
362 self.sysprep_info(task).name != 'shrink']
364 if len(enabled) != size:
365 enabled.append(self.shrink)
367 # Make sure the ms sysprep is the last task to run if it is enabled
368 enabled = [task for task in enabled if
369 self.sysprep_info(task).name != 'microsoft-sysprep']
371 ms_sysprep_enabled = False
372 if len(enabled) != size:
373 enabled.append(self.microsoft_sysprep)
374 ms_sysprep_enabled = True
379 self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False)
381 setattr(task.im_func, 'executed', True)
383 self.out.output("Sending shut down command ...", False)
384 if not ms_sysprep_enabled:
386 self.out.success("done")
388 self.out.output("Waiting for windows to shut down ...", False)
389 vm.wait(self.sysprep_params['shutdown_timeout'])
390 self.out.success("done")
392 if monitor is not None:
397 self.out.output("Destroying windows VM ...", False)
399 self.out.success("done")
401 self.image.enable_guestfs()
403 self.mount(readonly=False)
406 self._update_uac_remote_setting(0)
408 self._update_firewalls(*firewall_states)
413 """Shuts down the windows VM"""
414 self._guest_exec(r'shutdown /s /t 5')
416 def _wait_vm_boot(self, vm, fname, msg):
417 """Wait until a message appears on a file or the vm process dies"""
419 for _ in range(self.sysprep_params['boot_timeout']):
421 with open(fname) as f:
423 if line.startswith(msg):
426 raise FatalError("Windows VM died unexpectedly!")
428 raise FatalError("Windows VM booting timed out!")
430 def _disable_autologon(self):
431 """Disable automatic logon on the windows image"""
434 r'"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"'
436 self._guest_exec('REG DELETE %s /v DefaultUserName /f' % winlogon)
437 self._guest_exec('REG DELETE %s /v DefaultPassword /f' % winlogon)
438 self._guest_exec('REG DELETE %s /v AutoAdminLogon /f' % winlogon)
440 def _registry_file_path(self, regfile):
441 """Retrieves the case sensitive path to a registry file"""
443 systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
444 path = "%s/system32/config/%s" % (systemroot, regfile)
446 path = self.image.g.case_sensitive_path(path)
447 except RuntimeError as error:
448 raise FatalError("Unable to retrieve registry file: %s. Reason: %s"
449 % (regfile, str(error)))
452 def _enable_os_monitor(self):
453 """Add a script in the registry that will send a random string to the
454 first serial port when the windows image finishes booting.
457 token = "".join(random.choice(string.ascii_letters) for x in range(16))
459 path = self._registry_file_path('SOFTWARE')
460 softwarefd, software = tempfile.mkstemp()
463 self.image.g.download(path, software)
465 h = hivex.Hivex(software, write=True)
467 # Enable automatic logon.
468 # This is needed because we need to execute a script that we add in
469 # the RunOnce registry entry and those programs only get executed
470 # when a user logs on. There is a RunServicesOnce registry entry
471 # whose keys get executed in the background when the logon dialog
472 # box first appears, but they seem to only work with services and
473 # not arbitrary command line expressions :-(
475 # Instructions on how to turn on automatic logon in Windows can be
476 # found here: http://support.microsoft.com/kb/324737
478 # Warning: Registry change will not work if the “Logon Banner” is
479 # defined on the server either by a Group Policy object (GPO) or by
483 for child in ('Microsoft', 'Windows NT', 'CurrentVersion',
485 winlogon = h.node_get_child(winlogon, child)
489 {'key': 'DefaultUserName', 't': 1,
490 'value': "Administrator".encode('utf-16le')})
493 {'key': 'DefaultPassword', 't': 1,
494 'value': self.sysprep_params['password'].encode('utf-16le')})
497 {'key': 'AutoAdminLogon', 't': 1,
498 'value': "1".encode('utf-16le')})
501 for child in ('Microsoft', 'Windows', 'CurrentVersion'):
502 key = h.node_get_child(key, child)
504 runonce = h.node_get_child(key, "RunOnce")
506 runonce = h.node_add_child(key, "RunOnce")
509 r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe '
510 r'-ExecutionPolicy RemoteSigned '
511 r'"&{$port=new-Object System.IO.Ports.SerialPort COM1,9600,'
512 r'None,8,one;$port.open();$port.WriteLine(\"' + token + r'\");'
513 r'$port.Close()}"').encode('utf-16le')
515 h.node_set_value(runonce,
516 {'key': "BootMonitor", 't': 1, 'value': value})
519 r'REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion'
520 r'\policies\system /v LocalAccountTokenFilterPolicy'
521 r' /t REG_DWORD /d 1 /f').encode('utf-16le')
523 h.node_set_value(runonce,
524 {'key': "UpdateRegistry", 't': 1, 'value': value})
528 self.image.g.upload(software, path)
534 def _update_firewalls(self, domain, public, standard):
535 """Enables or disables the firewall for the Domain, the Public and the
536 Standard profile. Returns a triplete with the old values.
538 1 will enable a firewall and 0 will disable it
541 if domain not in (0, 1):
542 raise ValueError("Valid values for domain parameter are 0 and 1")
544 if public not in (0, 1):
545 raise ValueError("Valid values for public parameter are 0 and 1")
547 if standard not in (0, 1):
548 raise ValueError("Valid values for standard parameter are 0 and 1")
550 path = self._registry_file_path("SYSTEM")
551 systemfd, system = tempfile.mkstemp()
554 self.image.g.download(path, system)
556 h = hivex.Hivex(system, write=True)
558 select = h.node_get_child(h.root(), 'Select')
559 current_value = h.node_get_value(select, 'Current')
561 # expecting a little endian dword
562 assert h.value_type(current_value)[1] == 4
563 current = "%03d" % h.value_dword(current_value)
565 firewall_policy = h.root()
566 for child in ('ControlSet%s' % current, 'services', 'SharedAccess',
567 'Parameters', 'FirewallPolicy'):
568 firewall_policy = h.node_get_child(firewall_policy, child)
571 new_values = [domain, public, standard]
572 for profile in ('Domain', 'Public', 'Standard'):
573 node = h.node_get_child(firewall_policy, '%sProfile' % profile)
575 old_value = h.node_get_value(node, 'EnableFirewall')
577 # expecting a little endian dword
578 assert h.value_type(old_value)[1] == 4
579 old_values.append(h.value_dword(old_value))
582 node, {'key': 'EnableFirewall', 't': 4L,
583 'value': struct.pack("<I", new_values.pop(0))})
586 self.image.g.upload(system, path)
593 def _update_uac_remote_setting(self, value):
594 """Updates the registry key value:
595 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
596 \System]"LocalAccountTokenFilterPolicy"
598 value = 1 will disable the UAC remote restrictions
599 value = 0 will enable the UAC remote restrictions
601 For more info see here: http://support.microsoft.com/kb/951016
604 True if the key is changed
605 False if the key is unchanged
608 if value not in (0, 1):
609 raise ValueError("Valid values for value parameter are 0 and 1")
611 path = self._registry_file_path('SOFTWARE')
612 softwarefd, software = tempfile.mkstemp()
615 self.image.g.download(path, software)
617 h = hivex.Hivex(software, write=True)
620 for child in ('Microsoft', 'Windows', 'CurrentVersion', 'Policies',
622 key = h.node_get_child(key, child)
625 for val in h.node_values(key):
626 if h.value_key(val) == "LocalAccountTokenFilterPolicy":
629 if policy is not None:
630 dword = h.value_dword(policy)
636 new_value = {'key': "LocalAccountTokenFilterPolicy", 't': 4L,
637 'value': struct.pack("<I", value)}
639 h.node_set_value(key, new_value)
642 self.image.g.upload(software, path)
649 def _do_collect_metadata(self):
650 """Collect metadata about the OS"""
651 super(Windows, self)._do_collect_metadata()
652 self.meta["USERS"] = " ".join(self._get_users())
654 def _get_users(self):
655 """Returns a list of users found in the images"""
656 samfd, sam = tempfile.mkstemp()
659 self.image.g.download(self._registry_file_path('SAM'), sam)
663 # Navigate to /SAM/Domains/Account/Users
664 users_node = h.root()
665 for child in ('SAM', 'Domains', 'Account', 'Users'):
666 users_node = h.node_get_child(users_node, child)
668 # Navigate to /SAM/Domains/Account/Users/Names
669 names_node = h.node_get_child(users_node, 'Names')
671 # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\%RID%
672 # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\Names\%Username%
674 # The RID (relative identifier) of each user is stored as the type!
675 # (not the value) of the default key of the node under Names whose
676 # name is the user's username. Under the RID node, there in a F
677 # value that contains information about this user account.
679 # See sam.h of the chntpw project on how to translate the F value
680 # of an account in the registry. Bytes 56 & 57 are the account type
681 # and status flags. The first bit is the 'account disabled' bit
682 disabled = lambda f: int(f[56].encode('hex'), 16) & 0x01
685 for user_node in h.node_children(names_node):
686 username = h.node_name(user_node)
687 rid = h.value_type(h.node_get_value(user_node, ""))[0]
688 # if RID is 500 (=0x1f4), the corresponding node name under
689 # Users is '000001F4'
690 key = ("%8.x" % rid).replace(' ', '0').upper()
691 rid_node = h.node_get_child(users_node, key)
692 f_value = h.value_value(h.node_get_value(rid_node, 'F'))[1]
694 if disabled(f_value):
695 self.out.warn("Found disabled `%s' account!" % username)
698 users.append(username)
703 # Filter out the guest account
706 def _check_connectivity(self):
707 """Check if winexe works on the Windows VM"""
709 retries = self.sysprep_params['connection_retries']
710 # If the connection_retries parameter is set to 0 disable the
715 passwd = self.sysprep_params['password']
716 winexe = WinEXE('Administrator', passwd, 'localhost')
717 winexe.uninstall().debug(9)
719 for i in range(retries):
720 (stdout, stderr, rc) = winexe.run('cmd /C')
723 log = tempfile.NamedTemporaryFile(delete=False)
725 log.file.write(stdout)
728 self.out.output("failed! See: `%s' for the full output" % log.name)
730 self.out.output("retrying ...", False)
732 raise FatalError("Connection to the Windows VM failed after %d retries"
735 def _guest_exec(self, command, fatal=True):
736 """Execute a command on a windows VM"""
738 passwd = self.sysprep_params['password']
740 winexe = WinEXE('Administrator', passwd, 'localhost')
741 winexe.runas('Administrator', passwd).uninstall()
744 (stdout, stderr, rc) = winexe.run(command)
745 except WinexeTimeout:
746 FatalError("Command: `%s' timeout out." % command)
748 if rc != 0 and fatal:
749 reason = stderr if len(stderr) else stdout
750 self.out.output("Command: `%s' failed (rc=%d). Reason: %s" %
751 (command, rc, reason))
752 raise FatalError("Command: `%s' failed (rc=%d). Reason: %s" %
753 (command, rc, reason))
755 return (stdout, stderr, rc)
759 """Windows Virtual Machine"""
760 def __init__(self, disk, serial, params):
761 """Create _VM instance
764 serial: File to save the output of the serial port
772 """creates a random mac address"""
773 mac = [0x00, 0x16, 0x3e,
774 random.randint(0x00, 0x7f),
775 random.randint(0x00, 0xff),
776 random.randint(0x00, 0xff)]
778 return ':'.join(['%02x' % x for x in mac])
780 # Use ganeti's VNC port range for a random vnc port
781 self.display = random.randint(11000, 14999) - 5900
783 kvm, needed_args = get_kvm_binary()
786 FatalError("Can't find the kvm binary")
789 args.extend(needed_args)
792 '-smp', '1', '-m', '1024', '-drive',
793 'file=%s,format=raw,cache=unsafe,if=virtio' % self.disk,
794 '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
795 '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(),
796 '-vnc', ':%d' % self.display, '-serial', 'file:%s' % self.serial,
797 '-monitor', 'stdio'])
799 self.process = subprocess.Popen(args, stdin=subprocess.PIPE,
800 stdout=subprocess.PIPE)
803 """Check if the VM is still alive"""
804 return self.process.poll() is None
809 if not self.isalive():
812 def handler(signum, frame):
813 self.process.terminate()
818 raise FatalError("VM destroy timed-out")
820 signal.signal(signal.SIGALRM, handler)
822 signal.alarm(self.params['shutdown_timeout'])
823 self.process.communicate(input="system_powerdown\n")
826 def wait(self, timeout=0):
827 """Wait for the VM to terminate"""
829 def handler(signum, frame):
831 raise FatalError("VM wait timed-out.")
833 signal.signal(signal.SIGALRM, handler)
835 signal.alarm(timeout)
836 stdout, stderr = self.process.communicate()
839 return (stdout, stderr, self.process.poll())
841 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :