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
40 from image_creator.util import FatalError, check_guestfs_version, get_command
41 from image_creator.winexe import WinEXE, WinexeTimeout
54 SHUTDOWN_TIMEOUT = 120
55 CONNECTION_RETRIES = 5
57 # For more info see: http://technet.microsoft.com/en-us/library/jj612867.aspx
58 KMS_CLIENT_SETUP_KEYS = {
59 "Windows 8 Professional": "NG4HW-VH26C-733KW-K6F98-J8CK4",
60 "Windows 8 Professional N": "XCVCF-2NXM9-723PB-MHCB7-2RYQQ",
61 "Windows 8 Enterprise": "32JNW-9KQ84-P47T8-D8GGY-CWCK7",
62 "Windows 8 Enterprise N": "JMNMF-RHW7P-DMY6X-RF3DR-X2BQT",
63 "Windows Server 2012 Core": "BN3D2-R7TKB-3YPBD-8DRP2-27GG4",
64 "Windows Server 2012 Core N": "8N2M2-HWPGY-7PGT9-HGDD8-GVGGY",
65 "Windows Server 2012 Core Single Language":
66 "2WN2H-YGCQR-KFX6K-CD6TF-84YXQ",
67 "Windows Server 2012 Core Country Specific":
68 "4K36P-JN4VD-GDC6V-KDT89-DYFKP",
69 "Windows Server 2012 Server Standard": "XC9B7-NBPP2-83J2H-RHMBY-92BT4",
70 "Windows Server 2012 Standard Core": "XC9B7-NBPP2-83J2H-RHMBY-92BT4",
71 "Windows Server 2012 MultiPoint Standard": "HM7DN-YVMH3-46JC3-XYTG7-CYQJJ",
72 "Windows Server 2012 MultiPoint Premium": "XNH6W-2V9GX-RGJ4K-Y8X6F-QGJ2G",
73 "Windows Server 2012 Datacenter": "48HP8-DN98B-MYWDG-T2DCC-8W83P",
74 "Windows Server 2012 Datacenter Core": "48HP8-DN98B-MYWDG-T2DCC-8W83P",
75 "Windows 7 Professional": "FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4",
76 "Windows 7 Professional N": "MRPKT-YTG23-K7D7T-X2JMM-QY7MG",
77 "Windows 7 Professional E": "W82YF-2Q76Y-63HXB-FGJG9-GF7QX",
78 "Windows 7 Enterprise": "33PXH-7Y6KF-2VJC9-XBBR8-HVTHH",
79 "Windows 7 Enterprise N": "YDRBP-3D83W-TY26F-D46B2-XCKRJ",
80 "Windows 7 Enterprise E": "C29WB-22CC8-VJ326-GHFJW-H9DH4",
81 "Windows Server 2008 R2 Web": "6TPJF-RBVHG-WBW2R-86QPH-6RTM4",
82 "Windows Server 2008 R2 HPC edition": "TT8MH-CG224-D3D7Q-498W2-9QCTX",
83 "Windows Server 2008 R2 Standard": "YC6KT-GKW9T-YTKYR-T4X34-R7VHC",
84 "Windows Server 2008 R2 Enterprise": "489J6-VHDMP-X63PK-3K798-CPX3Y",
85 "Windows Server 2008 R2 Datacenter": "74YFP-3QFB3-KQT8W-PMXWJ-7M648",
86 "Windows Server 2008 R2 for Itanium-based Systems":
87 "GT63C-RJFQ3-4GMB6-BRFB9-CB83V",
88 "Windows Vista Business": "YFKBB-PQJJV-G996G-VWGXY-2V3X8",
89 "Windows Vista Business N": "HMBQG-8H2RH-C77VX-27R82-VMQBT",
90 "Windows Vista Enterprise": "VKK3X-68KWM-X2YGT-QR4M6-4BWMV",
91 "Windows Vista Enterprise N": "VTC42-BM838-43QHV-84HX6-XJXKV",
92 "Windows Web Server 2008": "WYR28-R7TFJ-3X2YQ-YCY4H-M249D",
93 "Windows Server 2008 Standard": "TM24T-X9RMF-VWXK6-X8JC9-BFGM2",
94 "Windows Server 2008 Standard without Hyper-V":
95 "W7VD6-7JFBR-RX26B-YKQ3Y-6FFFJ",
96 "Windows Server 2008 Enterprise":
97 "YQGMW-MPWTJ-34KDK-48M3W-X4Q6V",
98 "Windows Server 2008 Enterprise without Hyper-V":
99 "39BXF-X8Q23-P2WWT-38T2F-G3FPG",
100 "Windows Server 2008 HPC": "RCTX3-KWVHP-BR6TB-RB6DM-6X7HP",
101 "Windows Server 2008 Datacenter": "7M67G-PC374-GR742-YH8V4-TCBY3",
102 "Windows Server 2008 Datacenter without Hyper-V":
103 "22XQ2-VRXRG-P8D42-K34TD-G3QQC",
104 "Windows Server 2008 for Itanium-Based Systems":
105 "4DWFP-JF3DJ-B7DTH-78FJB-PDRHK"}
108 class Windows(OSBase):
109 """OS class for Windows"""
110 def __init__(self, image, **kargs):
111 super(Windows, self).__init__(image, **kargs)
113 device = self.g.part_to_dev(self.root)
115 self.last_part_num = self.g.part_list(device)[-1]['part_num']
116 self.last_drive = None
117 self.system_drive = None
119 for drive, partition in self.g.inspect_get_drive_mappings(self.root):
120 if partition == "%s%d" % (device, self.last_part_num):
121 self.last_drive = drive
122 if partition == self.root:
123 self.system_drive = drive
125 assert self.system_drive
127 self.product_name = self.g.inspect_get_product_name(self.root)
129 def needed_sysprep_params(self):
130 """Returns a list of needed sysprep parameters. Each element in the
131 list is a SysprepParam object.
133 password = self.SysprepParam(
134 'password', 'Image Administrator Password', 20, lambda x: True)
138 @sysprep('Disabling IPv6 privacy extensions')
139 def disable_ipv6_privacy_extensions(self):
140 """Disable IPv6 privacy extensions"""
142 self._guest_exec('netsh interface ipv6 set global '
143 'randomizeidentifiers=disabled store=persistent')
145 @sysprep('Disabling Teredo interface')
146 def disable_teredo(self):
147 """Disable Teredo interface"""
149 self._guest_exec('netsh interface teredo set state disabled')
151 @sysprep('Disabling ISATAP Adapters')
152 def disable_isatap(self):
153 """Disable ISATAP Adapters"""
155 self._guest_exec('netsh interface isa set state disabled')
157 @sysprep('Enabling ping responses')
158 def enable_pings(self):
159 """Enable ping responses"""
161 self._guest_exec('netsh firewall set icmpsetting 8')
163 @sysprep('Disabling hibernation support')
164 def disable_hibernation(self):
165 """Disable hibernation support and remove the hibernation file"""
167 self._guest_exec(r'powercfg.exe /hibernate off')
169 @sysprep('Setting the system clock to UTC')
171 """Set the hardware clock to UTC"""
173 path = r'HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
175 r'REG ADD %s /v RealTimeIsUniversal /t REG_DWORD /d 1 /f' % path)
177 @sysprep('Clearing the event logs')
178 def clear_logs(self):
179 """Clear all the event logs"""
182 r"cmd /q /c for /f %l in ('wevtutil el') do wevtutil cl %l")
184 @sysprep('Executing Sysprep on the image (may take more that 10 minutes)')
185 def microsoft_sysprep(self):
186 """Run the Microsoft System Preparation Tool. This will remove
187 system-specific data and will make the image ready to be deployed.
188 After this no other task may run.
191 self._guest_exec(r'C:\Windows\system32\sysprep\sysprep '
192 r'/quiet /generalize /oobe /shutdown')
193 self.syspreped = True
195 @sysprep('Converting the image into a KMS client', enabled=False)
196 def kms_client_setup(self):
197 """Install the appropriate KMS client setup key to the image to convert
198 it to a KMS client. Computers that are running volume licensing
199 editions of Windows 8, Windows Server 2012, Windows 7, Windows Server
200 2008 R2, Windows Vista, and Windows Server 2008 are, by default, KMS
201 clients with no additional configuration needed.
204 setup_key = KMS_CLIENT_SETUP_KEYS[self.product_name]
207 "Don't know the KMS client setup key for product: `%s'" %
212 "cscript \Windows\system32\slmgr.vbs /ipk %s" % setup_key)
214 @sysprep('Shrinking the last filesystem')
216 """Shrink the last filesystem. Make sure the filesystem is defragged"""
218 # Query for the maximum number of reclaimable bytes
220 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
221 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
222 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
223 r'ECHO SHRINK QUERYMAX >> %SCRIPT% & ' +
224 r'ECHO EXIT >> %SCRIPT% & ' +
225 r'DISKPART /S %SCRIPT% & ' +
226 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
229 stdout, stderr, rc = self._guest_exec(cmd)
232 for line in stdout.splitlines():
233 # diskpart will return something like this:
235 # The maximum number of reclaimable bytes is: xxxx MB
237 if line.find('reclaimable') >= 0:
238 querymax = line.split(':')[1].split()[0].strip()
239 assert querymax.isdigit(), \
240 "Number of reclaimable bytes not a number"
243 FatalError("Error in shrinking! "
244 "Couldn't find the max number of reclaimable bytes!")
246 querymax = int(querymax)
248 # Practically the smallest shrunken size generally is at around
249 # "used space" + (20-200 MB). Please also take into account that
250 # Windows might need about 50-100 MB free space left to boot safely.
251 # I'll give 100MB extra space just to be sure
255 self.out.warn("Not enought available space to shrink the image!")
258 self.out.output("\tReclaiming %dMB ..." % querymax)
261 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
262 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
263 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
264 'ECHO SHRINK DESIRED=%d >> %%SCRIPT%% & ' % querymax +
265 r'ECHO EXIT >> %SCRIPT% & ' +
266 r'DISKPART /S %SCRIPT% & ' +
267 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
270 stdout, stderr, rc = self._guest_exec(cmd)
272 for line in stdout.splitlines():
273 if line.find('shrunk') >= 0:
274 self.out.output(line)
276 def do_sysprep(self):
277 """Prepare system for image creation."""
279 if getattr(self, 'syspreped', False):
280 raise FatalError("Image is already syspreped!")
282 txt = "System preparation parameter: `%s' is needed but missing!"
283 for param in self.needed_sysprep_params():
284 if param[0] not in self.sysprep_params:
285 raise FatalError(txt % param[0])
287 self.mount(readonly=False)
289 disabled_uac = self._update_uac_remote_setting(1)
290 token = self._enable_os_monitor()
292 # disable the firewalls
293 firewall_states = self._update_firewalls(0, 0, 0)
295 # Delete the pagefile. It will be recreated when the system boots
296 systemroot = self.g.inspect_get_windows_systemroot(self.root)
297 pagefile = "%s/pagefile.sys" % systemroot
298 self.g.rm_rf(self.g.case_sensitive_path(pagefile))
303 self.out.output("Shutting down helper VM ...", False)
305 # guestfs_shutdown which is the prefered way to shutdown the backend
306 # process was introduced in version 1.19.16
307 if check_guestfs_version(self.g, 1, 19, 16) >= 0:
308 ret = self.g.shutdown()
310 ret = self.g.kill_subprocess()
312 self.out.success('done')
317 self.out.output("Starting windows VM ...", False)
318 monitorfd, monitor = tempfile.mkstemp()
320 vm = _VM(self.image.device, monitor)
321 self.out.success("started (console on vnc display: %d)." %
324 self.out.output("Waiting for OS to boot ...", False)
325 self._wait_vm_boot(vm, monitor, token)
326 self.out.success('done')
328 self.out.output("Checking connectivity to the VM ...", False)
329 self._check_connectivity()
330 self.out.success('done')
332 self.out.output("Disabling automatic logon ...", False)
333 self._disable_autologon()
334 self.out.success('done')
336 self.out.output('Preparing system for image creation:')
338 tasks = self.list_syspreps()
339 enabled = filter(lambda x: x.enabled, tasks)
342 # Make sure shrink runs in the end, before ms sysprep
343 enabled = filter(lambda x: self.sysprep_info(x).name != 'shrink',
346 shrink_enabled = False
347 if len(enabled) != size:
348 enabled.append(self.shrink)
349 shrink_enabled = True
351 # Make sure the ms sysprep is the last task to run if it is enabled
353 lambda x: self.sysprep_info(x).name != 'microsoft-sysprep',
356 ms_sysprep_enabled = False
357 if len(enabled) != size:
358 enabled.append(self.microsoft_sysprep)
359 ms_sysprep_enabled = True
364 self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False)
366 setattr(task.im_func, 'executed', True)
368 self.out.output("Sending shut down command ...", False)
369 if not ms_sysprep_enabled:
371 self.out.success("done")
373 self.out.output("Waiting for windows to shut down ...", False)
374 vm.wait(SHUTDOWN_TIMEOUT)
375 self.out.success("done")
377 if monitor is not None:
382 self.out.output("Destroying windows VM ...", False)
384 self.out.success("done")
386 self.out.output("Relaunching helper VM (may take a while) ...",
389 self.out.success('done')
391 self.mount(readonly=False)
394 self._update_uac_remote_setting(0)
396 self._update_firewalls(*firewall_states)
401 """Shuts down the windows VM"""
402 self._guest_exec(r'shutdown /s /t 5')
404 def _wait_vm_boot(self, vm, fname, msg):
405 """Wait until a message appears on a file or the vm process dies"""
407 for i in range(BOOT_TIMEOUT):
409 with open(fname) as f:
411 if line.startswith(msg):
414 raise FatalError("Windows VM died unexpectedly!")
416 raise FatalError("Windows VM booting timed out!")
418 def _disable_autologon(self):
419 """Disable automatic logon on the windows image"""
422 r'"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"'
424 self._guest_exec('REG DELETE %s /v DefaultUserName /f' % winlogon)
425 self._guest_exec('REG DELETE %s /v DefaultPassword /f' % winlogon)
426 self._guest_exec('REG DELETE %s /v AutoAdminLogon /f' % winlogon)
428 def _registry_file_path(self, regfile):
429 """Retrieves the case sensitive path to a registry file"""
431 systemroot = self.g.inspect_get_windows_systemroot(self.root)
432 path = "%s/system32/config/%s" % (systemroot, regfile)
434 path = self.g.case_sensitive_path(path)
435 except RuntimeError as e:
436 raise FatalError("Unable to retrieve registry file: %s. Reason: %s"
440 def _enable_os_monitor(self):
441 """Add a script in the registry that will send a random string to the
442 first serial port when the windows image finishes booting.
445 token = "".join(random.choice(string.ascii_letters) for x in range(16))
447 path = self._registry_file_path('SOFTWARE')
448 softwarefd, software = tempfile.mkstemp()
451 self.g.download(path, software)
453 h = hivex.Hivex(software, write=True)
455 # Enable automatic logon.
456 # This is needed because we need to execute a script that we add in
457 # the RunOnce registry entry and those programs only get executed
458 # when a user logs on. There is a RunServicesOnce registry entry
459 # whose keys get executed in the background when the logon dialog
460 # box first appears, but they seem to only work with services and
461 # not arbitrary command line expressions :-(
463 # Instructions on how to turn on automatic logon in Windows can be
464 # found here: http://support.microsoft.com/kb/324737
466 # Warning: Registry change will not work if the “Logon Banner” is
467 # defined on the server either by a Group Policy object (GPO) or by
471 for child in ('Microsoft', 'Windows NT', 'CurrentVersion',
473 winlogon = h.node_get_child(winlogon, child)
477 {'key': 'DefaultUserName', 't': 1,
478 'value': "Administrator".encode('utf-16le')})
481 {'key': 'DefaultPassword', 't': 1,
482 'value': self.sysprep_params['password'].encode('utf-16le')})
485 {'key': 'AutoAdminLogon', 't': 1,
486 'value': "1".encode('utf-16le')})
489 for child in ('Microsoft', 'Windows', 'CurrentVersion'):
490 key = h.node_get_child(key, child)
492 runonce = h.node_get_child(key, "RunOnce")
494 runonce = h.node_add_child(key, "RunOnce")
497 r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe '
498 r'-ExecutionPolicy RemoteSigned '
499 r'"&{$port=new-Object System.IO.Ports.SerialPort COM1,9600,'
500 r'None,8,one;$port.open();$port.WriteLine(\"' + token + r'\");'
501 r'$port.Close()}"').encode('utf-16le')
503 h.node_set_value(runonce,
504 {'key': "BootMonitor", 't': 1, 'value': value})
507 r'REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion'
508 r'\policies\system /v LocalAccountTokenFilterPolicy'
509 r' /t REG_DWORD /d 1 /f').encode('utf-16le')
511 h.node_set_value(runonce,
512 {'key': "UpdateRegistry", 't': 1, 'value': value})
516 self.g.upload(software, path)
522 def _update_firewalls(self, domain, public, standard):
523 """Enables or disables the firewall for the Domain, the Public and the
524 Standard profile. Returns a triplete with the old values.
526 1 will enable a firewall and 0 will disable it
529 if domain not in (0, 1):
530 raise ValueError("Valid values for domain parameter are 0 and 1")
532 if public not in (0, 1):
533 raise ValueError("Valid values for public parameter are 0 and 1")
535 if standard not in (0, 1):
536 raise ValueError("Valid values for standard parameter are 0 and 1")
538 path = self._registry_file_path("SYSTEM")
539 systemfd, system = tempfile.mkstemp()
542 self.g.download(path, system)
544 h = hivex.Hivex(system, write=True)
546 select = h.node_get_child(h.root(), 'Select')
547 current_value = h.node_get_value(select, 'Current')
549 # expecting a little endian dword
550 assert h.value_type(current_value)[1] == 4
551 current = "%03d" % h.value_dword(current_value)
553 firewall_policy = h.root()
554 for child in ('ControlSet%s' % current, 'services', 'SharedAccess',
555 'Parameters', 'FirewallPolicy'):
556 firewall_policy = h.node_get_child(firewall_policy, child)
559 new_values = [domain, public, standard]
560 for profile in ('Domain', 'Public', 'Standard'):
561 node = h.node_get_child(firewall_policy, '%sProfile' % profile)
563 old_value = h.node_get_value(node, 'EnableFirewall')
565 # expecting a little endian dword
566 assert h.value_type(old_value)[1] == 4
567 old_values.append(h.value_dword(old_value))
570 node, {'key': 'EnableFirewall', 't': 4L,
571 'value': struct.pack("<I", new_values.pop(0))})
574 self.g.upload(system, path)
581 def _update_uac_remote_setting(self, value):
582 """Updates the registry key value:
583 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
584 \System]"LocalAccountTokenFilterPolicy"
586 value = 1 will disable the UAC remote restrictions
587 value = 0 will enable the UAC remote restrictions
589 For more info see here: http://support.microsoft.com/kb/951016
592 True if the key is changed
593 False if the key is unchanged
596 if value not in (0, 1):
597 raise ValueError("Valid values for value parameter are 0 and 1")
599 path = self._registry_file_path('SOFTWARE')
600 softwarefd, software = tempfile.mkstemp()
603 self.g.download(path, software)
605 h = hivex.Hivex(software, write=True)
608 for child in ('Microsoft', 'Windows', 'CurrentVersion', 'Policies',
610 key = h.node_get_child(key, child)
613 for val in h.node_values(key):
614 if h.value_key(val) == "LocalAccountTokenFilterPolicy":
617 if policy is not None:
618 dword = h.value_dword(policy)
624 new_value = {'key': "LocalAccountTokenFilterPolicy", 't': 4L,
625 'value': struct.pack("<I", value)}
627 h.node_set_value(key, new_value)
630 self.g.upload(software, path)
637 def _do_collect_metadata(self):
638 """Collect metadata about the OS"""
639 super(Windows, self)._do_collect_metadata()
640 self.meta["USERS"] = " ".join(self._get_users())
642 def _get_users(self):
643 """Returns a list of users found in the images"""
644 path = self._registry_file_path('SAM')
645 samfd, sam = tempfile.mkstemp()
648 self.g.download(path, sam)
652 # Navigate to /SAM/Domains/Account/Users
653 users_node = h.root()
654 for child in ('SAM', 'Domains', 'Account', 'Users'):
655 users_node = h.node_get_child(users_node, child)
657 # Navigate to /SAM/Domains/Account/Users/Names
658 names_node = h.node_get_child(users_node, 'Names')
660 # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\%RID%
661 # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\Names\%Username%
663 # The RID (relative identifier) of each user is stored as the type!
664 # (not the value) of the default key of the node under Names whose
665 # name is the user's username. Under the RID node, there in a F
666 # value that contains information about this user account.
668 # See sam.h of the chntpw project on how to translate the F value
669 # of an account in the registry. Bytes 56 & 57 are the account type
670 # and status flags. The first bit is the 'account disabled' bit
671 disabled = lambda f: int(f[56].encode('hex'), 16) & 0x01
674 for user_node in h.node_children(names_node):
675 username = h.node_name(user_node)
676 rid = h.value_type(h.node_get_value(user_node, ""))[0]
677 # if RID is 500 (=0x1f4), the corresponding node name under
678 # Users is '000001F4'
679 key = ("%8.x" % rid).replace(' ', '0').upper()
680 rid_node = h.node_get_child(users_node, key)
681 f_value = h.value_value(h.node_get_value(rid_node, 'F'))[1]
683 if disabled(f_value):
684 self.out.warn("Found disabled `%s' account!" % username)
687 users.append(username)
692 # Filter out the guest account
695 def _check_connectivity(self):
696 """Check if winexe works on the Windows VM"""
698 passwd = self.sysprep_params['password']
699 winexe = WinEXE('Administrator', passwd, 'localhost')
700 winexe.uninstall().debug(9)
702 for i in range(CONNECTION_RETRIES):
703 (stdout, stderr, rc) = winexe.run('cmd /C')
706 log = tempfile.NamedTemporaryFile(delete=False)
708 log.file.write(stdout)
711 self.out.output("failed! See: `%' for the full output" % log.name)
712 if i < CONNECTION_RETRIES - 1:
713 self.out.output("Retrying ...", False)
714 raise FatalError("Connection to the 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):
743 """Create _VM instance
746 serial: File to save the output of the serial port
753 mac = [0x00, 0x16, 0x3e,
754 random.randint(0x00, 0x7f),
755 random.randint(0x00, 0xff),
756 random.randint(0x00, 0xff)]
758 return ':'.join(map(lambda x: "%02x" % x, mac))
760 # Use ganeti's VNC port range for a random vnc port
761 self.display = random.randint(11000, 14999) - 5900
764 'kvm', '-smp', '1', '-m', '1024', '-drive',
765 'file=%s,format=raw,cache=unsafe,if=virtio' % self.disk,
766 '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
767 '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(),
768 '-vnc', ':%d' % self.display, '-serial', 'file:%s' % self.serial,
771 self.process = subprocess.Popen(args, stdin=subprocess.PIPE,
772 stdout=subprocess.PIPE)
775 """Check if the VM is still alive"""
776 return self.process.poll() is None
781 if not self.isalive():
784 def handler(signum, frame):
785 self.process.terminate()
790 self.out.output("timed-out")
791 raise FatalError("VM destroy timed-out")
793 signal.signal(signal.SIGALRM, handler)
795 signal.alarm(SHUTDOWN_TIMEOUT)
796 self.process.communicate(input="system_powerdown\n")
799 def wait(self, timeout=0):
800 """Wait for the VM to terminate"""
802 def handler(signum, frame):
804 raise FatalError("VM wait timed-out.")
806 signal.signal(signal.SIGALRM, handler)
808 signal.alarm(timeout)
809 stdout, stderr = self.process.communicate()
812 return (stdout, stderr, self.process.poll())
814 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :