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, check_guestfs_version
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 device = self.g.part_to_dev(self.root)
120 self.last_part_num = self.g.part_list(device)[-1]['part_num']
121 self.last_drive = None
122 self.system_drive = None
124 for drive, partition in self.g.inspect_get_drive_mappings(self.root):
125 if partition == "%s%d" % (device, self.last_part_num):
126 self.last_drive = drive
127 if partition == self.root:
128 self.system_drive = drive
130 assert self.system_drive
132 self.product_name = self.g.inspect_get_product_name(self.root)
133 self.syspreped = False
135 @sysprep('Disabling IPv6 privacy extensions')
136 def disable_ipv6_privacy_extensions(self):
137 """Disable IPv6 privacy extensions"""
139 self._guest_exec('netsh interface ipv6 set global '
140 'randomizeidentifiers=disabled store=persistent')
142 @sysprep('Disabling Teredo interface')
143 def disable_teredo(self):
144 """Disable Teredo interface"""
146 self._guest_exec('netsh interface teredo set state disabled')
148 @sysprep('Disabling ISATAP Adapters')
149 def disable_isatap(self):
150 """Disable ISATAP Adapters"""
152 self._guest_exec('netsh interface isa set state disabled')
154 @sysprep('Enabling ping responses')
155 def enable_pings(self):
156 """Enable ping responses"""
158 self._guest_exec('netsh firewall set icmpsetting 8')
160 @sysprep('Disabling hibernation support')
161 def disable_hibernation(self):
162 """Disable hibernation support and remove the hibernation file"""
164 self._guest_exec(r'powercfg.exe /hibernate off')
166 @sysprep('Setting the system clock to UTC')
168 """Set the hardware clock to UTC"""
170 path = r'HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
172 r'REG ADD %s /v RealTimeIsUniversal /t REG_DWORD /d 1 /f' % path)
174 @sysprep('Clearing the event logs')
175 def clear_logs(self):
176 """Clear all the event logs"""
179 r"cmd /q /c for /f %l in ('wevtutil el') do wevtutil cl %l")
181 @sysprep('Executing Sysprep on the image (may take more that 10 minutes)')
182 def microsoft_sysprep(self):
183 """Run the Microsoft System Preparation Tool. This will remove
184 system-specific data and will make the image ready to be deployed.
185 After this no other task may run.
188 self._guest_exec(r'C:\Windows\system32\sysprep\sysprep '
189 r'/quiet /generalize /oobe /shutdown')
190 self.syspreped = True
192 @sysprep('Converting the image into a KMS client', enabled=False)
193 def kms_client_setup(self):
194 """Install the appropriate KMS client setup key to the image to convert
195 it to a KMS client. Computers that are running volume licensing
196 editions of Windows 8, Windows Server 2012, Windows 7, Windows Server
197 2008 R2, Windows Vista, and Windows Server 2008 are, by default, KMS
198 clients with no additional configuration needed.
201 setup_key = KMS_CLIENT_SETUP_KEYS[self.product_name]
204 "Don't know the KMS client setup key for product: `%s'" %
209 r"cscript \Windows\system32\slmgr.vbs /ipk %s" % setup_key)
211 @sysprep('Shrinking the last filesystem')
213 """Shrink the last filesystem. Make sure the filesystem is defragged"""
215 # Query for the maximum number of reclaimable bytes
217 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
218 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
219 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
220 r'ECHO SHRINK QUERYMAX >> %SCRIPT% & ' +
221 r'ECHO EXIT >> %SCRIPT% & ' +
222 r'DISKPART /S %SCRIPT% & ' +
223 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
226 stdout, stderr, rc = self._guest_exec(cmd)
229 for line in stdout.splitlines():
230 # diskpart will return something like this:
232 # The maximum number of reclaimable bytes is: xxxx MB
234 if line.find('reclaimable') >= 0:
235 querymax = line.split(':')[1].split()[0].strip()
236 assert querymax.isdigit(), \
237 "Number of reclaimable bytes not a number"
240 FatalError("Error in shrinking! "
241 "Couldn't find the max number of reclaimable bytes!")
243 querymax = int(querymax)
245 # Practically the smallest shrunken size generally is at around
246 # "used space" + (20-200 MB). Please also take into account that
247 # Windows might need about 50-100 MB free space left to boot safely.
248 # I'll give 100MB extra space just to be sure
252 self.out.warn("Not enought available space to shrink the image!")
255 self.out.output("\tReclaiming %dMB ..." % querymax)
258 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
259 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
260 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
261 'ECHO SHRINK DESIRED=%d >> %%SCRIPT%% & ' % querymax +
262 r'ECHO EXIT >> %SCRIPT% & ' +
263 r'DISKPART /S %SCRIPT% & ' +
264 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
267 stdout, stderr, rc = self._guest_exec(cmd)
269 for line in stdout.splitlines():
270 if line.find('shrunk') >= 0:
271 self.out.output(line)
273 def do_sysprep(self):
274 """Prepare system for image creation."""
276 if getattr(self, 'syspreped', False):
277 raise FatalError("Image is already syspreped!")
279 txt = "System preparation parameter: `%s' is needed but missing!"
280 for name, param in self.needed_sysprep_params.items():
281 if name not in self.sysprep_params:
282 raise FatalError(txt % param)
284 self.mount(readonly=False)
286 disabled_uac = self._update_uac_remote_setting(1)
287 token = self._enable_os_monitor()
289 # disable the firewalls
290 firewall_states = self._update_firewalls(0, 0, 0)
292 # Delete the pagefile. It will be recreated when the system boots
293 systemroot = self.g.inspect_get_windows_systemroot(self.root)
294 pagefile = "%s/pagefile.sys" % systemroot
295 self.g.rm_rf(self.g.case_sensitive_path(pagefile))
300 self.out.output("Shutting down helper VM ...", False)
302 # guestfs_shutdown which is the prefered way to shutdown the backend
303 # process was introduced in version 1.19.16
304 if check_guestfs_version(self.g, 1, 19, 16) >= 0:
307 self.g.kill_subprocess()
309 self.out.success('done')
314 self.out.output("Starting windows VM ...", False)
315 monitorfd, monitor = tempfile.mkstemp()
317 vm = _VM(self.image.device, monitor, self.sysprep_params)
318 self.out.success("started (console on vnc display: %d)." %
321 self.out.output("Waiting for OS to boot ...", False)
322 self._wait_vm_boot(vm, monitor, token)
323 self.out.success('done')
325 self.out.output("Checking connectivity to the VM ...", False)
326 self._check_connectivity()
327 self.out.success('done')
329 self.out.output("Disabling automatic logon ...", False)
330 self._disable_autologon()
331 self.out.success('done')
333 self.out.output('Preparing system for image creation:')
335 tasks = self.list_syspreps()
336 enabled = [task for task in tasks if task.enabled]
339 # Make sure shrink runs in the end, before ms sysprep
340 enabled = [task for task in enabled if
341 self.sysprep_info(task).name != 'shrink']
343 if len(enabled) != size:
344 enabled.append(self.shrink)
346 # Make sure the ms sysprep is the last task to run if it is enabled
347 enabled = [task for task in enabled if
348 self.sysprep_info(task).name != 'microsoft-sysprep']
350 ms_sysprep_enabled = False
351 if len(enabled) != size:
352 enabled.append(self.microsoft_sysprep)
353 ms_sysprep_enabled = True
358 self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False)
360 setattr(task.im_func, 'executed', True)
362 self.out.output("Sending shut down command ...", False)
363 if not ms_sysprep_enabled:
365 self.out.success("done")
367 self.out.output("Waiting for windows to shut down ...", False)
368 vm.wait(self.sysprep_params['shutdown_timeout'])
369 self.out.success("done")
371 if monitor is not None:
376 self.out.output("Destroying windows VM ...", False)
378 self.out.success("done")
380 self.out.output("Relaunching helper VM (may take a while) ...",
383 self.out.success('done')
385 self.mount(readonly=False)
388 self._update_uac_remote_setting(0)
390 self._update_firewalls(*firewall_states)
395 """Shuts down the windows VM"""
396 self._guest_exec(r'shutdown /s /t 5')
398 def _wait_vm_boot(self, vm, fname, msg):
399 """Wait until a message appears on a file or the vm process dies"""
401 for _ in range(self.sysprep_params['boot_timeout']):
403 with open(fname) as f:
405 if line.startswith(msg):
408 raise FatalError("Windows VM died unexpectedly!")
410 raise FatalError("Windows VM booting timed out!")
412 def _disable_autologon(self):
413 """Disable automatic logon on the windows image"""
416 r'"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"'
418 self._guest_exec('REG DELETE %s /v DefaultUserName /f' % winlogon)
419 self._guest_exec('REG DELETE %s /v DefaultPassword /f' % winlogon)
420 self._guest_exec('REG DELETE %s /v AutoAdminLogon /f' % winlogon)
422 def _registry_file_path(self, regfile):
423 """Retrieves the case sensitive path to a registry file"""
425 systemroot = self.g.inspect_get_windows_systemroot(self.root)
426 path = "%s/system32/config/%s" % (systemroot, regfile)
428 path = self.g.case_sensitive_path(path)
429 except RuntimeError as error:
430 raise FatalError("Unable to retrieve registry file: %s. Reason: %s"
431 % (regfile, str(error)))
434 def _enable_os_monitor(self):
435 """Add a script in the registry that will send a random string to the
436 first serial port when the windows image finishes booting.
439 token = "".join(random.choice(string.ascii_letters) for x in range(16))
441 path = self._registry_file_path('SOFTWARE')
442 softwarefd, software = tempfile.mkstemp()
445 self.g.download(path, software)
447 h = hivex.Hivex(software, write=True)
449 # Enable automatic logon.
450 # This is needed because we need to execute a script that we add in
451 # the RunOnce registry entry and those programs only get executed
452 # when a user logs on. There is a RunServicesOnce registry entry
453 # whose keys get executed in the background when the logon dialog
454 # box first appears, but they seem to only work with services and
455 # not arbitrary command line expressions :-(
457 # Instructions on how to turn on automatic logon in Windows can be
458 # found here: http://support.microsoft.com/kb/324737
460 # Warning: Registry change will not work if the “Logon Banner” is
461 # defined on the server either by a Group Policy object (GPO) or by
465 for child in ('Microsoft', 'Windows NT', 'CurrentVersion',
467 winlogon = h.node_get_child(winlogon, child)
471 {'key': 'DefaultUserName', 't': 1,
472 'value': "Administrator".encode('utf-16le')})
475 {'key': 'DefaultPassword', 't': 1,
476 'value': self.sysprep_params['password'].encode('utf-16le')})
479 {'key': 'AutoAdminLogon', 't': 1,
480 'value': "1".encode('utf-16le')})
483 for child in ('Microsoft', 'Windows', 'CurrentVersion'):
484 key = h.node_get_child(key, child)
486 runonce = h.node_get_child(key, "RunOnce")
488 runonce = h.node_add_child(key, "RunOnce")
491 r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe '
492 r'-ExecutionPolicy RemoteSigned '
493 r'"&{$port=new-Object System.IO.Ports.SerialPort COM1,9600,'
494 r'None,8,one;$port.open();$port.WriteLine(\"' + token + r'\");'
495 r'$port.Close()}"').encode('utf-16le')
497 h.node_set_value(runonce,
498 {'key': "BootMonitor", 't': 1, 'value': value})
501 r'REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion'
502 r'\policies\system /v LocalAccountTokenFilterPolicy'
503 r' /t REG_DWORD /d 1 /f').encode('utf-16le')
505 h.node_set_value(runonce,
506 {'key': "UpdateRegistry", 't': 1, 'value': value})
510 self.g.upload(software, path)
516 def _update_firewalls(self, domain, public, standard):
517 """Enables or disables the firewall for the Domain, the Public and the
518 Standard profile. Returns a triplete with the old values.
520 1 will enable a firewall and 0 will disable it
523 if domain not in (0, 1):
524 raise ValueError("Valid values for domain parameter are 0 and 1")
526 if public not in (0, 1):
527 raise ValueError("Valid values for public parameter are 0 and 1")
529 if standard not in (0, 1):
530 raise ValueError("Valid values for standard parameter are 0 and 1")
532 path = self._registry_file_path("SYSTEM")
533 systemfd, system = tempfile.mkstemp()
536 self.g.download(path, system)
538 h = hivex.Hivex(system, write=True)
540 select = h.node_get_child(h.root(), 'Select')
541 current_value = h.node_get_value(select, 'Current')
543 # expecting a little endian dword
544 assert h.value_type(current_value)[1] == 4
545 current = "%03d" % h.value_dword(current_value)
547 firewall_policy = h.root()
548 for child in ('ControlSet%s' % current, 'services', 'SharedAccess',
549 'Parameters', 'FirewallPolicy'):
550 firewall_policy = h.node_get_child(firewall_policy, child)
553 new_values = [domain, public, standard]
554 for profile in ('Domain', 'Public', 'Standard'):
555 node = h.node_get_child(firewall_policy, '%sProfile' % profile)
557 old_value = h.node_get_value(node, 'EnableFirewall')
559 # expecting a little endian dword
560 assert h.value_type(old_value)[1] == 4
561 old_values.append(h.value_dword(old_value))
564 node, {'key': 'EnableFirewall', 't': 4L,
565 'value': struct.pack("<I", new_values.pop(0))})
568 self.g.upload(system, path)
575 def _update_uac_remote_setting(self, value):
576 """Updates the registry key value:
577 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
578 \System]"LocalAccountTokenFilterPolicy"
580 value = 1 will disable the UAC remote restrictions
581 value = 0 will enable the UAC remote restrictions
583 For more info see here: http://support.microsoft.com/kb/951016
586 True if the key is changed
587 False if the key is unchanged
590 if value not in (0, 1):
591 raise ValueError("Valid values for value parameter are 0 and 1")
593 path = self._registry_file_path('SOFTWARE')
594 softwarefd, software = tempfile.mkstemp()
597 self.g.download(path, software)
599 h = hivex.Hivex(software, write=True)
602 for child in ('Microsoft', 'Windows', 'CurrentVersion', 'Policies',
604 key = h.node_get_child(key, child)
607 for val in h.node_values(key):
608 if h.value_key(val) == "LocalAccountTokenFilterPolicy":
611 if policy is not None:
612 dword = h.value_dword(policy)
618 new_value = {'key': "LocalAccountTokenFilterPolicy", 't': 4L,
619 'value': struct.pack("<I", value)}
621 h.node_set_value(key, new_value)
624 self.g.upload(software, path)
631 def _do_collect_metadata(self):
632 """Collect metadata about the OS"""
633 super(Windows, self)._do_collect_metadata()
634 self.meta["USERS"] = " ".join(self._get_users())
636 def _get_users(self):
637 """Returns a list of users found in the images"""
638 samfd, sam = tempfile.mkstemp()
641 self.g.download(self._registry_file_path('SAM'), sam)
645 # Navigate to /SAM/Domains/Account/Users
646 users_node = h.root()
647 for child in ('SAM', 'Domains', 'Account', 'Users'):
648 users_node = h.node_get_child(users_node, child)
650 # Navigate to /SAM/Domains/Account/Users/Names
651 names_node = h.node_get_child(users_node, 'Names')
653 # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\%RID%
654 # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\Names\%Username%
656 # The RID (relative identifier) of each user is stored as the type!
657 # (not the value) of the default key of the node under Names whose
658 # name is the user's username. Under the RID node, there in a F
659 # value that contains information about this user account.
661 # See sam.h of the chntpw project on how to translate the F value
662 # of an account in the registry. Bytes 56 & 57 are the account type
663 # and status flags. The first bit is the 'account disabled' bit
664 disabled = lambda f: int(f[56].encode('hex'), 16) & 0x01
667 for user_node in h.node_children(names_node):
668 username = h.node_name(user_node)
669 rid = h.value_type(h.node_get_value(user_node, ""))[0]
670 # if RID is 500 (=0x1f4), the corresponding node name under
671 # Users is '000001F4'
672 key = ("%8.x" % rid).replace(' ', '0').upper()
673 rid_node = h.node_get_child(users_node, key)
674 f_value = h.value_value(h.node_get_value(rid_node, 'F'))[1]
676 if disabled(f_value):
677 self.out.warn("Found disabled `%s' account!" % username)
680 users.append(username)
685 # Filter out the guest account
688 def _check_connectivity(self):
689 """Check if winexe works on the Windows VM"""
691 retries = self.sysprep_params['connection_retries']
692 # If the connection_retries parameter is set to 0 disable the
697 passwd = self.sysprep_params['password']
698 winexe = WinEXE('Administrator', passwd, 'localhost')
699 winexe.uninstall().debug(9)
701 for i in range(retries):
702 (stdout, stderr, rc) = winexe.run('cmd /C')
705 log = tempfile.NamedTemporaryFile(delete=False)
707 log.file.write(stdout)
710 self.out.output("failed! See: `%s' for the full output" % log.name)
712 self.out.output("retrying ...", False)
714 raise FatalError("Connection to the Windows VM failed after %d retries"
717 def _guest_exec(self, command, fatal=True):
718 """Execute a command on a windows VM"""
720 passwd = self.sysprep_params['password']
722 winexe = WinEXE('Administrator', passwd, 'localhost')
723 winexe.runas('Administrator', passwd).uninstall()
726 (stdout, stderr, rc) = winexe.run(command)
727 except WinexeTimeout:
728 FatalError("Command: `%s' timeout out." % command)
730 if rc != 0 and fatal:
731 reason = stderr if len(stderr) else stdout
732 self.out.output("Command: `%s' failed (rc=%d). Reason: %s" %
733 (command, rc, reason))
734 raise FatalError("Command: `%s' failed (rc=%d). Reason: %s" %
735 (command, rc, reason))
737 return (stdout, stderr, rc)
741 """Windows Virtual Machine"""
742 def __init__(self, disk, serial, params):
743 """Create _VM instance
746 serial: File to save the output of the serial port
754 """creates a random mac address"""
755 mac = [0x00, 0x16, 0x3e,
756 random.randint(0x00, 0x7f),
757 random.randint(0x00, 0xff),
758 random.randint(0x00, 0xff)]
760 return ':'.join(['%02x' % x for x in mac])
762 # Use ganeti's VNC port range for a random vnc port
763 self.display = random.randint(11000, 14999) - 5900
766 'kvm', '-smp', '1', '-m', '1024', '-drive',
767 'file=%s,format=raw,cache=unsafe,if=virtio' % self.disk,
768 '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
769 '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(),
770 '-vnc', ':%d' % self.display, '-serial', 'file:%s' % self.serial,
773 self.process = subprocess.Popen(args, stdin=subprocess.PIPE,
774 stdout=subprocess.PIPE)
777 """Check if the VM is still alive"""
778 return self.process.poll() is None
783 if not self.isalive():
786 def handler(signum, frame):
787 self.process.terminate()
792 raise FatalError("VM destroy timed-out")
794 signal.signal(signal.SIGALRM, handler)
796 signal.alarm(self.params['shutdown_timeout'])
797 self.process.communicate(input="system_powerdown\n")
800 def wait(self, timeout=0):
801 """Wait for the VM to terminate"""
803 def handler(signum, frame):
805 raise FatalError("VM wait timed-out.")
807 signal.signal(signal.SIGALRM, handler)
809 signal.alarm(timeout)
810 stdout, stderr = self.process.communicate()
813 return (stdout, stderr, self.process.poll())
815 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :