54e4797834b89036ad7d6202b1bdf6f06b80e72c
[snf-image-creator] / image_creator / os_type / windows.py
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright 2012 GRNET S.A. All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or
6 # without modification, are permitted provided that the following
7 # conditions are met:
8 #
9 #   1. Redistributions of source code must retain the above
10 #      copyright notice, this list of conditions and the following
11 #      disclaimer.
12 #
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.
17 #
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.
30 #
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.
35
36 """This module hosts OS-specific code common for the various Microsoft
37 Windows OSs."""
38
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
42
43 import hivex
44 import tempfile
45 import os
46 import time
47 import random
48 import string
49 import subprocess
50 import struct
51
52 kvm = get_command('kvm')
53
54 BOOT_TIMEOUT = 300
55
56 # For more info see: http://technet.microsoft.com/en-us/library/jj612867.aspx
57 KMS_CLIENT_SETUP_KEYS = {
58     "Windows 8 Professional": "NG4HW-VH26C-733KW-K6F98-J8CK4",
59     "Windows 8 Professional N": "XCVCF-2NXM9-723PB-MHCB7-2RYQQ",
60     "Windows 8 Enterprise": "32JNW-9KQ84-P47T8-D8GGY-CWCK7",
61     "Windows 8 Enterprise N": "JMNMF-RHW7P-DMY6X-RF3DR-X2BQT",
62     "Windows Server 2012 Core": "BN3D2-R7TKB-3YPBD-8DRP2-27GG4",
63     "Windows Server 2012 Core N": "8N2M2-HWPGY-7PGT9-HGDD8-GVGGY",
64     "Windows Server 2012 Core Single Language":
65     "2WN2H-YGCQR-KFX6K-CD6TF-84YXQ",
66     "Windows Server 2012 Core Country Specific":
67     "4K36P-JN4VD-GDC6V-KDT89-DYFKP",
68     "Windows Server 2012 Server Standard": "XC9B7-NBPP2-83J2H-RHMBY-92BT4",
69     "Windows Server 2012 Standard Core": "XC9B7-NBPP2-83J2H-RHMBY-92BT4",
70     "Windows Server 2012 MultiPoint Standard": "HM7DN-YVMH3-46JC3-XYTG7-CYQJJ",
71     "Windows Server 2012 MultiPoint Premium": "XNH6W-2V9GX-RGJ4K-Y8X6F-QGJ2G",
72     "Windows Server 2012 Datacenter": "48HP8-DN98B-MYWDG-T2DCC-8W83P",
73     "Windows Server 2012 Datacenter Core": "48HP8-DN98B-MYWDG-T2DCC-8W83P",
74     "Windows 7 Professional": "FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4",
75     "Windows 7 Professional N": "MRPKT-YTG23-K7D7T-X2JMM-QY7MG",
76     "Windows 7 Professional E": "W82YF-2Q76Y-63HXB-FGJG9-GF7QX",
77     "Windows 7 Enterprise": "33PXH-7Y6KF-2VJC9-XBBR8-HVTHH",
78     "Windows 7 Enterprise N": "YDRBP-3D83W-TY26F-D46B2-XCKRJ",
79     "Windows 7 Enterprise E": "C29WB-22CC8-VJ326-GHFJW-H9DH4",
80     "Windows Server 2008 R2 Web": "6TPJF-RBVHG-WBW2R-86QPH-6RTM4",
81     "Windows Server 2008 R2 HPC edition": "TT8MH-CG224-D3D7Q-498W2-9QCTX",
82     "Windows Server 2008 R2 Standard": "YC6KT-GKW9T-YTKYR-T4X34-R7VHC",
83     "Windows Server 2008 R2 Enterprise": "489J6-VHDMP-X63PK-3K798-CPX3Y",
84     "Windows Server 2008 R2 Datacenter": "74YFP-3QFB3-KQT8W-PMXWJ-7M648",
85     "Windows Server 2008 R2 for Itanium-based Systems":
86     "GT63C-RJFQ3-4GMB6-BRFB9-CB83V",
87     "Windows Vista Business": "YFKBB-PQJJV-G996G-VWGXY-2V3X8",
88     "Windows Vista Business N": "HMBQG-8H2RH-C77VX-27R82-VMQBT",
89     "Windows Vista Enterprise": "VKK3X-68KWM-X2YGT-QR4M6-4BWMV",
90     "Windows Vista Enterprise N": "VTC42-BM838-43QHV-84HX6-XJXKV",
91     "Windows Web Server 2008": "WYR28-R7TFJ-3X2YQ-YCY4H-M249D",
92     "Windows Server 2008 Standard": "TM24T-X9RMF-VWXK6-X8JC9-BFGM2",
93     "Windows Server 2008 Standard without Hyper-V":
94     "W7VD6-7JFBR-RX26B-YKQ3Y-6FFFJ",
95     "Windows Server 2008 Enterprise":
96     "YQGMW-MPWTJ-34KDK-48M3W-X4Q6V",
97     "Windows Server 2008 Enterprise without Hyper-V":
98     "39BXF-X8Q23-P2WWT-38T2F-G3FPG",
99     "Windows Server 2008 HPC": "RCTX3-KWVHP-BR6TB-RB6DM-6X7HP",
100     "Windows Server 2008 Datacenter": "7M67G-PC374-GR742-YH8V4-TCBY3",
101     "Windows Server 2008 Datacenter without Hyper-V":
102     "22XQ2-VRXRG-P8D42-K34TD-G3QQC",
103     "Windows Server 2008 for Itanium-Based Systems":
104     "4DWFP-JF3DJ-B7DTH-78FJB-PDRHK"}
105
106
107 class Windows(OSBase):
108     """OS class for Windows"""
109     def __init__(self, image, **kargs):
110         super(Windows, self).__init__(image, **kargs)
111
112         device = self.g.part_to_dev(self.root)
113
114         self.last_part_num = self.g.part_list(device)[-1]['part_num']
115         self.last_drive = None
116         self.system_drive = None
117
118         for drive, partition in self.g.inspect_get_drive_mappings(self.root):
119             if partition == "%s%d" % (device, self.last_part_num):
120                 self.last_drive = drive
121             if partition == self.root:
122                 self.system_drive = drive
123
124         assert self.system_drive
125
126         self.product_name = self.g.inspect_get_product_name(self.root)
127
128     def needed_sysprep_params(self):
129         """Returns a list of needed sysprep parameters. Each element in the
130         list is a SysprepParam object.
131         """
132         password = self.SysprepParam(
133             'password', 'Image Administrator Password', 20, lambda x: True)
134
135         return [password]
136
137     @sysprep('Disabling IPv6 privacy extensions')
138     def disable_ipv6_privacy_extensions(self):
139         """Disable IPv6 privacy extensions"""
140
141         self._guest_exec('netsh interface ipv6 set global '
142                          'randomizeidentifiers=disabled store=persistent')
143
144     @sysprep('Disabling Teredo interface')
145     def disable_teredo(self):
146         """Disable Teredo interface"""
147
148         self._guest_exec('netsh interface teredo set state disabled')
149
150     @sysprep('Disabling ISATAP Adapters')
151     def disable_isatap(self):
152         """Disable ISATAP Adapters"""
153
154         self._guest_exec('netsh interface isa set state disabled')
155
156     @sysprep('Enabling ping responses')
157     def enable_pings(self):
158         """Enable ping responses"""
159
160         self._guest_exec('netsh firewall set icmpsetting 8')
161
162     @sysprep('Disabling hibernation support')
163     def disable_hibernation(self):
164         """Disable hibernation support and remove the hibernation file"""
165
166         self._guest_exec(r'powercfg.exe /hibernate off')
167
168     @sysprep('Setting the system clock to UTC')
169     def utc(self):
170         """Set the hardware clock to UTC"""
171
172         path = r'HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
173         self._guest_exec(
174             r'REG ADD %s /v RealTimeIsUniversal /t REG_DWORD /d 1 /f' % path)
175
176     @sysprep('Clearing the event logs')
177     def clear_logs(self):
178         """Clear all the event logs"""
179
180         self._guest_exec(
181             r"cmd /q /c for /f %l in ('wevtutil el') do wevtutil cl %l")
182
183     @sysprep('Executing Sysprep on the image (may take more that 10 minutes)')
184     def microsoft_sysprep(self):
185         """Run the Microsoft System Preparation Tool. This will remove
186         system-specific data and will make the image ready to be deployed.
187         After this no other task may run.
188         """
189
190         self._guest_exec(r'C:\Windows\system32\sysprep\sysprep '
191                          r'/quiet /generalize /oobe /shutdown')
192         self.syspreped = True
193
194     @sysprep('Converting the image into a KMS client', enabled=False)
195     def kms_client_setup(self):
196         """Install the appropriate KMS client setup key to the image to convert
197         it to a KMS client. Computers that are running volume licensing
198         editions of Windows 8, Windows Server 2012, Windows 7, Windows Server
199         2008 R2, Windows Vista, and Windows Server 2008 are, by default, KMS
200         clients with no additional configuration needed.
201         """
202         try:
203             setup_key = KMS_CLIENT_SETUP_KEYS[self.product_name]
204         except KeyError:
205             self.out.warn(
206                 "Don't know the KMS client setup key for product: `%s'" %
207                 self.product_name)
208             return
209
210         self._guest_exec(
211             "cscript \Windows\system32\slmgr.vbs /ipk %s" % setup_key)
212
213     @sysprep('Shrinking the last filesystem')
214     def shrink(self):
215         """Shrink the last filesystem. Make sure the filesystem is defragged"""
216
217         # Query for the maximum number of reclaimable bytes
218         cmd = (
219             r'cmd /Q /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
220             r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
221             'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
222             r'ECHO SHRINK QUERYMAX >> %SCRIPT% & ' +
223             r'ECHO EXIT >> %SCRIPT% & ' +
224             r'DISKPART /S %SCRIPT% & ' +
225             r'IF ERRORLEVEL 1 EXIT /B 1 & ' +
226             r'DEL /Q %SCRIPT%"')
227
228         stdout, stderr, rc = self._guest_exec(cmd)
229
230         querymax = None
231         for line in stdout.splitlines():
232             # diskpart will return something like this:
233             #
234             #   The maximum number of reclaimable bytes is: xxxx MB
235             #
236             if line.find('reclaimable') >= 0:
237                 querymax = line.split(':')[1].split()[0].strip()
238                 assert querymax.isdigit(), \
239                     "Number of reclaimable bytes not a number"
240
241         if querymax is None:
242             FatalError("Error in shrinking! "
243                        "Couldn't find the max number of reclaimable bytes!")
244
245         querymax = int(querymax)
246         # From ntfsresize:
247         # Practically the smallest shrunken size generally is at around
248         # "used space" + (20-200 MB). Please also take into account that
249         # Windows might need about 50-100 MB free space left to boot safely.
250         # I'll give 100MB extra space just to be sure
251         querymax -= 100
252
253         if querymax < 0:
254             self.out.warn("Not enought available space to shrink the image!")
255             return
256
257         cmd = (
258             r'cmd /Q /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
259             r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
260             'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
261             'ECHO SHRINK DESIRED=%d >> %%SCRIPT%% & ' % querymax +
262             r'ECHO EXIT >> %SCRIPT% & ' +
263             r'DISKPART /S %SCRIPT% & ' +
264             r'IF ERRORLEVEL 1 EXIT /B 1 & ' +
265             r'DEL /Q %SCRIPT%"')
266
267         stdout, stderr, rc = self._guest_exec(cmd)
268
269         for line in stdout.splitlines():
270             if line.find('shrunk') >= 0:
271                 self.out.output(line)
272
273     def do_sysprep(self):
274         """Prepare system for image creation."""
275
276         if getattr(self, 'syspreped', False):
277             raise FatalError("Image is already syspreped!")
278
279         txt = "System preparation parameter: `%s' is needed but missing!"
280         for param in self.needed_sysprep_params():
281             if param[0] not in self.sysprep_params:
282                 raise FatalError(txt % param[0])
283
284         self.mount(readonly=False)
285         try:
286             disabled_uac = self._update_uac_remote_setting(1)
287             token = self._enable_os_monitor()
288
289             # disable the firewalls
290             firewall_states = self._update_firewalls(0, 0, 0)
291
292             # Delete the pagefile. It will be recreated when the system boots
293             systemroot = self.g.inspect_get_windows_systemroot(self.root)
294             pagefile = "%s/pagefile.sys" % systemroot
295             self.g.rm_rf(self.g.case_sensitive_path(pagefile))
296
297         finally:
298             self.umount()
299
300         self.out.output("Shutting down helper VM ...", False)
301         self.g.sync()
302         # guestfs_shutdown which is the prefered way to shutdown the backend
303         # process was introduced in version 1.19.16
304         if check_guestfs_version(self.g, 1, 19, 16) >= 0:
305             ret = self.g.shutdown()
306         else:
307             ret = self.g.kill_subprocess()
308
309         self.out.success('done')
310
311         vm = None
312         monitor = None
313         try:
314             self.out.output("Starting windows VM ...", False)
315             monitorfd, monitor = tempfile.mkstemp()
316             os.close(monitorfd)
317             vm, display = self._create_vm(monitor)
318             self.out.success("started (console on vnc display: %d)." % display)
319
320             self.out.output("Waiting for OS to boot ...", False)
321             self._wait_vm_boot(vm, monitor, token)
322             self.out.success('done')
323
324             self.out.output("Disabling automatic logon ...", False)
325             self._disable_autologon()
326             self.out.success('done')
327
328             self.out.output('Preparing system from image creation:')
329
330             tasks = self.list_syspreps()
331             enabled = filter(lambda x: x.enabled, tasks)
332             size = len(enabled)
333
334             # Make sure shrink runs in the end, before ms sysprep
335             enabled = filter(lambda x: self.sysprep_info(x).name != 'shrink',
336                              enabled)
337
338             shrink_enabled = False
339             if len(enabled) != size:
340                 enabled.append(self.shrink)
341                 shrink_enabled = True
342
343             # Make sure the ms sysprep is the last task to run if it is enabled
344             enabled = filter(
345                 lambda x: self.sysprep_info(x).name != 'microsoft-sysprep',
346                 enabled)
347
348             ms_sysprep_enabled = False
349             if len(enabled) != size:
350                 enabled.append(self.microsoft_sysprep)
351                 ms_sysprep_enabled = True
352
353             cnt = 0
354             for task in enabled:
355                 cnt += 1
356                 self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False)
357                 task()
358                 setattr(task.im_func, 'executed', True)
359
360             self.out.output("Sending shut down command ...", False)
361             if not ms_sysprep_enabled:
362                 self._shutdown()
363             self.out.success("done")
364
365             self.out.output("Waiting for windows to shut down ...", False)
366             vm.wait()
367             self.out.success("done")
368         finally:
369             if monitor is not None:
370                 os.unlink(monitor)
371
372             if vm is not None:
373                 self._destroy_vm(vm)
374
375             self.out.output("Relaunching helper VM (may take a while) ...",
376                             False)
377             self.g.launch()
378             self.out.success('done')
379
380             self.mount(readonly=False)
381             try:
382                 if disabled_uac:
383                     self._update_uac_remote_setting(0)
384
385                 self._update_firewalls(*firewall_states)
386             finally:
387                 self.umount()
388
389     def _create_vm(self, monitor):
390         """Create a VM with the image attached as the disk
391
392             monitor: a file to be used to monitor when the OS is up
393         """
394
395         def random_mac():
396             mac = [0x00, 0x16, 0x3e,
397                    random.randint(0x00, 0x7f),
398                    random.randint(0x00, 0xff),
399                    random.randint(0x00, 0xff)]
400
401             return ':'.join(map(lambda x: "%02x" % x, mac))
402
403         # Use ganeti's VNC port range for a random vnc port
404         vnc_port = random.randint(11000, 14999)
405         display = vnc_port - 5900
406
407         vm = kvm(
408             '-smp', '1', '-m', '1024', '-drive',
409             'file=%s,format=raw,cache=unsafe,if=virtio' % self.image.device,
410             '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
411             '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(),
412             '-vnc', ':%d' % display, '-serial', 'file:%s' % monitor, _bg=True)
413
414         return vm, display
415
416     def _destroy_vm(self, vm):
417         """Destroy a VM previously created by _create_vm"""
418         if vm.process.alive:
419             vm.terminate()
420
421     def _shutdown(self):
422         """Shuts down the windows VM"""
423         self._guest_exec(r'shutdown /s /t 5')
424
425     def _wait_vm_boot(self, vm, fname, msg):
426         """Wait until a message appears on a file or the vm process dies"""
427
428         for i in range(BOOT_TIMEOUT):
429             time.sleep(1)
430             with open(fname) as f:
431                 for line in f:
432                     if line.startswith(msg):
433                         return True
434             if not vm.process.alive:
435                 raise FatalError("Windows VM died unexpectedly!")
436         raise FatalError("Windows booting timed out!")
437
438     def _disable_autologon(self):
439         """Disable automatic logon on the windows image"""
440
441         winlogon = \
442             r'"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"'
443
444         self._guest_exec('REG DELETE %s /v DefaultUserName /f' % winlogon)
445         self._guest_exec('REG DELETE %s /v DefaultPassword /f' % winlogon)
446         self._guest_exec('REG DELETE %s /v AutoAdminLogon /f' % winlogon)
447
448     def _registry_file_path(self, regfile):
449         """Retrieves the case sensitive path to a registry file"""
450
451         systemroot = self.g.inspect_get_windows_systemroot(self.root)
452         path = "%s/system32/config/%s" % (systemroot, regfile)
453         try:
454             path = self.g.case_sensitive_path(path)
455         except RuntimeError as e:
456             raise FatalError("Unable to retrieve registry file: %s. Reason: %s"
457                              % (regfile, str(e)))
458         return path
459
460     def _enable_os_monitor(self):
461         """Add a script in the registry that will send a random string to the
462         first serial port when the windows image finishes booting.
463         """
464
465         token = "".join(random.choice(string.ascii_letters) for x in range(16))
466
467         path = self._registry_file_path('SOFTWARE')
468         softwarefd, software = tempfile.mkstemp()
469         try:
470             os.close(softwarefd)
471             self.g.download(path, software)
472
473             h = hivex.Hivex(software, write=True)
474
475             # Enable automatic logon.
476             # This is needed because we need to execute a script that we add in
477             # the RunOnce registry entry and those programs only get executed
478             # when a user logs on. There is a RunServicesOnce registry entry
479             # whose keys get executed in the background when the logon dialog
480             # box first appears, but they seem to only work with services and
481             # not arbitrary command line expressions :-(
482             #
483             # Instructions on how to turn on automatic logon in Windows can be
484             # found here: http://support.microsoft.com/kb/324737
485             #
486             # Warning: Registry change will not work if the “Logon Banner” is
487             # defined on the server either by a Group Policy object (GPO) or by
488             # a local policy.
489
490             winlogon = h.root()
491             for child in ('Microsoft', 'Windows NT', 'CurrentVersion',
492                           'Winlogon'):
493                 winlogon = h.node_get_child(winlogon, child)
494
495             h.node_set_value(
496                 winlogon,
497                 {'key': 'DefaultUserName', 't': 1,
498                  'value': "Administrator".encode('utf-16le')})
499             h.node_set_value(
500                 winlogon,
501                 {'key': 'DefaultPassword', 't': 1,
502                  'value':  self.sysprep_params['password'].encode('utf-16le')})
503             h.node_set_value(
504                 winlogon,
505                 {'key': 'AutoAdminLogon', 't': 1,
506                  'value': "1".encode('utf-16le')})
507
508             key = h.root()
509             for child in ('Microsoft', 'Windows', 'CurrentVersion'):
510                 key = h.node_get_child(key, child)
511
512             runonce = h.node_get_child(key, "RunOnce")
513             if runonce is None:
514                 runonce = h.node_add_child(key, "RunOnce")
515
516             value = (
517                 r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe '
518                 r'-ExecutionPolicy RemoteSigned '
519                 r'"&{$port=new-Object System.IO.Ports.SerialPort COM1,9600,'
520                 r'None,8,one;$port.open();$port.WriteLine(\"' + token + r'\");'
521                 r'$port.Close()}"').encode('utf-16le')
522
523             h.node_set_value(runonce,
524                              {'key': "BootMonitor", 't': 1, 'value': value})
525
526             value = (
527                 r'REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion'
528                 r'\policies\system /v LocalAccountTokenFilterPolicy'
529                 r' /t REG_DWORD /d 1 /f').encode('utf-16le')
530
531             h.node_set_value(runonce,
532                              {'key': "UpdateRegistry", 't': 1, 'value': value})
533
534             h.commit(None)
535
536             self.g.upload(software, path)
537         finally:
538             os.unlink(software)
539
540         return token
541
542     def _update_firewalls(self, domain, public, standard):
543         """Enables or disables the firewall for the Domain, the Public and the
544         Standard profile. Returns a triplete with the old values.
545
546         1 will enable a firewall and 0 will disable it
547         """
548
549         if domain not in (0, 1):
550             raise ValueError("Valid values for domain parameter are 0 and 1")
551
552         if public not in (0, 1):
553             raise ValueError("Valid values for public parameter are 0 and 1")
554
555         if standard not in (0, 1):
556             raise ValueError("Valid values for standard parameter are 0 and 1")
557
558         path = self._registry_file_path("SYSTEM")
559         systemfd, system = tempfile.mkstemp()
560         try:
561             os.close(systemfd)
562             self.g.download(path, system)
563
564             h = hivex.Hivex(system, write=True)
565
566             select = h.node_get_child(h.root(), 'Select')
567             current_value = h.node_get_value(select, 'Current')
568
569             # expecting a little endian dword
570             assert h.value_type(current_value)[1] == 4
571             current = "%03d" % h.value_dword(current_value)
572
573             firewall_policy = h.root()
574             for child in ('ControlSet%s' % current, 'services', 'SharedAccess',
575                           'Parameters', 'FirewallPolicy'):
576                 firewall_policy = h.node_get_child(firewall_policy, child)
577
578             old_values = []
579             new_values = [domain, public, standard]
580             for profile in ('Domain', 'Public', 'Standard'):
581                 node = h.node_get_child(firewall_policy, '%sProfile' % profile)
582
583                 old_value = h.node_get_value(node, 'EnableFirewall')
584
585                 # expecting a little endian dword
586                 assert h.value_type(old_value)[1] == 4
587                 old_values.append(h.value_dword(old_value))
588
589                 h.node_set_value(
590                     node, {'key': 'EnableFirewall', 't': 4L,
591                            'value': struct.pack("<I", new_values.pop(0))})
592
593             h.commit(None)
594             self.g.upload(system, path)
595
596         finally:
597             os.unlink(system)
598
599         return old_values
600
601     def _update_uac_remote_setting(self, value):
602         """Updates the registry key value:
603         [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
604         \System]"LocalAccountTokenFilterPolicy"
605
606         value = 1 will disable the UAC remote restrictions
607         value = 0 will enable the UAC remote restrictions
608
609         For more info see here: http://support.microsoft.com/kb/951016
610
611         Returns:
612             True if the key is changed
613             False if the key is unchanged
614         """
615
616         if value not in (0, 1):
617             raise ValueError("Valid values for value parameter are 0 and 1")
618
619         path = self._registry_file_path('SOFTWARE')
620         softwarefd, software = tempfile.mkstemp()
621         try:
622             os.close(softwarefd)
623             self.g.download(path, software)
624
625             h = hivex.Hivex(software, write=True)
626
627             key = h.root()
628             for child in ('Microsoft', 'Windows', 'CurrentVersion', 'Policies',
629                           'System'):
630                 key = h.node_get_child(key, child)
631
632             policy = None
633             for val in h.node_values(key):
634                 if h.value_key(val) == "LocalAccountTokenFilterPolicy":
635                     policy = val
636
637             if policy is not None:
638                 dword = h.value_dword(policy)
639                 if dword == value:
640                     return False
641             elif value == 0:
642                 return False
643
644             new_value = {'key': "LocalAccountTokenFilterPolicy", 't': 4L,
645                          'value': struct.pack("<I", value)}
646
647             h.node_set_value(key, new_value)
648             h.commit(None)
649
650             self.g.upload(software, path)
651
652         finally:
653             os.unlink(software)
654
655         return True
656
657     def _do_collect_metadata(self):
658         """Collect metadata about the OS"""
659         super(Windows, self)._do_collect_metadata()
660         self.meta["USERS"] = " ".join(self._get_users())
661
662     def _get_users(self):
663         """Returns a list of users found in the images"""
664         path = self._registry_file_path('SAM')
665         samfd, sam = tempfile.mkstemp()
666         try:
667             os.close(samfd)
668             self.g.download(path, sam)
669
670             h = hivex.Hivex(sam)
671
672             key = h.root()
673             # Navigate to /SAM/Domains/Account/Users/Names
674             for child in ('SAM', 'Domains', 'Account', 'Users', 'Names'):
675                 key = h.node_get_child(key, child)
676
677             users = [h.node_name(x) for x in h.node_children(key)]
678
679         finally:
680             os.unlink(sam)
681
682         # Filter out the guest account
683         return filter(lambda x: x != "Guest", users)
684
685     def _guest_exec(self, command, fatal=True):
686         """Execute a command on a windows VM"""
687
688         passwd = self.sysprep_params['password']
689
690         winexe = WinEXE('Administrator', passwd, 'localhost')
691         winexe.runas('Administrator', passwd).uninstall()
692
693         try:
694             (stdout, stderr, rc) = winexe.run(command)
695         except WinexeTimeout:
696             FatalError("Command: `%s' timeout out." % command)
697
698         if rc != 0 and fatal:
699             reason = stderr if len(stderr) else stdout
700             self.out.output("Command: `%s' failed (rc=%d). Reason: %s" %
701                             (command, rc, reason))
702             raise FatalError("Command: `%s' failed (rc=%d). Reason: %s" %
703                              (command, rc, reason))
704
705         return (stdout, stderr, rc)
706
707 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :