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, 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"""
111 @add_sysprep_param('password', 'Image Administrator Password', 20)
112 def __init__(self, image, **kargs):
113 super(Windows, self).__init__(image, **kargs)
115 device = self.g.part_to_dev(self.root)
117 self.last_part_num = self.g.part_list(device)[-1]['part_num']
118 self.last_drive = None
119 self.system_drive = None
121 for drive, partition in self.g.inspect_get_drive_mappings(self.root):
122 if partition == "%s%d" % (device, self.last_part_num):
123 self.last_drive = drive
124 if partition == self.root:
125 self.system_drive = drive
127 assert self.system_drive
129 self.product_name = self.g.inspect_get_product_name(self.root)
131 @sysprep('Disabling IPv6 privacy extensions')
132 def disable_ipv6_privacy_extensions(self):
133 """Disable IPv6 privacy extensions"""
135 self._guest_exec('netsh interface ipv6 set global '
136 'randomizeidentifiers=disabled store=persistent')
138 @sysprep('Disabling Teredo interface')
139 def disable_teredo(self):
140 """Disable Teredo interface"""
142 self._guest_exec('netsh interface teredo set state disabled')
144 @sysprep('Disabling ISATAP Adapters')
145 def disable_isatap(self):
146 """Disable ISATAP Adapters"""
148 self._guest_exec('netsh interface isa set state disabled')
150 @sysprep('Enabling ping responses')
151 def enable_pings(self):
152 """Enable ping responses"""
154 self._guest_exec('netsh firewall set icmpsetting 8')
156 @sysprep('Disabling hibernation support')
157 def disable_hibernation(self):
158 """Disable hibernation support and remove the hibernation file"""
160 self._guest_exec(r'powercfg.exe /hibernate off')
162 @sysprep('Setting the system clock to UTC')
164 """Set the hardware clock to UTC"""
166 path = r'HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
168 r'REG ADD %s /v RealTimeIsUniversal /t REG_DWORD /d 1 /f' % path)
170 @sysprep('Clearing the event logs')
171 def clear_logs(self):
172 """Clear all the event logs"""
175 r"cmd /q /c for /f %l in ('wevtutil el') do wevtutil cl %l")
177 @sysprep('Executing Sysprep on the image (may take more that 10 minutes)')
178 def microsoft_sysprep(self):
179 """Run the Microsoft System Preparation Tool. This will remove
180 system-specific data and will make the image ready to be deployed.
181 After this no other task may run.
184 self._guest_exec(r'C:\Windows\system32\sysprep\sysprep '
185 r'/quiet /generalize /oobe /shutdown')
186 self.syspreped = True
188 @sysprep('Converting the image into a KMS client', enabled=False)
189 def kms_client_setup(self):
190 """Install the appropriate KMS client setup key to the image to convert
191 it to a KMS client. Computers that are running volume licensing
192 editions of Windows 8, Windows Server 2012, Windows 7, Windows Server
193 2008 R2, Windows Vista, and Windows Server 2008 are, by default, KMS
194 clients with no additional configuration needed.
197 setup_key = KMS_CLIENT_SETUP_KEYS[self.product_name]
200 "Don't know the KMS client setup key for product: `%s'" %
205 "cscript \Windows\system32\slmgr.vbs /ipk %s" % setup_key)
207 @sysprep('Shrinking the last filesystem')
209 """Shrink the last filesystem. Make sure the filesystem is defragged"""
211 # Query for the maximum number of reclaimable bytes
213 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
214 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
215 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
216 r'ECHO SHRINK QUERYMAX >> %SCRIPT% & ' +
217 r'ECHO EXIT >> %SCRIPT% & ' +
218 r'DISKPART /S %SCRIPT% & ' +
219 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
222 stdout, stderr, rc = self._guest_exec(cmd)
225 for line in stdout.splitlines():
226 # diskpart will return something like this:
228 # The maximum number of reclaimable bytes is: xxxx MB
230 if line.find('reclaimable') >= 0:
231 querymax = line.split(':')[1].split()[0].strip()
232 assert querymax.isdigit(), \
233 "Number of reclaimable bytes not a number"
236 FatalError("Error in shrinking! "
237 "Couldn't find the max number of reclaimable bytes!")
239 querymax = int(querymax)
241 # Practically the smallest shrunken size generally is at around
242 # "used space" + (20-200 MB). Please also take into account that
243 # Windows might need about 50-100 MB free space left to boot safely.
244 # I'll give 100MB extra space just to be sure
248 self.out.warn("Not enought available space to shrink the image!")
251 self.out.output("\tReclaiming %dMB ..." % querymax)
254 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
255 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
256 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
257 'ECHO SHRINK DESIRED=%d >> %%SCRIPT%% & ' % querymax +
258 r'ECHO EXIT >> %SCRIPT% & ' +
259 r'DISKPART /S %SCRIPT% & ' +
260 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
263 stdout, stderr, rc = self._guest_exec(cmd)
265 for line in stdout.splitlines():
266 if line.find('shrunk') >= 0:
267 self.out.output(line)
269 def do_sysprep(self):
270 """Prepare system for image creation."""
272 if getattr(self, 'syspreped', False):
273 raise FatalError("Image is already syspreped!")
275 txt = "System preparation parameter: `%s' is needed but missing!"
276 for param in self.needed_sysprep_params:
277 if param not in self.sysprep_params:
278 raise FatalError(txt % param)
280 self.mount(readonly=False)
282 disabled_uac = self._update_uac_remote_setting(1)
283 token = self._enable_os_monitor()
285 # disable the firewalls
286 firewall_states = self._update_firewalls(0, 0, 0)
288 # Delete the pagefile. It will be recreated when the system boots
289 systemroot = self.g.inspect_get_windows_systemroot(self.root)
290 pagefile = "%s/pagefile.sys" % systemroot
291 self.g.rm_rf(self.g.case_sensitive_path(pagefile))
296 self.out.output("Shutting down helper VM ...", False)
298 # guestfs_shutdown which is the prefered way to shutdown the backend
299 # process was introduced in version 1.19.16
300 if check_guestfs_version(self.g, 1, 19, 16) >= 0:
301 ret = self.g.shutdown()
303 ret = self.g.kill_subprocess()
305 self.out.success('done')
310 self.out.output("Starting windows VM ...", False)
311 monitorfd, monitor = tempfile.mkstemp()
313 vm = _VM(self.image.device, monitor)
314 self.out.success("started (console on vnc display: %d)." %
317 self.out.output("Waiting for OS to boot ...", False)
318 self._wait_vm_boot(vm, monitor, token)
319 self.out.success('done')
321 self.out.output("Checking connectivity to the VM ...", False)
322 self._check_connectivity()
323 self.out.success('done')
325 self.out.output("Disabling automatic logon ...", False)
326 self._disable_autologon()
327 self.out.success('done')
329 self.out.output('Preparing system for image creation:')
331 tasks = self.list_syspreps()
332 enabled = filter(lambda x: x.enabled, tasks)
335 # Make sure shrink runs in the end, before ms sysprep
336 enabled = filter(lambda x: self.sysprep_info(x).name != 'shrink',
339 shrink_enabled = False
340 if len(enabled) != size:
341 enabled.append(self.shrink)
342 shrink_enabled = True
344 # Make sure the ms sysprep is the last task to run if it is enabled
346 lambda x: self.sysprep_info(x).name != 'microsoft-sysprep',
349 ms_sysprep_enabled = False
350 if len(enabled) != size:
351 enabled.append(self.microsoft_sysprep)
352 ms_sysprep_enabled = True
357 self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False)
359 setattr(task.im_func, 'executed', True)
361 self.out.output("Sending shut down command ...", False)
362 if not ms_sysprep_enabled:
364 self.out.success("done")
366 self.out.output("Waiting for windows to shut down ...", False)
367 vm.wait(SHUTDOWN_TIMEOUT)
368 self.out.success("done")
370 if monitor is not None:
375 self.out.output("Destroying windows VM ...", False)
377 self.out.success("done")
379 self.out.output("Relaunching helper VM (may take a while) ...",
382 self.out.success('done')
384 self.mount(readonly=False)
387 self._update_uac_remote_setting(0)
389 self._update_firewalls(*firewall_states)
394 """Shuts down the windows VM"""
395 self._guest_exec(r'shutdown /s /t 5')
397 def _wait_vm_boot(self, vm, fname, msg):
398 """Wait until a message appears on a file or the vm process dies"""
400 for i in range(BOOT_TIMEOUT):
402 with open(fname) as f:
404 if line.startswith(msg):
407 raise FatalError("Windows VM died unexpectedly!")
409 raise FatalError("Windows VM booting timed out!")
411 def _disable_autologon(self):
412 """Disable automatic logon on the windows image"""
415 r'"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"'
417 self._guest_exec('REG DELETE %s /v DefaultUserName /f' % winlogon)
418 self._guest_exec('REG DELETE %s /v DefaultPassword /f' % winlogon)
419 self._guest_exec('REG DELETE %s /v AutoAdminLogon /f' % winlogon)
421 def _registry_file_path(self, regfile):
422 """Retrieves the case sensitive path to a registry file"""
424 systemroot = self.g.inspect_get_windows_systemroot(self.root)
425 path = "%s/system32/config/%s" % (systemroot, regfile)
427 path = self.g.case_sensitive_path(path)
428 except RuntimeError as e:
429 raise FatalError("Unable to retrieve registry file: %s. Reason: %s"
433 def _enable_os_monitor(self):
434 """Add a script in the registry that will send a random string to the
435 first serial port when the windows image finishes booting.
438 token = "".join(random.choice(string.ascii_letters) for x in range(16))
440 path = self._registry_file_path('SOFTWARE')
441 softwarefd, software = tempfile.mkstemp()
444 self.g.download(path, software)
446 h = hivex.Hivex(software, write=True)
448 # Enable automatic logon.
449 # This is needed because we need to execute a script that we add in
450 # the RunOnce registry entry and those programs only get executed
451 # when a user logs on. There is a RunServicesOnce registry entry
452 # whose keys get executed in the background when the logon dialog
453 # box first appears, but they seem to only work with services and
454 # not arbitrary command line expressions :-(
456 # Instructions on how to turn on automatic logon in Windows can be
457 # found here: http://support.microsoft.com/kb/324737
459 # Warning: Registry change will not work if the “Logon Banner” is
460 # defined on the server either by a Group Policy object (GPO) or by
464 for child in ('Microsoft', 'Windows NT', 'CurrentVersion',
466 winlogon = h.node_get_child(winlogon, child)
470 {'key': 'DefaultUserName', 't': 1,
471 'value': "Administrator".encode('utf-16le')})
474 {'key': 'DefaultPassword', 't': 1,
475 'value': self.sysprep_params['password'].encode('utf-16le')})
478 {'key': 'AutoAdminLogon', 't': 1,
479 'value': "1".encode('utf-16le')})
482 for child in ('Microsoft', 'Windows', 'CurrentVersion'):
483 key = h.node_get_child(key, child)
485 runonce = h.node_get_child(key, "RunOnce")
487 runonce = h.node_add_child(key, "RunOnce")
490 r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe '
491 r'-ExecutionPolicy RemoteSigned '
492 r'"&{$port=new-Object System.IO.Ports.SerialPort COM1,9600,'
493 r'None,8,one;$port.open();$port.WriteLine(\"' + token + r'\");'
494 r'$port.Close()}"').encode('utf-16le')
496 h.node_set_value(runonce,
497 {'key': "BootMonitor", 't': 1, 'value': value})
500 r'REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion'
501 r'\policies\system /v LocalAccountTokenFilterPolicy'
502 r' /t REG_DWORD /d 1 /f').encode('utf-16le')
504 h.node_set_value(runonce,
505 {'key': "UpdateRegistry", 't': 1, 'value': value})
509 self.g.upload(software, path)
515 def _update_firewalls(self, domain, public, standard):
516 """Enables or disables the firewall for the Domain, the Public and the
517 Standard profile. Returns a triplete with the old values.
519 1 will enable a firewall and 0 will disable it
522 if domain not in (0, 1):
523 raise ValueError("Valid values for domain parameter are 0 and 1")
525 if public not in (0, 1):
526 raise ValueError("Valid values for public parameter are 0 and 1")
528 if standard not in (0, 1):
529 raise ValueError("Valid values for standard parameter are 0 and 1")
531 path = self._registry_file_path("SYSTEM")
532 systemfd, system = tempfile.mkstemp()
535 self.g.download(path, system)
537 h = hivex.Hivex(system, write=True)
539 select = h.node_get_child(h.root(), 'Select')
540 current_value = h.node_get_value(select, 'Current')
542 # expecting a little endian dword
543 assert h.value_type(current_value)[1] == 4
544 current = "%03d" % h.value_dword(current_value)
546 firewall_policy = h.root()
547 for child in ('ControlSet%s' % current, 'services', 'SharedAccess',
548 'Parameters', 'FirewallPolicy'):
549 firewall_policy = h.node_get_child(firewall_policy, child)
552 new_values = [domain, public, standard]
553 for profile in ('Domain', 'Public', 'Standard'):
554 node = h.node_get_child(firewall_policy, '%sProfile' % profile)
556 old_value = h.node_get_value(node, 'EnableFirewall')
558 # expecting a little endian dword
559 assert h.value_type(old_value)[1] == 4
560 old_values.append(h.value_dword(old_value))
563 node, {'key': 'EnableFirewall', 't': 4L,
564 'value': struct.pack("<I", new_values.pop(0))})
567 self.g.upload(system, path)
574 def _update_uac_remote_setting(self, value):
575 """Updates the registry key value:
576 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
577 \System]"LocalAccountTokenFilterPolicy"
579 value = 1 will disable the UAC remote restrictions
580 value = 0 will enable the UAC remote restrictions
582 For more info see here: http://support.microsoft.com/kb/951016
585 True if the key is changed
586 False if the key is unchanged
589 if value not in (0, 1):
590 raise ValueError("Valid values for value parameter are 0 and 1")
592 path = self._registry_file_path('SOFTWARE')
593 softwarefd, software = tempfile.mkstemp()
596 self.g.download(path, software)
598 h = hivex.Hivex(software, write=True)
601 for child in ('Microsoft', 'Windows', 'CurrentVersion', 'Policies',
603 key = h.node_get_child(key, child)
606 for val in h.node_values(key):
607 if h.value_key(val) == "LocalAccountTokenFilterPolicy":
610 if policy is not None:
611 dword = h.value_dword(policy)
617 new_value = {'key': "LocalAccountTokenFilterPolicy", 't': 4L,
618 'value': struct.pack("<I", value)}
620 h.node_set_value(key, new_value)
623 self.g.upload(software, path)
630 def _do_collect_metadata(self):
631 """Collect metadata about the OS"""
632 super(Windows, self)._do_collect_metadata()
633 self.meta["USERS"] = " ".join(self._get_users())
635 def _get_users(self):
636 """Returns a list of users found in the images"""
637 path = self._registry_file_path('SAM')
638 samfd, sam = tempfile.mkstemp()
641 self.g.download(path, 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 passwd = self.sysprep_params['password']
692 winexe = WinEXE('Administrator', passwd, 'localhost')
693 winexe.uninstall().debug(9)
695 for i in range(CONNECTION_RETRIES):
696 (stdout, stderr, rc) = winexe.run('cmd /C')
699 log = tempfile.NamedTemporaryFile(delete=False)
701 log.file.write(stdout)
704 self.out.output("failed! See: `%' for the full output" % log.name)
705 if i < CONNECTION_RETRIES - 1:
706 self.out.output("Retrying ...", False)
707 raise FatalError("Connection to the VM failed after %d retries" %
710 def _guest_exec(self, command, fatal=True):
711 """Execute a command on a windows VM"""
713 passwd = self.sysprep_params['password']
715 winexe = WinEXE('Administrator', passwd, 'localhost')
716 winexe.runas('Administrator', passwd).uninstall()
719 (stdout, stderr, rc) = winexe.run(command)
720 except WinexeTimeout:
721 FatalError("Command: `%s' timeout out." % command)
723 if rc != 0 and fatal:
724 reason = stderr if len(stderr) else stdout
725 self.out.output("Command: `%s' failed (rc=%d). Reason: %s" %
726 (command, rc, reason))
727 raise FatalError("Command: `%s' failed (rc=%d). Reason: %s" %
728 (command, rc, reason))
730 return (stdout, stderr, rc)
734 """Windows Virtual Machine"""
735 def __init__(self, disk, serial):
736 """Create _VM instance
739 serial: File to save the output of the serial port
746 mac = [0x00, 0x16, 0x3e,
747 random.randint(0x00, 0x7f),
748 random.randint(0x00, 0xff),
749 random.randint(0x00, 0xff)]
751 return ':'.join(map(lambda x: "%02x" % x, mac))
753 # Use ganeti's VNC port range for a random vnc port
754 self.display = random.randint(11000, 14999) - 5900
757 'kvm', '-smp', '1', '-m', '1024', '-drive',
758 'file=%s,format=raw,cache=unsafe,if=virtio' % self.disk,
759 '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
760 '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(),
761 '-vnc', ':%d' % self.display, '-serial', 'file:%s' % self.serial,
764 self.process = subprocess.Popen(args, stdin=subprocess.PIPE,
765 stdout=subprocess.PIPE)
768 """Check if the VM is still alive"""
769 return self.process.poll() is None
774 if not self.isalive():
777 def handler(signum, frame):
778 self.process.terminate()
783 self.out.output("timed-out")
784 raise FatalError("VM destroy timed-out")
786 signal.signal(signal.SIGALRM, handler)
788 signal.alarm(SHUTDOWN_TIMEOUT)
789 self.process.communicate(input="system_powerdown\n")
792 def wait(self, timeout=0):
793 """Wait for the VM to terminate"""
795 def handler(signum, frame):
797 raise FatalError("VM wait timed-out.")
799 signal.signal(signal.SIGALRM, handler)
801 signal.alarm(timeout)
802 stdout, stderr = self.process.communicate()
805 return (stdout, stderr, self.process.poll())
807 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :