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 Professional": "NG4HW-VH26C-733KW-K6F98-J8CK4",
56 "Windows 8 Professional N": "XCVCF-2NXM9-723PB-MHCB7-2RYQQ",
57 "Windows 8 Enterprise": "32JNW-9KQ84-P47T8-D8GGY-CWCK7",
58 "Windows 8 Enterprise N": "JMNMF-RHW7P-DMY6X-RF3DR-X2BQT",
59 "Windows Server 2012 Core": "BN3D2-R7TKB-3YPBD-8DRP2-27GG4",
60 "Windows Server 2012 Core N": "8N2M2-HWPGY-7PGT9-HGDD8-GVGGY",
61 "Windows Server 2012 Core Single Language":
62 "2WN2H-YGCQR-KFX6K-CD6TF-84YXQ",
63 "Windows Server 2012 Core Country Specific":
64 "4K36P-JN4VD-GDC6V-KDT89-DYFKP",
65 "Windows Server 2012 Server Standard": "XC9B7-NBPP2-83J2H-RHMBY-92BT4",
66 "Windows Server 2012 Standard Core": "XC9B7-NBPP2-83J2H-RHMBY-92BT4",
67 "Windows Server 2012 MultiPoint Standard": "HM7DN-YVMH3-46JC3-XYTG7-CYQJJ",
68 "Windows Server 2012 MultiPoint Premium": "XNH6W-2V9GX-RGJ4K-Y8X6F-QGJ2G",
69 "Windows Server 2012 Datacenter": "48HP8-DN98B-MYWDG-T2DCC-8W83P",
70 "Windows Server 2012 Datacenter Core": "48HP8-DN98B-MYWDG-T2DCC-8W83P",
71 "Windows 7 Professional": "FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4",
72 "Windows 7 Professional N": "MRPKT-YTG23-K7D7T-X2JMM-QY7MG",
73 "Windows 7 Professional E": "W82YF-2Q76Y-63HXB-FGJG9-GF7QX",
74 "Windows 7 Enterprise": "33PXH-7Y6KF-2VJC9-XBBR8-HVTHH",
75 "Windows 7 Enterprise N": "YDRBP-3D83W-TY26F-D46B2-XCKRJ",
76 "Windows 7 Enterprise E": "C29WB-22CC8-VJ326-GHFJW-H9DH4",
77 "Windows Server 2008 R2 Web": "6TPJF-RBVHG-WBW2R-86QPH-6RTM4",
78 "Windows Server 2008 R2 HPC edition": "TT8MH-CG224-D3D7Q-498W2-9QCTX",
79 "Windows Server 2008 R2 Standard": "YC6KT-GKW9T-YTKYR-T4X34-R7VHC",
80 "Windows Server 2008 R2 Enterprise": "489J6-VHDMP-X63PK-3K798-CPX3Y",
81 "Windows Server 2008 R2 Datacenter": "74YFP-3QFB3-KQT8W-PMXWJ-7M648",
82 "Windows Server 2008 R2 for Itanium-based Systems":
83 "GT63C-RJFQ3-4GMB6-BRFB9-CB83V",
84 "Windows Vista Business": "YFKBB-PQJJV-G996G-VWGXY-2V3X8",
85 "Windows Vista Business N": "HMBQG-8H2RH-C77VX-27R82-VMQBT",
86 "Windows Vista Enterprise": "VKK3X-68KWM-X2YGT-QR4M6-4BWMV",
87 "Windows Vista Enterprise N": "VTC42-BM838-43QHV-84HX6-XJXKV",
88 "Windows Web Server 2008": "WYR28-R7TFJ-3X2YQ-YCY4H-M249D",
89 "Windows Server 2008 Standard": "TM24T-X9RMF-VWXK6-X8JC9-BFGM2",
90 "Windows Server 2008 Standard without Hyper-V":
91 "W7VD6-7JFBR-RX26B-YKQ3Y-6FFFJ",
92 "Windows Server 2008 Enterprise":
93 "YQGMW-MPWTJ-34KDK-48M3W-X4Q6V",
94 "Windows Server 2008 Enterprise without Hyper-V":
95 "39BXF-X8Q23-P2WWT-38T2F-G3FPG",
96 "Windows Server 2008 HPC": "RCTX3-KWVHP-BR6TB-RB6DM-6X7HP",
97 "Windows Server 2008 Datacenter": "7M67G-PC374-GR742-YH8V4-TCBY3",
98 "Windows Server 2008 Datacenter without Hyper-V":
99 "22XQ2-VRXRG-P8D42-K34TD-G3QQC",
100 "Windows Server 2008 for Itanium-Based Systems":
101 "4DWFP-JF3DJ-B7DTH-78FJB-PDRHK"}
103 _POSINT = lambda x: type(x) == int and x >= 0
106 class Windows(OSBase):
107 """OS class for Windows"""
109 'shutdown_timeout', int, 120, "Shutdown Timeout (seconds)", _POSINT)
111 'boot_timeout', int, 300, "Boot Timeout (seconds)", _POSINT)
113 'connection_retries', int, 5, "Connection Retries", _POSINT)
114 @add_sysprep_param('password', str, None, 'Image Administrator Password')
115 def __init__(self, image, **kargs):
116 super(Windows, self).__init__(image, **kargs)
118 # The commit with the following message was added in
119 # libguestfs 1.17.18 and was backported in version 1.16.11:
121 # When a Windows guest doesn't have a HKLM\SYSTEM\MountedDevices node,
122 # inspection fails. However inspection should not completely fail just
123 # because we cannot get the drive letter mapping from a guest.
125 # Since Microsoft Sysprep removes the aforementioned key, image
126 # creation for windows can only be supported if the installed guestfs
127 # version is 1.17.18 or higher
128 if self.image.check_guestfs_version(1, 17, 18) < 0 and \
129 (self.image.check_guestfs_version(1, 17, 0) >= 0 or
130 self.image.check_guestfs_version(1, 16, 11) < 0):
132 'For windows support libguestfs 1.16.11 or above is required')
134 device = self.image.g.part_to_dev(self.root)
136 self.last_part_num = self.image.g.part_list(device)[-1]['part_num']
137 self.last_drive = None
138 self.system_drive = None
140 for drive, part in self.image.g.inspect_get_drive_mappings(self.root):
141 if part == "%s%d" % (device, self.last_part_num):
142 self.last_drive = drive
143 if part == self.root:
144 self.system_drive = drive
146 assert self.system_drive
148 self.product_name = self.image.g.inspect_get_product_name(self.root)
149 self.syspreped = False
151 @sysprep('Disabling IPv6 privacy extensions')
152 def disable_ipv6_privacy_extensions(self):
153 """Disable IPv6 privacy extensions"""
155 self._guest_exec('netsh interface ipv6 set global '
156 'randomizeidentifiers=disabled store=persistent')
158 @sysprep('Disabling Teredo interface')
159 def disable_teredo(self):
160 """Disable Teredo interface"""
162 self._guest_exec('netsh interface teredo set state disabled')
164 @sysprep('Disabling ISATAP Adapters')
165 def disable_isatap(self):
166 """Disable ISATAP Adapters"""
168 self._guest_exec('netsh interface isa set state disabled')
170 @sysprep('Enabling ping responses')
171 def enable_pings(self):
172 """Enable ping responses"""
174 self._guest_exec('netsh firewall set icmpsetting 8')
176 @sysprep('Disabling hibernation support')
177 def disable_hibernation(self):
178 """Disable hibernation support and remove the hibernation file"""
180 self._guest_exec(r'powercfg.exe /hibernate off')
182 @sysprep('Setting the system clock to UTC')
184 """Set the hardware clock to UTC"""
186 path = r'HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
188 r'REG ADD %s /v RealTimeIsUniversal /t REG_DWORD /d 1 /f' % path)
190 @sysprep('Clearing the event logs')
191 def clear_logs(self):
192 """Clear all the event logs"""
195 r"cmd /q /c for /f %l in ('wevtutil el') do wevtutil cl %l")
197 @sysprep('Executing Sysprep on the image (may take more that 10 minutes)')
198 def microsoft_sysprep(self):
199 """Run the Microsoft System Preparation Tool. This will remove
200 system-specific data and will make the image ready to be deployed.
201 After this no other task may run.
204 self._guest_exec(r'C:\Windows\system32\sysprep\sysprep '
205 r'/quiet /generalize /oobe /shutdown')
206 self.syspreped = True
208 @sysprep('Converting the image into a KMS client', enabled=False)
209 def kms_client_setup(self):
210 """Install the appropriate KMS client setup key to the image to convert
211 it to a KMS client. Computers that are running volume licensing
212 editions of Windows 8, Windows Server 2012, Windows 7, Windows Server
213 2008 R2, Windows Vista, and Windows Server 2008 are by default KMS
214 clients with no additional configuration needed.
217 setup_key = KMS_CLIENT_SETUP_KEYS[self.product_name]
220 "Don't know the KMS client setup key for product: `%s'" %
225 r"cscript \Windows\system32\slmgr.vbs /ipk %s" % setup_key)
227 @sysprep('Shrinking the last filesystem')
229 """Shrink the last filesystem. Make sure the filesystem is defragged"""
231 # Query for the maximum number of reclaimable bytes
233 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
234 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
235 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
236 r'ECHO SHRINK QUERYMAX >> %SCRIPT% & ' +
237 r'ECHO EXIT >> %SCRIPT% & ' +
238 r'DISKPART /S %SCRIPT% & ' +
239 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
242 stdout, stderr, rc = self._guest_exec(cmd)
245 for line in stdout.splitlines():
246 # diskpart will return something like this:
248 # The maximum number of reclaimable bytes is: xxxx MB
250 if line.find('reclaimable') >= 0:
251 querymax = line.split(':')[1].split()[0].strip()
252 assert querymax.isdigit(), \
253 "Number of reclaimable bytes not a number"
256 FatalError("Error in shrinking! "
257 "Couldn't find the max number of reclaimable bytes!")
259 querymax = int(querymax)
261 # Practically the smallest shrunken size generally is at around
262 # "used space" + (20-200 MB). Please also take into account that
263 # Windows might need about 50-100 MB free space left to boot safely.
264 # I'll give 100MB extra space just to be sure
268 self.out.warn("Not enought available space to shrink the image!")
271 self.out.output("\tReclaiming %dMB ..." % querymax)
274 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
275 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
276 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
277 'ECHO SHRINK DESIRED=%d >> %%SCRIPT%% & ' % querymax +
278 r'ECHO EXIT >> %SCRIPT% & ' +
279 r'DISKPART /S %SCRIPT% & ' +
280 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
283 stdout, stderr, rc = self._guest_exec(cmd, False)
286 FatalError("Shrinking failed. Please make sure the media is "
287 "defraged with a command like this: "
288 "`Defrag.exe /U /X /W'")
289 for line in stdout.splitlines():
290 if line.find('shrunk') >= 0:
291 self.out.output(line)
293 def do_sysprep(self):
294 """Prepare system for image creation."""
296 if getattr(self, 'syspreped', False):
297 raise FatalError("Image is already syspreped!")
299 txt = "System preparation parameter: `%s' is needed but missing!"
300 for name, param in self.needed_sysprep_params.items():
301 if name not in self.sysprep_params:
302 raise FatalError(txt % name)
304 self.mount(readonly=False)
306 disabled_uac = self._update_uac_remote_setting(1)
307 token = self._enable_os_monitor()
309 # disable the firewalls
310 firewall_states = self._update_firewalls(0, 0, 0)
312 # Delete the pagefile. It will be recreated when the system boots
313 systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
315 pagefile = "%s/pagefile.sys" % systemroot
316 self.image.g.rm_rf(self.image.g.case_sensitive_path(pagefile))
323 self.image.disable_guestfs()
328 self.out.output("Starting windows VM ...", False)
329 monitorfd, monitor = tempfile.mkstemp()
331 vm = _VM(self.image.device, monitor, self.sysprep_params)
332 self.out.success("started (console on vnc display: %d)." %
335 self.out.output("Waiting for OS to boot ...", False)
336 self._wait_vm_boot(vm, monitor, token)
337 self.out.success('done')
339 self.out.output("Checking connectivity to the VM ...", False)
340 self._check_connectivity()
341 self.out.success('done')
343 self.out.output("Disabling automatic logon ...", False)
344 self._disable_autologon()
345 self.out.success('done')
347 self.out.output('Preparing system for image creation:')
349 tasks = self.list_syspreps()
350 enabled = [task for task in tasks if task.enabled]
353 # Make sure shrink runs in the end, before ms sysprep
354 enabled = [task for task in enabled if
355 self.sysprep_info(task).name != 'shrink']
357 if len(enabled) != size:
358 enabled.append(self.shrink)
360 # Make sure the ms sysprep is the last task to run if it is enabled
361 enabled = [task for task in enabled if
362 self.sysprep_info(task).name != 'microsoft-sysprep']
364 ms_sysprep_enabled = False
365 if len(enabled) != size:
366 enabled.append(self.microsoft_sysprep)
367 ms_sysprep_enabled = True
372 self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False)
374 setattr(task.im_func, 'executed', True)
376 self.out.output("Sending shut down command ...", False)
377 if not ms_sysprep_enabled:
379 self.out.success("done")
381 self.out.output("Waiting for windows to shut down ...", False)
382 vm.wait(self.sysprep_params['shutdown_timeout'])
383 self.out.success("done")
385 if monitor is not None:
390 self.out.output("Destroying windows VM ...", False)
392 self.out.success("done")
394 self.image.enable_guestfs()
396 self.mount(readonly=False)
399 self._update_uac_remote_setting(0)
401 self._update_firewalls(*firewall_states)
406 """Shuts down the windows VM"""
407 self._guest_exec(r'shutdown /s /t 5')
409 def _wait_vm_boot(self, vm, fname, msg):
410 """Wait until a message appears on a file or the vm process dies"""
412 for _ in range(self.sysprep_params['boot_timeout']):
414 with open(fname) as f:
416 if line.startswith(msg):
419 raise FatalError("Windows VM died unexpectedly!")
421 raise FatalError("Windows VM booting timed out!")
423 def _disable_autologon(self):
424 """Disable automatic logon on the windows image"""
427 r'"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"'
429 self._guest_exec('REG DELETE %s /v DefaultUserName /f' % winlogon)
430 self._guest_exec('REG DELETE %s /v DefaultPassword /f' % winlogon)
431 self._guest_exec('REG DELETE %s /v AutoAdminLogon /f' % winlogon)
433 def _registry_file_path(self, regfile):
434 """Retrieves the case sensitive path to a registry file"""
436 systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
437 path = "%s/system32/config/%s" % (systemroot, regfile)
439 path = self.image.g.case_sensitive_path(path)
440 except RuntimeError as error:
441 raise FatalError("Unable to retrieve registry file: %s. Reason: %s"
442 % (regfile, str(error)))
445 def _enable_os_monitor(self):
446 """Add a script in the registry that will send a random string to the
447 first serial port when the windows image finishes booting.
450 token = "".join(random.choice(string.ascii_letters) for x in range(16))
452 path = self._registry_file_path('SOFTWARE')
453 softwarefd, software = tempfile.mkstemp()
456 self.image.g.download(path, software)
458 h = hivex.Hivex(software, write=True)
460 # Enable automatic logon.
461 # This is needed because we need to execute a script that we add in
462 # the RunOnce registry entry and those programs only get executed
463 # when a user logs on. There is a RunServicesOnce registry entry
464 # whose keys get executed in the background when the logon dialog
465 # box first appears, but they seem to only work with services and
466 # not arbitrary command line expressions :-(
468 # Instructions on how to turn on automatic logon in Windows can be
469 # found here: http://support.microsoft.com/kb/324737
471 # Warning: Registry change will not work if the “Logon Banner” is
472 # defined on the server either by a Group Policy object (GPO) or by
476 for child in ('Microsoft', 'Windows NT', 'CurrentVersion',
478 winlogon = h.node_get_child(winlogon, child)
482 {'key': 'DefaultUserName', 't': 1,
483 'value': "Administrator".encode('utf-16le')})
486 {'key': 'DefaultPassword', 't': 1,
487 'value': self.sysprep_params['password'].encode('utf-16le')})
490 {'key': 'AutoAdminLogon', 't': 1,
491 'value': "1".encode('utf-16le')})
494 for child in ('Microsoft', 'Windows', 'CurrentVersion'):
495 key = h.node_get_child(key, child)
497 runonce = h.node_get_child(key, "RunOnce")
499 runonce = h.node_add_child(key, "RunOnce")
502 r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe '
503 r'-ExecutionPolicy RemoteSigned '
504 r'"&{$port=new-Object System.IO.Ports.SerialPort COM1,9600,'
505 r'None,8,one;$port.open();$port.WriteLine(\"' + token + r'\");'
506 r'$port.Close()}"').encode('utf-16le')
508 h.node_set_value(runonce,
509 {'key': "BootMonitor", 't': 1, 'value': value})
512 r'REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion'
513 r'\policies\system /v LocalAccountTokenFilterPolicy'
514 r' /t REG_DWORD /d 1 /f').encode('utf-16le')
516 h.node_set_value(runonce,
517 {'key': "UpdateRegistry", 't': 1, 'value': value})
521 self.image.g.upload(software, path)
527 def _update_firewalls(self, domain, public, standard):
528 """Enables or disables the firewall for the Domain, the Public and the
529 Standard profile. Returns a triplete with the old values.
531 1 will enable a firewall and 0 will disable it
534 if domain not in (0, 1):
535 raise ValueError("Valid values for domain parameter are 0 and 1")
537 if public not in (0, 1):
538 raise ValueError("Valid values for public parameter are 0 and 1")
540 if standard not in (0, 1):
541 raise ValueError("Valid values for standard parameter are 0 and 1")
543 path = self._registry_file_path("SYSTEM")
544 systemfd, system = tempfile.mkstemp()
547 self.image.g.download(path, system)
549 h = hivex.Hivex(system, write=True)
551 select = h.node_get_child(h.root(), 'Select')
552 current_value = h.node_get_value(select, 'Current')
554 # expecting a little endian dword
555 assert h.value_type(current_value)[1] == 4
556 current = "%03d" % h.value_dword(current_value)
558 firewall_policy = h.root()
559 for child in ('ControlSet%s' % current, 'services', 'SharedAccess',
560 'Parameters', 'FirewallPolicy'):
561 firewall_policy = h.node_get_child(firewall_policy, child)
564 new_values = [domain, public, standard]
565 for profile in ('Domain', 'Public', 'Standard'):
566 node = h.node_get_child(firewall_policy, '%sProfile' % profile)
568 old_value = h.node_get_value(node, 'EnableFirewall')
570 # expecting a little endian dword
571 assert h.value_type(old_value)[1] == 4
572 old_values.append(h.value_dword(old_value))
575 node, {'key': 'EnableFirewall', 't': 4L,
576 'value': struct.pack("<I", new_values.pop(0))})
579 self.image.g.upload(system, path)
586 def _update_uac_remote_setting(self, value):
587 """Updates the registry key value:
588 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
589 \System]"LocalAccountTokenFilterPolicy"
591 value = 1 will disable the UAC remote restrictions
592 value = 0 will enable the UAC remote restrictions
594 For more info see here: http://support.microsoft.com/kb/951016
597 True if the key is changed
598 False if the key is unchanged
601 if value not in (0, 1):
602 raise ValueError("Valid values for value parameter are 0 and 1")
604 path = self._registry_file_path('SOFTWARE')
605 softwarefd, software = tempfile.mkstemp()
608 self.image.g.download(path, software)
610 h = hivex.Hivex(software, write=True)
613 for child in ('Microsoft', 'Windows', 'CurrentVersion', 'Policies',
615 key = h.node_get_child(key, child)
618 for val in h.node_values(key):
619 if h.value_key(val) == "LocalAccountTokenFilterPolicy":
622 if policy is not None:
623 dword = h.value_dword(policy)
629 new_value = {'key': "LocalAccountTokenFilterPolicy", 't': 4L,
630 'value': struct.pack("<I", value)}
632 h.node_set_value(key, new_value)
635 self.image.g.upload(software, path)
642 def _do_collect_metadata(self):
643 """Collect metadata about the OS"""
644 super(Windows, self)._do_collect_metadata()
645 self.meta["USERS"] = " ".join(self._get_users())
647 def _get_users(self):
648 """Returns a list of users found in the images"""
649 samfd, sam = tempfile.mkstemp()
652 self.image.g.download(self._registry_file_path('SAM'), sam)
656 # Navigate to /SAM/Domains/Account/Users
657 users_node = h.root()
658 for child in ('SAM', 'Domains', 'Account', 'Users'):
659 users_node = h.node_get_child(users_node, child)
661 # Navigate to /SAM/Domains/Account/Users/Names
662 names_node = h.node_get_child(users_node, 'Names')
664 # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\%RID%
665 # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\Names\%Username%
667 # The RID (relative identifier) of each user is stored as the type!
668 # (not the value) of the default key of the node under Names whose
669 # name is the user's username. Under the RID node, there in a F
670 # value that contains information about this user account.
672 # See sam.h of the chntpw project on how to translate the F value
673 # of an account in the registry. Bytes 56 & 57 are the account type
674 # and status flags. The first bit is the 'account disabled' bit
675 disabled = lambda f: int(f[56].encode('hex'), 16) & 0x01
678 for user_node in h.node_children(names_node):
679 username = h.node_name(user_node)
680 rid = h.value_type(h.node_get_value(user_node, ""))[0]
681 # if RID is 500 (=0x1f4), the corresponding node name under
682 # Users is '000001F4'
683 key = ("%8.x" % rid).replace(' ', '0').upper()
684 rid_node = h.node_get_child(users_node, key)
685 f_value = h.value_value(h.node_get_value(rid_node, 'F'))[1]
687 if disabled(f_value):
688 self.out.warn("Found disabled `%s' account!" % username)
691 users.append(username)
696 # Filter out the guest account
699 def _check_connectivity(self):
700 """Check if winexe works on the Windows VM"""
702 retries = self.sysprep_params['connection_retries']
703 # If the connection_retries parameter is set to 0 disable the
708 passwd = self.sysprep_params['password']
709 winexe = WinEXE('Administrator', passwd, 'localhost')
710 winexe.uninstall().debug(9)
712 for i in range(retries):
713 (stdout, stderr, rc) = winexe.run('cmd /C')
716 log = tempfile.NamedTemporaryFile(delete=False)
718 log.file.write(stdout)
721 self.out.output("failed! See: `%s' for the full output" % log.name)
723 self.out.output("retrying ...", False)
725 raise FatalError("Connection to the Windows VM failed after %d retries"
728 def _guest_exec(self, command, fatal=True):
729 """Execute a command on a windows VM"""
731 passwd = self.sysprep_params['password']
733 winexe = WinEXE('Administrator', passwd, 'localhost')
734 winexe.runas('Administrator', passwd).uninstall()
737 (stdout, stderr, rc) = winexe.run(command)
738 except WinexeTimeout:
739 FatalError("Command: `%s' timeout out." % command)
741 if rc != 0 and fatal:
742 reason = stderr if len(stderr) else stdout
743 self.out.output("Command: `%s' failed (rc=%d). Reason: %s" %
744 (command, rc, reason))
745 raise FatalError("Command: `%s' failed (rc=%d). Reason: %s" %
746 (command, rc, reason))
748 return (stdout, stderr, rc)
752 """Windows Virtual Machine"""
753 def __init__(self, disk, serial, params):
754 """Create _VM instance
757 serial: File to save the output of the serial port
765 """creates a random mac address"""
766 mac = [0x00, 0x16, 0x3e,
767 random.randint(0x00, 0x7f),
768 random.randint(0x00, 0xff),
769 random.randint(0x00, 0xff)]
771 return ':'.join(['%02x' % x for x in mac])
773 # Use ganeti's VNC port range for a random vnc port
774 self.display = random.randint(11000, 14999) - 5900
776 kvm, needed_args = get_kvm_binary()
779 FatalError("Can't find the kvm binary")
782 args.extend(needed_args)
785 '-smp', '1', '-m', '1024', '-drive',
786 'file=%s,format=raw,cache=unsafe,if=virtio' % self.disk,
787 '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
788 '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(),
789 '-vnc', ':%d' % self.display, '-serial', 'file:%s' % self.serial,
790 '-monitor', 'stdio'])
792 self.process = subprocess.Popen(args, stdin=subprocess.PIPE,
793 stdout=subprocess.PIPE)
796 """Check if the VM is still alive"""
797 return self.process.poll() is None
802 if not self.isalive():
805 def handler(signum, frame):
806 self.process.terminate()
811 raise FatalError("VM destroy timed-out")
813 signal.signal(signal.SIGALRM, handler)
815 signal.alarm(self.params['shutdown_timeout'])
816 self.process.communicate(input="system_powerdown\n")
819 def wait(self, timeout=0):
820 """Wait for the VM to terminate"""
822 def handler(signum, frame):
824 raise FatalError("VM wait timed-out.")
826 signal.signal(signal.SIGALRM, handler)
828 signal.alarm(timeout)
829 stdout, stderr = self.process.communicate()
832 return (stdout, stderr, self.process.poll())
834 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :