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
54 # For more info see: http://technet.microsoft.com/en-us/library/jj612867.aspx
55 KMS_CLIENT_SETUP_KEYS = {
56 "Windows 8.1 Professional": "GCRJD-8NW9H-F2CDX-CCM8D-9D6T9",
57 "Windows 8.1 Professional N": "HMCNV-VVBFX-7HMBH-CTY9B-B4FXY",
58 "Windows 8.1 Enterprise": "MHF9N-XY6XB-WVXMC-BTDCT-MKKG7",
59 "Windows 8.1 Enterprise N": "TT4HM-HN7YT-62K67-RGRQJ-JFFXW",
60 "Windows Server 2012 R2 Server Standard": "D2N9P-3P6X9-2R39C-7RTCD-MDVJX",
61 "Windows Server 2012 R2 Datacenter": "W3GGN-FT8W3-Y4M27-J84CP-Q3VJ9",
62 "Windows Server 2012 R2 Essentials": "KNC87-3J2TX-XB4WP-VCPJV-M4FWM",
63 "Windows 8 Professional": "NG4HW-VH26C-733KW-K6F98-J8CK4",
64 "Windows 8 Professional N": "XCVCF-2NXM9-723PB-MHCB7-2RYQQ",
65 "Windows 8 Enterprise": "32JNW-9KQ84-P47T8-D8GGY-CWCK7",
66 "Windows 8 Enterprise N": "JMNMF-RHW7P-DMY6X-RF3DR-X2BQT",
67 "Windows Server 2012 Core": "BN3D2-R7TKB-3YPBD-8DRP2-27GG4",
68 "Windows Server 2012 Core N": "8N2M2-HWPGY-7PGT9-HGDD8-GVGGY",
69 "Windows Server 2012 Core Single Language":
70 "2WN2H-YGCQR-KFX6K-CD6TF-84YXQ",
71 "Windows Server 2012 Core Country Specific":
72 "4K36P-JN4VD-GDC6V-KDT89-DYFKP",
73 "Windows Server 2012 Server Standard": "XC9B7-NBPP2-83J2H-RHMBY-92BT4",
74 "Windows Server 2012 Standard Core": "XC9B7-NBPP2-83J2H-RHMBY-92BT4",
75 "Windows Server 2012 MultiPoint Standard": "HM7DN-YVMH3-46JC3-XYTG7-CYQJJ",
76 "Windows Server 2012 MultiPoint Premium": "XNH6W-2V9GX-RGJ4K-Y8X6F-QGJ2G",
77 "Windows Server 2012 Datacenter": "48HP8-DN98B-MYWDG-T2DCC-8W83P",
78 "Windows Server 2012 Datacenter Core": "48HP8-DN98B-MYWDG-T2DCC-8W83P",
79 "Windows 7 Professional": "FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4",
80 "Windows 7 Professional N": "MRPKT-YTG23-K7D7T-X2JMM-QY7MG",
81 "Windows 7 Professional E": "W82YF-2Q76Y-63HXB-FGJG9-GF7QX",
82 "Windows 7 Enterprise": "33PXH-7Y6KF-2VJC9-XBBR8-HVTHH",
83 "Windows 7 Enterprise N": "YDRBP-3D83W-TY26F-D46B2-XCKRJ",
84 "Windows 7 Enterprise E": "C29WB-22CC8-VJ326-GHFJW-H9DH4",
85 "Windows Server 2008 R2 Web": "6TPJF-RBVHG-WBW2R-86QPH-6RTM4",
86 "Windows Server 2008 R2 HPC edition": "TT8MH-CG224-D3D7Q-498W2-9QCTX",
87 "Windows Server 2008 R2 Standard": "YC6KT-GKW9T-YTKYR-T4X34-R7VHC",
88 "Windows Server 2008 R2 Enterprise": "489J6-VHDMP-X63PK-3K798-CPX3Y",
89 "Windows Server 2008 R2 Datacenter": "74YFP-3QFB3-KQT8W-PMXWJ-7M648",
90 "Windows Server 2008 R2 for Itanium-based Systems":
91 "GT63C-RJFQ3-4GMB6-BRFB9-CB83V",
92 "Windows Vista Business": "YFKBB-PQJJV-G996G-VWGXY-2V3X8",
93 "Windows Vista Business N": "HMBQG-8H2RH-C77VX-27R82-VMQBT",
94 "Windows Vista Enterprise": "VKK3X-68KWM-X2YGT-QR4M6-4BWMV",
95 "Windows Vista Enterprise N": "VTC42-BM838-43QHV-84HX6-XJXKV",
96 "Windows Web Server 2008": "WYR28-R7TFJ-3X2YQ-YCY4H-M249D",
97 "Windows Server 2008 Standard": "TM24T-X9RMF-VWXK6-X8JC9-BFGM2",
98 "Windows Server 2008 Standard without Hyper-V":
99 "W7VD6-7JFBR-RX26B-YKQ3Y-6FFFJ",
100 "Windows Server 2008 Enterprise":
101 "YQGMW-MPWTJ-34KDK-48M3W-X4Q6V",
102 "Windows Server 2008 Enterprise without Hyper-V":
103 "39BXF-X8Q23-P2WWT-38T2F-G3FPG",
104 "Windows Server 2008 HPC": "RCTX3-KWVHP-BR6TB-RB6DM-6X7HP",
105 "Windows Server 2008 Datacenter": "7M67G-PC374-GR742-YH8V4-TCBY3",
106 "Windows Server 2008 Datacenter without Hyper-V":
107 "22XQ2-VRXRG-P8D42-K34TD-G3QQC",
108 "Windows Server 2008 for Itanium-Based Systems":
109 "4DWFP-JF3DJ-B7DTH-78FJB-PDRHK"}
111 _POSINT = lambda x: type(x) == int and x >= 0
114 class Windows(OSBase):
115 """OS class for Windows"""
117 'shutdown_timeout', int, 120, "Shutdown Timeout (seconds)", _POSINT)
119 'boot_timeout', int, 300, "Boot Timeout (seconds)", _POSINT)
121 'connection_retries', int, 5, "Connection Retries", _POSINT)
123 'smp', int, 1, "Number of CPUs for the helper VM", _POSINT)
125 'mem', int, 1024, "Virtual RAM size for the helper VM (MiB)", _POSINT)
126 @add_sysprep_param('password', str, None, 'Image Administrator Password')
127 def __init__(self, image, **kargs):
128 super(Windows, self).__init__(image, **kargs)
130 # The commit with the following message was added in
131 # libguestfs 1.17.18 and was backported in version 1.16.11:
133 # When a Windows guest doesn't have a HKLM\SYSTEM\MountedDevices node,
134 # inspection fails. However inspection should not completely fail just
135 # because we cannot get the drive letter mapping from a guest.
137 # Since Microsoft Sysprep removes the aforementioned key, image
138 # creation for windows can only be supported if the installed guestfs
139 # version is 1.17.18 or higher
140 if self.image.check_guestfs_version(1, 17, 18) < 0 and \
141 (self.image.check_guestfs_version(1, 17, 0) >= 0 or
142 self.image.check_guestfs_version(1, 16, 11) < 0):
144 'For windows support libguestfs 1.16.11 or above is required')
146 # Check if winexe is installed
147 if not WinEXE.is_installed():
149 "For windows support `Winexe' needs to be installed")
151 device = self.image.g.part_to_dev(self.root)
153 self.last_part_num = self.image.g.part_list(device)[-1]['part_num']
154 self.last_drive = None
155 self.system_drive = None
157 for drive, part in self.image.g.inspect_get_drive_mappings(self.root):
158 if part == "%s%d" % (device, self.last_part_num):
159 self.last_drive = drive
160 if part == self.root:
161 self.system_drive = drive
163 assert self.system_drive
165 self.product_name = self.image.g.inspect_get_product_name(self.root)
166 self.syspreped = False
168 @sysprep('Disabling IPv6 privacy extensions')
169 def disable_ipv6_privacy_extensions(self):
170 """Disable IPv6 privacy extensions"""
172 self._guest_exec('netsh interface ipv6 set global '
173 'randomizeidentifiers=disabled store=persistent')
175 @sysprep('Disabling Teredo interface')
176 def disable_teredo(self):
177 """Disable Teredo interface"""
179 self._guest_exec('netsh interface teredo set state disabled')
181 @sysprep('Disabling ISATAP Adapters')
182 def disable_isatap(self):
183 """Disable ISATAP Adapters"""
185 self._guest_exec('netsh interface isa set state disabled')
187 @sysprep('Enabling ping responses')
188 def enable_pings(self):
189 """Enable ping responses"""
191 self._guest_exec('netsh firewall set icmpsetting 8')
193 @sysprep('Disabling hibernation support')
194 def disable_hibernation(self):
195 """Disable hibernation support and remove the hibernation file"""
197 self._guest_exec(r'powercfg.exe /hibernate off')
199 @sysprep('Setting the system clock to UTC')
201 """Set the hardware clock to UTC"""
203 path = r'HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
205 r'REG ADD %s /v RealTimeIsUniversal /t REG_DWORD /d 1 /f' % path)
207 @sysprep('Clearing the event logs')
208 def clear_logs(self):
209 """Clear all the event logs"""
212 r"cmd /q /c for /f %l in ('wevtutil el') do wevtutil cl %l")
214 @sysprep('Executing Sysprep on the image (may take more that 10 minutes)')
215 def microsoft_sysprep(self):
216 """Run the Microsoft System Preparation Tool. This will remove
217 system-specific data and will make the image ready to be deployed.
218 After this no other task may run.
221 self._guest_exec(r'C:\Windows\system32\sysprep\sysprep '
222 r'/quiet /generalize /oobe /shutdown')
223 self.syspreped = True
225 @sysprep('Converting the image into a KMS client', enabled=False)
226 def kms_client_setup(self):
227 """Install the appropriate KMS client setup key to the image to convert
228 it to a KMS client. Computers that are running volume licensing
229 editions of Windows 8, Windows Server 2012, Windows 7, Windows Server
230 2008 R2, Windows Vista, and Windows Server 2008 are by default KMS
231 clients with no additional configuration needed.
234 setup_key = KMS_CLIENT_SETUP_KEYS[self.product_name]
237 "Don't know the KMS client setup key for product: `%s'" %
242 r"cscript \Windows\system32\slmgr.vbs /ipk %s" % setup_key)
244 @sysprep('Shrinking the last filesystem')
246 """Shrink the last filesystem. Make sure the filesystem is defragged"""
248 # Query for the maximum number of reclaimable bytes
250 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
251 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
252 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
253 r'ECHO SHRINK QUERYMAX >> %SCRIPT% & ' +
254 r'ECHO EXIT >> %SCRIPT% & ' +
255 r'DISKPART /S %SCRIPT% & ' +
256 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
259 stdout, stderr, rc = self._guest_exec(cmd)
262 for line in stdout.splitlines():
263 # diskpart will return something like this:
265 # The maximum number of reclaimable bytes is: xxxx MB
267 if line.find('reclaimable') >= 0:
268 answer = line.split(':')[1].strip()
269 m = re.search('(\d+) MB', answer)
271 querymax = m.group(1)
274 "Unexpected output for `shrink querymax' command: %s" %
278 FatalError("Error in shrinking! "
279 "Couldn't find the max number of reclaimable bytes!")
281 querymax = int(querymax)
283 # Practically the smallest shrunken size generally is at around
284 # "used space" + (20-200 MB). Please also take into account that
285 # Windows might need about 50-100 MB free space left to boot safely.
286 # I'll give 100MB extra space just to be sure
290 self.out.warn("Not enough available space to shrink the image!")
293 self.out.output("\tReclaiming %dMB ..." % querymax)
296 r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
297 r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
298 'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
299 'ECHO SHRINK DESIRED=%d >> %%SCRIPT%% & ' % querymax +
300 r'ECHO EXIT >> %SCRIPT% & ' +
301 r'DISKPART /S %SCRIPT% & ' +
302 r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
305 stdout, stderr, rc = self._guest_exec(cmd, False)
308 FatalError("Shrinking failed. Please make sure the media is "
309 "defraged with a command like this: "
310 "`Defrag.exe /U /X /W'")
311 for line in stdout.splitlines():
312 if line.find('shrunk') >= 0:
313 self.out.output(line)
315 def do_sysprep(self):
316 """Prepare system for image creation."""
318 if getattr(self, 'syspreped', False):
319 raise FatalError("Image is already syspreped!")
321 txt = "System preparation parameter: `%s' is needed but missing!"
322 for name, param in self.needed_sysprep_params.items():
323 if name not in self.sysprep_params:
324 raise FatalError(txt % name)
326 self.mount(readonly=False)
328 disabled_uac = self._update_uac_remote_setting(1)
329 token = self._enable_os_monitor()
331 # disable the firewalls
332 firewall_states = self._update_firewalls(0, 0, 0)
334 # Delete the pagefile. It will be recreated when the system boots
335 systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
337 pagefile = "%s/pagefile.sys" % systemroot
338 self.image.g.rm_rf(self.image.g.case_sensitive_path(pagefile))
345 self.image.disable_guestfs()
350 self.out.output("Starting windows VM ...", False)
351 monitorfd, monitor = tempfile.mkstemp()
353 vm = _VM(self.image.device, monitor, self.sysprep_params)
354 self.out.success("started (console on vnc display: %d)." %
357 self.out.output("Waiting for OS to boot ...", False)
358 self._wait_vm_boot(vm, monitor, token)
359 self.out.success('done')
361 self.out.output("Checking connectivity to the VM ...", False)
362 self._check_connectivity()
363 self.out.success('done')
365 self.out.output("Disabling automatic logon ...", False)
366 self._disable_autologon()
367 self.out.success('done')
369 self.out.output('Preparing system for image creation:')
371 tasks = self.list_syspreps()
372 enabled = [task for task in tasks if task.enabled]
375 # Make sure shrink runs in the end, before ms sysprep
376 enabled = [task for task in enabled if
377 self.sysprep_info(task).name != 'shrink']
379 if len(enabled) != size:
380 enabled.append(self.shrink)
382 # Make sure the ms sysprep is the last task to run if it is enabled
383 enabled = [task for task in enabled if
384 self.sysprep_info(task).name != 'microsoft-sysprep']
386 ms_sysprep_enabled = False
387 if len(enabled) != size:
388 enabled.append(self.microsoft_sysprep)
389 ms_sysprep_enabled = True
394 self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False)
396 setattr(task.im_func, 'executed', True)
398 self.out.output("Sending shut down command ...", False)
399 if not ms_sysprep_enabled:
401 self.out.success("done")
403 self.out.output("Waiting for windows to shut down ...", False)
404 vm.wait(self.sysprep_params['shutdown_timeout'])
405 self.out.success("done")
407 if monitor is not None:
412 self.out.output("Destroying windows VM ...", False)
414 self.out.success("done")
416 self.image.enable_guestfs()
418 self.mount(readonly=False)
421 self._update_uac_remote_setting(0)
423 self._update_firewalls(*firewall_states)
428 """Shuts down the windows VM"""
429 self._guest_exec(r'shutdown /s /t 5')
431 def _wait_vm_boot(self, vm, fname, msg):
432 """Wait until a message appears on a file or the vm process dies"""
434 for _ in range(self.sysprep_params['boot_timeout']):
436 with open(fname) as f:
438 if line.startswith(msg):
441 raise FatalError("Windows VM died unexpectedly!")
443 raise FatalError("Windows VM booting timed out!")
445 def _disable_autologon(self):
446 """Disable automatic logon on the windows image"""
449 r'"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"'
451 self._guest_exec('REG DELETE %s /v DefaultUserName /f' % winlogon)
452 self._guest_exec('REG DELETE %s /v DefaultPassword /f' % winlogon)
453 self._guest_exec('REG DELETE %s /v AutoAdminLogon /f' % winlogon)
455 def _registry_file_path(self, regfile):
456 """Retrieves the case sensitive path to a registry file"""
458 systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
459 path = "%s/system32/config/%s" % (systemroot, regfile)
461 path = self.image.g.case_sensitive_path(path)
462 except RuntimeError as error:
463 raise FatalError("Unable to retrieve registry file: %s. Reason: %s"
464 % (regfile, str(error)))
467 def _enable_os_monitor(self):
468 """Add a script in the registry that will send a random string to the
469 first serial port when the windows image finishes booting.
472 token = "".join(random.choice(string.ascii_letters) for x in range(16))
474 path = self._registry_file_path('SOFTWARE')
475 softwarefd, software = tempfile.mkstemp()
478 self.image.g.download(path, software)
480 h = hivex.Hivex(software, write=True)
482 # Enable automatic logon.
483 # This is needed because we need to execute a script that we add in
484 # the RunOnce registry entry and those programs only get executed
485 # when a user logs on. There is a RunServicesOnce registry entry
486 # whose keys get executed in the background when the logon dialog
487 # box first appears, but they seem to only work with services and
488 # not arbitrary command line expressions :-(
490 # Instructions on how to turn on automatic logon in Windows can be
491 # found here: http://support.microsoft.com/kb/324737
493 # Warning: Registry change will not work if the “Logon Banner” is
494 # defined on the server either by a Group Policy object (GPO) or by
498 for child in ('Microsoft', 'Windows NT', 'CurrentVersion',
500 winlogon = h.node_get_child(winlogon, child)
504 {'key': 'DefaultUserName', 't': 1,
505 'value': "Administrator".encode('utf-16le')})
508 {'key': 'DefaultPassword', 't': 1,
509 'value': self.sysprep_params['password'].encode('utf-16le')})
512 {'key': 'AutoAdminLogon', 't': 1,
513 'value': "1".encode('utf-16le')})
516 for child in ('Microsoft', 'Windows', 'CurrentVersion'):
517 key = h.node_get_child(key, child)
519 runonce = h.node_get_child(key, "RunOnce")
521 runonce = h.node_add_child(key, "RunOnce")
524 r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe '
525 r'-ExecutionPolicy RemoteSigned '
526 r'"&{$port=new-Object System.IO.Ports.SerialPort COM1,9600,'
527 r'None,8,one;$port.open();$port.WriteLine(\"' + token + r'\");'
528 r'$port.Close()}"').encode('utf-16le')
530 h.node_set_value(runonce,
531 {'key': "BootMonitor", 't': 1, 'value': value})
534 r'REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion'
535 r'\policies\system /v LocalAccountTokenFilterPolicy'
536 r' /t REG_DWORD /d 1 /f').encode('utf-16le')
538 h.node_set_value(runonce,
539 {'key': "UpdateRegistry", 't': 1, 'value': value})
543 self.image.g.upload(software, path)
549 def _update_firewalls(self, domain, public, standard):
550 """Enables or disables the firewall for the Domain, the Public and the
551 Standard profile. Returns a triplete with the old values.
553 1 will enable a firewall and 0 will disable it
556 if domain not in (0, 1):
557 raise ValueError("Valid values for domain parameter are 0 and 1")
559 if public not in (0, 1):
560 raise ValueError("Valid values for public parameter are 0 and 1")
562 if standard not in (0, 1):
563 raise ValueError("Valid values for standard parameter are 0 and 1")
565 path = self._registry_file_path("SYSTEM")
566 systemfd, system = tempfile.mkstemp()
569 self.image.g.download(path, system)
571 h = hivex.Hivex(system, write=True)
573 select = h.node_get_child(h.root(), 'Select')
574 current_value = h.node_get_value(select, 'Current')
576 # expecting a little endian dword
577 assert h.value_type(current_value)[1] == 4
578 current = "%03d" % h.value_dword(current_value)
580 firewall_policy = h.root()
581 for child in ('ControlSet%s' % current, 'services', 'SharedAccess',
582 'Parameters', 'FirewallPolicy'):
583 firewall_policy = h.node_get_child(firewall_policy, child)
586 new_values = [domain, public, standard]
587 for profile in ('Domain', 'Public', 'Standard'):
588 node = h.node_get_child(firewall_policy, '%sProfile' % profile)
590 old_value = h.node_get_value(node, 'EnableFirewall')
592 # expecting a little endian dword
593 assert h.value_type(old_value)[1] == 4
594 old_values.append(h.value_dword(old_value))
597 node, {'key': 'EnableFirewall', 't': 4L,
598 'value': struct.pack("<I", new_values.pop(0))})
601 self.image.g.upload(system, path)
608 def _update_uac_remote_setting(self, value):
609 """Updates the registry key value:
610 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
611 \System]"LocalAccountTokenFilterPolicy"
613 value = 1 will disable the UAC remote restrictions
614 value = 0 will enable the UAC remote restrictions
616 For more info see here: http://support.microsoft.com/kb/951016
619 True if the key is changed
620 False if the key is unchanged
623 if value not in (0, 1):
624 raise ValueError("Valid values for value parameter are 0 and 1")
626 path = self._registry_file_path('SOFTWARE')
627 softwarefd, software = tempfile.mkstemp()
630 self.image.g.download(path, software)
632 h = hivex.Hivex(software, write=True)
635 for child in ('Microsoft', 'Windows', 'CurrentVersion', 'Policies',
637 key = h.node_get_child(key, child)
640 for val in h.node_values(key):
641 if h.value_key(val) == "LocalAccountTokenFilterPolicy":
644 if policy is not None:
645 dword = h.value_dword(policy)
651 new_value = {'key': "LocalAccountTokenFilterPolicy", 't': 4L,
652 'value': struct.pack("<I", value)}
654 h.node_set_value(key, new_value)
657 self.image.g.upload(software, path)
664 def _do_collect_metadata(self):
665 """Collect metadata about the OS"""
666 super(Windows, self)._do_collect_metadata()
667 self.meta["USERS"] = " ".join(self._get_users())
669 def _get_users(self):
670 """Returns a list of users found in the images"""
671 samfd, sam = tempfile.mkstemp()
674 self.image.g.download(self._registry_file_path('SAM'), sam)
678 # Navigate to /SAM/Domains/Account/Users
679 users_node = h.root()
680 for child in ('SAM', 'Domains', 'Account', 'Users'):
681 users_node = h.node_get_child(users_node, child)
683 # Navigate to /SAM/Domains/Account/Users/Names
684 names_node = h.node_get_child(users_node, 'Names')
686 # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\%RID%
687 # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\Names\%Username%
689 # The RID (relative identifier) of each user is stored as the type!
690 # (not the value) of the default key of the node under Names whose
691 # name is the user's username. Under the RID node, there in a F
692 # value that contains information about this user account.
694 # See sam.h of the chntpw project on how to translate the F value
695 # of an account in the registry. Bytes 56 & 57 are the account type
696 # and status flags. The first bit is the 'account disabled' bit
697 disabled = lambda f: int(f[56].encode('hex'), 16) & 0x01
700 for user_node in h.node_children(names_node):
701 username = h.node_name(user_node)
702 rid = h.value_type(h.node_get_value(user_node, ""))[0]
703 # if RID is 500 (=0x1f4), the corresponding node name under
704 # Users is '000001F4'
705 key = ("%8.x" % rid).replace(' ', '0').upper()
706 rid_node = h.node_get_child(users_node, key)
707 f_value = h.value_value(h.node_get_value(rid_node, 'F'))[1]
709 if disabled(f_value):
710 self.out.warn("Found disabled `%s' account!" % username)
713 users.append(username)
718 # Filter out the guest account
721 def _check_connectivity(self):
722 """Check if winexe works on the Windows VM"""
724 retries = self.sysprep_params['connection_retries']
725 # If the connection_retries parameter is set to 0 disable the
730 passwd = self.sysprep_params['password']
731 winexe = WinEXE('Administrator', passwd, 'localhost')
732 winexe.uninstall().debug(9)
734 for i in range(retries):
735 (stdout, stderr, rc) = winexe.run('cmd /C')
738 log = tempfile.NamedTemporaryFile(delete=False)
740 log.file.write(stdout)
743 self.out.output("failed! See: `%s' for the full output" % log.name)
745 self.out.output("retrying ...", False)
747 raise FatalError("Connection to the Windows VM failed after %d retries"
750 def _guest_exec(self, command, fatal=True):
751 """Execute a command on a windows VM"""
753 passwd = self.sysprep_params['password']
755 winexe = WinEXE('Administrator', passwd, 'localhost')
756 winexe.runas('Administrator', passwd).uninstall()
759 (stdout, stderr, rc) = winexe.run(command)
760 except WinexeTimeout:
761 FatalError("Command: `%s' timeout out." % command)
763 if rc != 0 and fatal:
764 reason = stderr if len(stderr) else stdout
765 self.out.output("Command: `%s' failed (rc=%d). Reason: %s" %
766 (command, rc, reason))
767 raise FatalError("Command: `%s' failed (rc=%d). Reason: %s" %
768 (command, rc, reason))
770 return (stdout, stderr, rc)
774 """Windows Virtual Machine"""
775 def __init__(self, disk, serial, params):
776 """Create _VM instance
779 serial: File to save the output of the serial port
787 """creates a random mac address"""
788 mac = [0x00, 0x16, 0x3e,
789 random.randint(0x00, 0x7f),
790 random.randint(0x00, 0xff),
791 random.randint(0x00, 0xff)]
793 return ':'.join(['%02x' % x for x in mac])
795 # Use ganeti's VNC port range for a random vnc port
796 self.display = random.randint(11000, 14999) - 5900
798 kvm, needed_args = get_kvm_binary()
801 FatalError("Can't find the kvm binary")
804 args.extend(needed_args)
807 '-smp', str(self.params['smp']), '-m', str(self.params['mem']),
808 '-drive', 'file=%s,format=raw,cache=unsafe,if=virtio' % self.disk,
809 '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
810 '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(),
811 '-vnc', ':%d' % self.display, '-serial', 'file:%s' % self.serial,
812 '-monitor', 'stdio'])
814 self.process = subprocess.Popen(args, stdin=subprocess.PIPE,
815 stdout=subprocess.PIPE)
818 """Check if the VM is still alive"""
819 return self.process.poll() is None
824 if not self.isalive():
827 def handler(signum, frame):
828 self.process.terminate()
833 raise FatalError("VM destroy timed-out")
835 signal.signal(signal.SIGALRM, handler)
837 signal.alarm(self.params['shutdown_timeout'])
838 self.process.communicate(input="system_powerdown\n")
841 def wait(self, timeout=0):
842 """Wait for the VM to terminate"""
844 def handler(signum, frame):
846 raise FatalError("VM wait timed-out.")
848 signal.signal(signal.SIGALRM, handler)
850 signal.alarm(timeout)
851 stdout, stderr = self.process.communicate()
854 return (stdout, stderr, self.process.poll())
856 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :