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:
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:
130 'For windows support libguestfs 1.17.18 or above is required')
132 device = self.image.g.part_to_dev(self.root)
134 self.last_part_num = self.image.g.part_list(device)[-1]['part_num']
135 self.last_drive = None
136 self.system_drive = None
138 for drive, part in self.image.g.inspect_get_drive_mappings(self.root):
139 if part == "%s%d" % (device, self.last_part_num):
140 self.last_drive = drive
141 if part == self.root:
142 self.system_drive = drive
144 assert self.system_drive
146 self.product_name = self.image.g.inspect_get_product_name(self.root)
147 self.syspreped = False
149 @sysprep('Disabling IPv6 privacy extensions')
150 def disable_ipv6_privacy_extensions(self):
151 """Disable IPv6 privacy extensions"""
153 self._guest_exec('netsh interface ipv6 set global '
154 'randomizeidentifiers=disabled store=persistent')
156 @sysprep('Disabling Teredo interface')
157 def disable_teredo(self):
158 """Disable Teredo interface"""
160 self._guest_exec('netsh interface teredo set state disabled')
162 @sysprep('Disabling ISATAP Adapters')
163 def disable_isatap(self):
164 """Disable ISATAP Adapters"""
166 self._guest_exec('netsh interface isa set state disabled')
168 @sysprep('Enabling ping responses')
169 def enable_pings(self):
170 """Enable ping responses"""
172 self._guest_exec('netsh firewall set icmpsetting 8')
174 @sysprep('Disabling hibernation support')
175 def disable_hibernation(self):
176 """Disable hibernation support and remove the hibernation file"""
178 self._guest_exec(r'powercfg.exe /hibernate off')
180 @sysprep('Setting the system clock to UTC')
182 """Set the hardware clock to UTC"""
184 path = r'HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
186 r'REG ADD %s /v RealTimeIsUniversal /t REG_DWORD /d 1 /f' % path)
188 @sysprep('Clearing the event logs')
189 def clear_logs(self):
190 """Clear all the event logs"""
193 r"cmd /q /c for /f %l in ('wevtutil el') do wevtutil cl %l")
195 @sysprep('Executing Sysprep on the image (may take more that 10 minutes)')
196 def microsoft_sysprep(self):
197 """Run the Microsoft System Preparation Tool. This will remove
198 system-specific data and will make the image ready to be deployed.
199 After this no other task may run.
202 self._guest_exec(r'C:\Windows\system32\sysprep\sysprep '
203 r'/quiet /generalize /oobe /shutdown')
204 self.syspreped = True
206 @sysprep('Converting the image into a KMS client', enabled=False)
207 def kms_client_setup(self):
208 """Install the appropriate KMS client setup key to the image to convert
209 it to a KMS client. Computers that are running volume licensing
210 editions of Windows 8, Windows Server 2012, Windows 7, Windows Server
211 2008 R2, Windows Vista, and Windows Server 2008 are, by default, KMS
212 clients with no additional configuration needed.
215 setup_key = KMS_CLIENT_SETUP_KEYS[self.product_name]
218 "Don't know the KMS client setup key for product: `%s'" %
223 r"cscript \Windows\system32\slmgr.vbs /ipk %s" % setup_key)
225 @sysprep('Shrinking the last filesystem')
227 """Shrink the last filesystem. Make sure the filesystem is defragged"""
229 # Query for the maximum number of reclaimable bytes
231 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
232 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
233 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
234 r'ECHO SHRINK QUERYMAX >> %SCRIPT% & ' +
235 r'ECHO EXIT >> %SCRIPT% & ' +
236 r'DISKPART /S %SCRIPT% & ' +
237 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
240 stdout, stderr, rc = self._guest_exec(cmd)
243 for line in stdout.splitlines():
244 # diskpart will return something like this:
246 # The maximum number of reclaimable bytes is: xxxx MB
248 if line.find('reclaimable') >= 0:
249 querymax = line.split(':')[1].split()[0].strip()
250 assert querymax.isdigit(), \
251 "Number of reclaimable bytes not a number"
254 FatalError("Error in shrinking! "
255 "Couldn't find the max number of reclaimable bytes!")
257 querymax = int(querymax)
259 # Practically the smallest shrunken size generally is at around
260 # "used space" + (20-200 MB). Please also take into account that
261 # Windows might need about 50-100 MB free space left to boot safely.
262 # I'll give 100MB extra space just to be sure
266 self.out.warn("Not enought available space to shrink the image!")
269 self.out.output("\tReclaiming %dMB ..." % querymax)
272 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
273 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
274 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
275 'ECHO SHRINK DESIRED=%d >> %%SCRIPT%% & ' % querymax +
276 r'ECHO EXIT >> %SCRIPT% & ' +
277 r'DISKPART /S %SCRIPT% & ' +
278 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
281 stdout, stderr, rc = self._guest_exec(cmd, False)
284 FatalError("Shrinking failed. Please make sure the media is "
285 "defraged with a command like this: "
286 "`Defrag.exe /U /X /W'")
287 for line in stdout.splitlines():
288 if line.find('shrunk') >= 0:
289 self.out.output(line)
291 def do_sysprep(self):
292 """Prepare system for image creation."""
294 if getattr(self, 'syspreped', False):
295 raise FatalError("Image is already syspreped!")
297 txt = "System preparation parameter: `%s' is needed but missing!"
298 for name, param in self.needed_sysprep_params.items():
299 if name not in self.sysprep_params:
300 raise FatalError(txt % name)
302 self.mount(readonly=False)
304 disabled_uac = self._update_uac_remote_setting(1)
305 token = self._enable_os_monitor()
307 # disable the firewalls
308 firewall_states = self._update_firewalls(0, 0, 0)
310 # Delete the pagefile. It will be recreated when the system boots
311 systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
313 pagefile = "%s/pagefile.sys" % systemroot
314 self.image.g.rm_rf(self.image.g.case_sensitive_path(pagefile))
321 self.image.disable_guestfs()
326 self.out.output("Starting windows VM ...", False)
327 monitorfd, monitor = tempfile.mkstemp()
329 vm = _VM(self.image.device, monitor, self.sysprep_params)
330 self.out.success("started (console on vnc display: %d)." %
333 self.out.output("Waiting for OS to boot ...", False)
334 self._wait_vm_boot(vm, monitor, token)
335 self.out.success('done')
337 self.out.output("Checking connectivity to the VM ...", False)
338 self._check_connectivity()
339 self.out.success('done')
341 self.out.output("Disabling automatic logon ...", False)
342 self._disable_autologon()
343 self.out.success('done')
345 self.out.output('Preparing system for image creation:')
347 tasks = self.list_syspreps()
348 enabled = [task for task in tasks if task.enabled]
351 # Make sure shrink runs in the end, before ms sysprep
352 enabled = [task for task in enabled if
353 self.sysprep_info(task).name != 'shrink']
355 if len(enabled) != size:
356 enabled.append(self.shrink)
358 # Make sure the ms sysprep is the last task to run if it is enabled
359 enabled = [task for task in enabled if
360 self.sysprep_info(task).name != 'microsoft-sysprep']
362 ms_sysprep_enabled = False
363 if len(enabled) != size:
364 enabled.append(self.microsoft_sysprep)
365 ms_sysprep_enabled = True
370 self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False)
372 setattr(task.im_func, 'executed', True)
374 self.out.output("Sending shut down command ...", False)
375 if not ms_sysprep_enabled:
377 self.out.success("done")
379 self.out.output("Waiting for windows to shut down ...", False)
380 vm.wait(self.sysprep_params['shutdown_timeout'])
381 self.out.success("done")
383 if monitor is not None:
388 self.out.output("Destroying windows VM ...", False)
390 self.out.success("done")
392 self.image.enable_guestfs()
394 self.mount(readonly=False)
397 self._update_uac_remote_setting(0)
399 self._update_firewalls(*firewall_states)
404 """Shuts down the windows VM"""
405 self._guest_exec(r'shutdown /s /t 5')
407 def _wait_vm_boot(self, vm, fname, msg):
408 """Wait until a message appears on a file or the vm process dies"""
410 for _ in range(self.sysprep_params['boot_timeout']):
412 with open(fname) as f:
414 if line.startswith(msg):
417 raise FatalError("Windows VM died unexpectedly!")
419 raise FatalError("Windows VM booting timed out!")
421 def _disable_autologon(self):
422 """Disable automatic logon on the windows image"""
425 r'"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"'
427 self._guest_exec('REG DELETE %s /v DefaultUserName /f' % winlogon)
428 self._guest_exec('REG DELETE %s /v DefaultPassword /f' % winlogon)
429 self._guest_exec('REG DELETE %s /v AutoAdminLogon /f' % winlogon)
431 def _registry_file_path(self, regfile):
432 """Retrieves the case sensitive path to a registry file"""
434 systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
435 path = "%s/system32/config/%s" % (systemroot, regfile)
437 path = self.image.g.case_sensitive_path(path)
438 except RuntimeError as error:
439 raise FatalError("Unable to retrieve registry file: %s. Reason: %s"
440 % (regfile, str(error)))
443 def _enable_os_monitor(self):
444 """Add a script in the registry that will send a random string to the
445 first serial port when the windows image finishes booting.
448 token = "".join(random.choice(string.ascii_letters) for x in range(16))
450 path = self._registry_file_path('SOFTWARE')
451 softwarefd, software = tempfile.mkstemp()
454 self.image.g.download(path, software)
456 h = hivex.Hivex(software, write=True)
458 # Enable automatic logon.
459 # This is needed because we need to execute a script that we add in
460 # the RunOnce registry entry and those programs only get executed
461 # when a user logs on. There is a RunServicesOnce registry entry
462 # whose keys get executed in the background when the logon dialog
463 # box first appears, but they seem to only work with services and
464 # not arbitrary command line expressions :-(
466 # Instructions on how to turn on automatic logon in Windows can be
467 # found here: http://support.microsoft.com/kb/324737
469 # Warning: Registry change will not work if the “Logon Banner” is
470 # defined on the server either by a Group Policy object (GPO) or by
474 for child in ('Microsoft', 'Windows NT', 'CurrentVersion',
476 winlogon = h.node_get_child(winlogon, child)
480 {'key': 'DefaultUserName', 't': 1,
481 'value': "Administrator".encode('utf-16le')})
484 {'key': 'DefaultPassword', 't': 1,
485 'value': self.sysprep_params['password'].encode('utf-16le')})
488 {'key': 'AutoAdminLogon', 't': 1,
489 'value': "1".encode('utf-16le')})
492 for child in ('Microsoft', 'Windows', 'CurrentVersion'):
493 key = h.node_get_child(key, child)
495 runonce = h.node_get_child(key, "RunOnce")
497 runonce = h.node_add_child(key, "RunOnce")
500 r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe '
501 r'-ExecutionPolicy RemoteSigned '
502 r'"&{$port=new-Object System.IO.Ports.SerialPort COM1,9600,'
503 r'None,8,one;$port.open();$port.WriteLine(\"' + token + r'\");'
504 r'$port.Close()}"').encode('utf-16le')
506 h.node_set_value(runonce,
507 {'key': "BootMonitor", 't': 1, 'value': value})
510 r'REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion'
511 r'\policies\system /v LocalAccountTokenFilterPolicy'
512 r' /t REG_DWORD /d 1 /f').encode('utf-16le')
514 h.node_set_value(runonce,
515 {'key': "UpdateRegistry", 't': 1, 'value': value})
519 self.image.g.upload(software, path)
525 def _update_firewalls(self, domain, public, standard):
526 """Enables or disables the firewall for the Domain, the Public and the
527 Standard profile. Returns a triplete with the old values.
529 1 will enable a firewall and 0 will disable it
532 if domain not in (0, 1):
533 raise ValueError("Valid values for domain parameter are 0 and 1")
535 if public not in (0, 1):
536 raise ValueError("Valid values for public parameter are 0 and 1")
538 if standard not in (0, 1):
539 raise ValueError("Valid values for standard parameter are 0 and 1")
541 path = self._registry_file_path("SYSTEM")
542 systemfd, system = tempfile.mkstemp()
545 self.image.g.download(path, system)
547 h = hivex.Hivex(system, write=True)
549 select = h.node_get_child(h.root(), 'Select')
550 current_value = h.node_get_value(select, 'Current')
552 # expecting a little endian dword
553 assert h.value_type(current_value)[1] == 4
554 current = "%03d" % h.value_dword(current_value)
556 firewall_policy = h.root()
557 for child in ('ControlSet%s' % current, 'services', 'SharedAccess',
558 'Parameters', 'FirewallPolicy'):
559 firewall_policy = h.node_get_child(firewall_policy, child)
562 new_values = [domain, public, standard]
563 for profile in ('Domain', 'Public', 'Standard'):
564 node = h.node_get_child(firewall_policy, '%sProfile' % profile)
566 old_value = h.node_get_value(node, 'EnableFirewall')
568 # expecting a little endian dword
569 assert h.value_type(old_value)[1] == 4
570 old_values.append(h.value_dword(old_value))
573 node, {'key': 'EnableFirewall', 't': 4L,
574 'value': struct.pack("<I", new_values.pop(0))})
577 self.image.g.upload(system, path)
584 def _update_uac_remote_setting(self, value):
585 """Updates the registry key value:
586 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
587 \System]"LocalAccountTokenFilterPolicy"
589 value = 1 will disable the UAC remote restrictions
590 value = 0 will enable the UAC remote restrictions
592 For more info see here: http://support.microsoft.com/kb/951016
595 True if the key is changed
596 False if the key is unchanged
599 if value not in (0, 1):
600 raise ValueError("Valid values for value parameter are 0 and 1")
602 path = self._registry_file_path('SOFTWARE')
603 softwarefd, software = tempfile.mkstemp()
606 self.image.g.download(path, software)
608 h = hivex.Hivex(software, write=True)
611 for child in ('Microsoft', 'Windows', 'CurrentVersion', 'Policies',
613 key = h.node_get_child(key, child)
616 for val in h.node_values(key):
617 if h.value_key(val) == "LocalAccountTokenFilterPolicy":
620 if policy is not None:
621 dword = h.value_dword(policy)
627 new_value = {'key': "LocalAccountTokenFilterPolicy", 't': 4L,
628 'value': struct.pack("<I", value)}
630 h.node_set_value(key, new_value)
633 self.image.g.upload(software, path)
640 def _do_collect_metadata(self):
641 """Collect metadata about the OS"""
642 super(Windows, self)._do_collect_metadata()
643 self.meta["USERS"] = " ".join(self._get_users())
645 def _get_users(self):
646 """Returns a list of users found in the images"""
647 samfd, sam = tempfile.mkstemp()
650 self.image.g.download(self._registry_file_path('SAM'), sam)
654 # Navigate to /SAM/Domains/Account/Users
655 users_node = h.root()
656 for child in ('SAM', 'Domains', 'Account', 'Users'):
657 users_node = h.node_get_child(users_node, child)
659 # Navigate to /SAM/Domains/Account/Users/Names
660 names_node = h.node_get_child(users_node, 'Names')
662 # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\%RID%
663 # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\Names\%Username%
665 # The RID (relative identifier) of each user is stored as the type!
666 # (not the value) of the default key of the node under Names whose
667 # name is the user's username. Under the RID node, there in a F
668 # value that contains information about this user account.
670 # See sam.h of the chntpw project on how to translate the F value
671 # of an account in the registry. Bytes 56 & 57 are the account type
672 # and status flags. The first bit is the 'account disabled' bit
673 disabled = lambda f: int(f[56].encode('hex'), 16) & 0x01
676 for user_node in h.node_children(names_node):
677 username = h.node_name(user_node)
678 rid = h.value_type(h.node_get_value(user_node, ""))[0]
679 # if RID is 500 (=0x1f4), the corresponding node name under
680 # Users is '000001F4'
681 key = ("%8.x" % rid).replace(' ', '0').upper()
682 rid_node = h.node_get_child(users_node, key)
683 f_value = h.value_value(h.node_get_value(rid_node, 'F'))[1]
685 if disabled(f_value):
686 self.out.warn("Found disabled `%s' account!" % username)
689 users.append(username)
694 # Filter out the guest account
697 def _check_connectivity(self):
698 """Check if winexe works on the Windows VM"""
700 retries = self.sysprep_params['connection_retries']
701 # If the connection_retries parameter is set to 0 disable the
706 passwd = self.sysprep_params['password']
707 winexe = WinEXE('Administrator', passwd, 'localhost')
708 winexe.uninstall().debug(9)
710 for i in range(retries):
711 (stdout, stderr, rc) = winexe.run('cmd /C')
714 log = tempfile.NamedTemporaryFile(delete=False)
716 log.file.write(stdout)
719 self.out.output("failed! See: `%s' for the full output" % log.name)
721 self.out.output("retrying ...", False)
723 raise FatalError("Connection to the Windows VM failed after %d retries"
726 def _guest_exec(self, command, fatal=True):
727 """Execute a command on a windows VM"""
729 passwd = self.sysprep_params['password']
731 winexe = WinEXE('Administrator', passwd, 'localhost')
732 winexe.runas('Administrator', passwd).uninstall()
735 (stdout, stderr, rc) = winexe.run(command)
736 except WinexeTimeout:
737 FatalError("Command: `%s' timeout out." % command)
739 if rc != 0 and fatal:
740 reason = stderr if len(stderr) else stdout
741 self.out.output("Command: `%s' failed (rc=%d). Reason: %s" %
742 (command, rc, reason))
743 raise FatalError("Command: `%s' failed (rc=%d). Reason: %s" %
744 (command, rc, reason))
746 return (stdout, stderr, rc)
750 """Windows Virtual Machine"""
751 def __init__(self, disk, serial, params):
752 """Create _VM instance
755 serial: File to save the output of the serial port
763 """creates a random mac address"""
764 mac = [0x00, 0x16, 0x3e,
765 random.randint(0x00, 0x7f),
766 random.randint(0x00, 0xff),
767 random.randint(0x00, 0xff)]
769 return ':'.join(['%02x' % x for x in mac])
771 # Use ganeti's VNC port range for a random vnc port
772 self.display = random.randint(11000, 14999) - 5900
774 kvm, needed_args = get_kvm_binary()
777 FatalError("Can't find the kvm binary")
780 args.extend(needed_args)
783 '-smp', '1', '-m', '1024', '-drive',
784 'file=%s,format=raw,cache=unsafe,if=virtio' % self.disk,
785 '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
786 '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(),
787 '-vnc', ':%d' % self.display, '-serial', 'file:%s' % self.serial,
788 '-monitor', 'stdio'])
790 self.process = subprocess.Popen(args, stdin=subprocess.PIPE,
791 stdout=subprocess.PIPE)
794 """Check if the VM is still alive"""
795 return self.process.poll() is None
800 if not self.isalive():
803 def handler(signum, frame):
804 self.process.terminate()
809 raise FatalError("VM destroy timed-out")
811 signal.signal(signal.SIGALRM, handler)
813 signal.alarm(self.params['shutdown_timeout'])
814 self.process.communicate(input="system_powerdown\n")
817 def wait(self, timeout=0):
818 """Wait for the VM to terminate"""
820 def handler(signum, frame):
822 raise FatalError("VM wait timed-out.")
824 signal.signal(signal.SIGALRM, handler)
826 signal.alarm(timeout)
827 stdout, stderr = self.process.communicate()
830 return (stdout, stderr, self.process.poll())
832 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :