Mix minor typos
[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
42 import hivex
43 import tempfile
44 import os
45 import time
46 import random
47 import string
48 import subprocess
49 import struct
50
51 kvm = get_command('kvm')
52
53 BOOT_TIMEOUT = 300
54
55 # For more info see: http://technet.microsoft.com/en-us/library/jj612867.aspx
56 KMS_CLIENT_SETUP_KEYS = {
57     "Windows 8 Professional": "NG4HW-VH26C-733KW-K6F98-J8CK4",
58     "Windows 8 Professional N": "XCVCF-2NXM9-723PB-MHCB7-2RYQQ",
59     "Windows 8 Enterprise": "32JNW-9KQ84-P47T8-D8GGY-CWCK7",
60     "Windows 8 Enterprise N": "JMNMF-RHW7P-DMY6X-RF3DR-X2BQT",
61     "Windows Server 2012 Core": "BN3D2-R7TKB-3YPBD-8DRP2-27GG4",
62     "Windows Server 2012 Core N": "8N2M2-HWPGY-7PGT9-HGDD8-GVGGY",
63     "Windows Server 2012 Core Single Language":
64     "2WN2H-YGCQR-KFX6K-CD6TF-84YXQ",
65     "Windows Server 2012 Core Country Specific":
66     "4K36P-JN4VD-GDC6V-KDT89-DYFKP",
67     "Windows Server 2012 Server Standard": "XC9B7-NBPP2-83J2H-RHMBY-92BT4",
68     "Windows Server 2012 Standard Core": "XC9B7-NBPP2-83J2H-RHMBY-92BT4",
69     "Windows Server 2012 MultiPoint Standard": "HM7DN-YVMH3-46JC3-XYTG7-CYQJJ",
70     "Windows Server 2012 MultiPoint Premium": "XNH6W-2V9GX-RGJ4K-Y8X6F-QGJ2G",
71     "Windows Server 2012 Datacenter": "48HP8-DN98B-MYWDG-T2DCC-8W83P",
72     "Windows Server 2012 Datacenter Core": "48HP8-DN98B-MYWDG-T2DCC-8W83P",
73     "Windows 7 Professional": "FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4",
74     "Windows 7 Professional N": "MRPKT-YTG23-K7D7T-X2JMM-QY7MG",
75     "Windows 7 Professional E": "W82YF-2Q76Y-63HXB-FGJG9-GF7QX",
76     "Windows 7 Enterprise": "33PXH-7Y6KF-2VJC9-XBBR8-HVTHH",
77     "Windows 7 Enterprise N": "YDRBP-3D83W-TY26F-D46B2-XCKRJ",
78     "Windows 7 Enterprise E": "C29WB-22CC8-VJ326-GHFJW-H9DH4",
79     "Windows Server 2008 R2 Web": "6TPJF-RBVHG-WBW2R-86QPH-6RTM4",
80     "Windows Server 2008 R2 HPC edition": "TT8MH-CG224-D3D7Q-498W2-9QCTX",
81     "Windows Server 2008 R2 Standard": "YC6KT-GKW9T-YTKYR-T4X34-R7VHC",
82     "Windows Server 2008 R2 Enterprise": "489J6-VHDMP-X63PK-3K798-CPX3Y",
83     "Windows Server 2008 R2 Datacenter": "74YFP-3QFB3-KQT8W-PMXWJ-7M648",
84     "Windows Server 2008 R2 for Itanium-based Systems":
85     "GT63C-RJFQ3-4GMB6-BRFB9-CB83V",
86     "Windows Vista Business": "YFKBB-PQJJV-G996G-VWGXY-2V3X8",
87     "Windows Vista Business N": "HMBQG-8H2RH-C77VX-27R82-VMQBT",
88     "Windows Vista Enterprise": "VKK3X-68KWM-X2YGT-QR4M6-4BWMV",
89     "Windows Vista Enterprise N": "VTC42-BM838-43QHV-84HX6-XJXKV",
90     "Windows Web Server 2008": "WYR28-R7TFJ-3X2YQ-YCY4H-M249D",
91     "Windows Server 2008 Standard": "TM24T-X9RMF-VWXK6-X8JC9-BFGM2",
92     "Windows Server 2008 Standard without Hyper-V":
93     "W7VD6-7JFBR-RX26B-YKQ3Y-6FFFJ",
94     "Windows Server 2008 Enterprise":
95     "YQGMW-MPWTJ-34KDK-48M3W-X4Q6V",
96     "Windows Server 2008 Enterprise without Hyper-V":
97     "39BXF-X8Q23-P2WWT-38T2F-G3FPG",
98     "Windows Server 2008 HPC": "RCTX3-KWVHP-BR6TB-RB6DM-6X7HP",
99     "Windows Server 2008 Datacenter": "7M67G-PC374-GR742-YH8V4-TCBY3",
100     "Windows Server 2008 Datacenter without Hyper-V":
101     "22XQ2-VRXRG-P8D42-K34TD-G3QQC",
102     "Windows Server 2008 for Itanium-Based Systems":
103     "4DWFP-JF3DJ-B7DTH-78FJB-PDRHK"}
104
105
106 class Windows(OSBase):
107     """OS class for Windows"""
108     def __init__(self, image, **kargs):
109         super(Windows, self).__init__(image, **kargs)
110
111         device = self.g.part_to_dev(self.root)
112
113         self.last_part_num = self.g.part_list(device)[-1]['part_num']
114         self.last_drive = None
115         self.system_drive = None
116
117         for drive, partition in self.g.inspect_get_drive_mappings(self.root):
118             if partition == "%s%d" % (device, self.last_part_num):
119                 self.last_drive = drive
120             if partition == self.root:
121                 self.system_drive = drive
122
123         assert self.system_drive
124
125         self.product_name = self.g.inspect_get_product_name(self.root)
126
127     def needed_sysprep_params(self):
128         """Returns a list of needed sysprep parameters. Each element in the
129         list is a SysprepParam object.
130         """
131         password = self.SysprepParam(
132             'password', 'Image Administrator Password', 20, lambda x: True)
133
134         return [password]
135
136     @sysprep('Disabling IPv6 privacy extensions')
137     def disable_ipv6_privacy_extensions(self):
138         """Disable IPv6 privacy extensions"""
139
140         self._guest_exec('netsh interface ipv6 set global '
141                          'randomizeidentifiers=disabled store=persistent')
142
143     @sysprep('Disabling Teredo interface')
144     def disable_teredo(self):
145         """Disable Teredo interface"""
146
147         self._guest_exec('netsh interface teredo set state disabled')
148
149     @sysprep('Disabling ISATAP Adapters')
150     def disable_isatap(self):
151         """Disable ISATAP Adapters"""
152
153         self._guest_exec('netsh interface isa set state disabled')
154
155     @sysprep('Enabling ping responses')
156     def enable_pings(self):
157         """Enable ping responses"""
158
159         self._guest_exec('netsh firewall set icmpsetting 8')
160
161     @sysprep('Disabling hibernation support')
162     def disable_hibernation(self):
163         """Disable hibernation support and remove the hibernation file"""
164
165         self._guest_exec(r'powercfg.exe /hibernate off')
166
167     @sysprep('Setting the system clock to UTC')
168     def utc(self):
169         """Set the hardware clock to UTC"""
170
171         path = r'HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
172         self._guest_exec(
173             r'REG ADD %s /v RealTimeIsUniversal /t REG_DWORD /d 1 /f' % path)
174
175     @sysprep('Clearing the event logs')
176     def clear_logs(self):
177         """Clear all the event logs"""
178
179         self._guest_exec(
180             r"cmd /q /c for /f %l in ('wevtutil el') do wevtutil cl %l")
181
182     @sysprep('Executing Sysprep on the image (may take more that 10 minutes)')
183     def microsoft_sysprep(self):
184         """Run the Microsoft System Preparation Tool. This will remove
185         system-specific data and will make the image ready to be deployed.
186         After this no other task may run.
187         """
188
189         self._guest_exec(r'C:\Windows\system32\sysprep\sysprep '
190                          r'/quiet /generalize /oobe /shutdown')
191         self.syspreped = True
192
193     @sysprep('Converting the image into a KMS client', enabled=False)
194     def kms_client_setup(self):
195         """Install the appropriate KMS client setup key to the image to convert
196         it to a KMS client. Computers that are running volume licensing
197         editions of Windows 8, Windows Server 2012, Windows 7, Windows Server
198         2008 R2, Windows Vista, and Windows Server 2008 are, by default, KMS
199         clients with no additional configuration needed.
200         """
201         try:
202             setup_key = KMS_CLIENT_SETUP_KEYS[self.product_name]
203         except KeyError:
204             self.out.warn(
205                 "Don't know the KMS client setup key for product: `%s'" %
206                 self.product_name)
207             return
208
209         self._guest_exec(
210             "cscript \Windows\system32\slmgr.vbs /ipk %s" % setup_key)
211
212     @sysprep('Shrinking the last filesystem')
213     def shrink(self):
214         """Shrink the last filesystem. Make sure the filesystem is defragged"""
215
216         # Query for the maximum number of reclaimable bytes
217         cmd = (
218             r'cmd /Q /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
219             r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
220             'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
221             r'ECHO SHRINK QUERYMAX >> %SCRIPT% & ' +
222             r'ECHO EXIT >> %SCRIPT% & ' +
223             r'DISKPART /S %SCRIPT% & ' +
224             r'IF ERRORLEVEL 1 EXIT /B 1 & ' +
225             r'DEL /Q %SCRIPT%"')
226
227         stdout, stderr, rc = self._guest_exec(cmd)
228
229         querymax = None
230         for line in stdout.splitlines():
231             # diskpart will return something like this:
232             #
233             #   The maximum number of reclaimable bytes is: xxxx MB
234             #
235             if line.find('reclaimable') >= 0:
236                 querymax = line.split(':')[1].split()[0].strip()
237                 assert querymax.isdigit(), \
238                     "Number of reclaimable bytes not a number"
239
240         if querymax is None:
241             FatalError("Error in shrinking! "
242                        "Couldn't find the max number of reclaimable bytes!")
243
244         querymax = int(querymax)
245         # From ntfsresize:
246         # Practically the smallest shrunken size generally is at around
247         # "used space" + (20-200 MB). Please also take into account that
248         # Windows might need about 50-100 MB free space left to boot safely.
249         # I'll give 100MB extra space just to be sure
250         querymax -= 100
251
252         if querymax < 0:
253             self.out.warn("Not enought available space to shrink the image!")
254             return
255
256         cmd = (
257             r'cmd /Q /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
258             r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
259             'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
260             'ECHO SHRINK DESIRED=%d >> %%SCRIPT%% & ' % querymax +
261             r'ECHO EXIT >> %SCRIPT% & ' +
262             r'DISKPART /S %SCRIPT% & ' +
263             r'IF ERRORLEVEL 1 EXIT /B 1 & ' +
264             r'DEL /Q %SCRIPT%"')
265
266         stdout, stderr, rc = self._guest_exec(cmd)
267
268         for line in stdout.splitlines():
269             if line.find('shrunk') >= 0:
270                 self.out.output(line)
271
272     def do_sysprep(self):
273         """Prepare system for image creation."""
274
275         if getattr(self, 'syspreped', False):
276             raise FatalError("Image is already syspreped!")
277
278         txt = "System preparation parameter: `%s' is needed but missing!"
279         for param in self.needed_sysprep_params():
280             if param[0] not in self.sysprep_params:
281                 raise FatalError(txt % param[0])
282
283         self.mount(readonly=False)
284         try:
285             disabled_uac = self._update_uac_remote_setting(1)
286             token = self._enable_os_monitor()
287
288             # disable the firewalls
289             firewall_states = self._update_firewalls(0, 0, 0)
290
291             # Delete the pagefile. It will be recreated when the system boots
292             systemroot = self.g.inspect_get_windows_systemroot(self.root)
293             pagefile = "%s/pagefile.sys" % systemroot
294             self.g.rm_rf(self.g.case_sensitive_path(pagefile))
295
296         finally:
297             self.umount()
298
299         self.out.output("Shutting down helper VM ...", False)
300         self.g.sync()
301         # guestfs_shutdown which is the prefered way to shutdown the backend
302         # process was introduced in version 1.19.16
303         if check_guestfs_version(self.g, 1, 19, 16) >= 0:
304             ret = self.g.shutdown()
305         else:
306             ret = self.g.kill_subprocess()
307
308         self.out.success('done')
309
310         vm = None
311         monitor = None
312         try:
313             self.out.output("Starting windows VM ...", False)
314             monitorfd, monitor = tempfile.mkstemp()
315             os.close(monitorfd)
316             vm, display = self._create_vm(monitor)
317             self.out.success("started (console on vnc display: %d)." % display)
318
319             self.out.output("Waiting for OS to boot ...", False)
320             if not self._wait_on_file(monitor, token):
321                 raise FatalError("Windows booting timed out.")
322             else:
323                 time.sleep(10)  # Just to be sure everything is up
324                 self.out.success('done')
325
326             self.out.output("Disabling automatic logon ...", False)
327             self._disable_autologon()
328             self.out.success('done')
329
330             self.out.output('Preparing system from image creation:')
331
332             tasks = self.list_syspreps()
333             enabled = filter(lambda x: x.enabled, tasks)
334             size = len(enabled)
335
336             # Make sure shrink runs in the end, before ms sysprep
337             enabled = filter(lambda x: self.sysprep_info(x).name != 'shrink',
338                              enabled)
339
340             shrink_enabled = False
341             if len(enabled) != size:
342                 enabled.append(self.shrink)
343                 shrink_enabled = True
344
345             # Make sure the ms sysprep is the last task to run if it is enabled
346             enabled = filter(
347                 lambda x: self.sysprep_info(x).name != 'microsoft-sysprep',
348                 enabled)
349
350             ms_sysprep_enabled = False
351             if len(enabled) != size:
352                 enabled.append(self.microsoft_sysprep)
353                 ms_sysprep_enabled = True
354
355             cnt = 0
356             for task in enabled:
357                 cnt += 1
358                 self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False)
359                 task()
360                 setattr(task.im_func, 'executed', True)
361
362             self.out.output("Sending shut down command ...", False)
363             if not ms_sysprep_enabled:
364                 self._shutdown()
365             self.out.success("done")
366
367             self.out.output("Waiting for windows to shut down ...", False)
368             vm.wait()
369             self.out.success("done")
370         finally:
371             if monitor is not None:
372                 os.unlink(monitor)
373
374             if vm is not None:
375                 self._destroy_vm(vm)
376
377             self.out.output("Relaunching helper VM (may take a while) ...",
378                             False)
379             self.g.launch()
380             self.out.success('done')
381
382             self.mount(readonly=False)
383             try:
384                 if disabled_uac:
385                     self._update_uac_remote_setting(0)
386
387                 self._update_firewalls(*firewall_states)
388             finally:
389                 self.umount()
390
391     def _create_vm(self, monitor):
392         """Create a VM with the image attached as the disk
393
394             monitor: a file to be used to monitor when the OS is up
395         """
396
397         def random_mac():
398             mac = [0x00, 0x16, 0x3e,
399                    random.randint(0x00, 0x7f),
400                    random.randint(0x00, 0xff),
401                    random.randint(0x00, 0xff)]
402
403             return ':'.join(map(lambda x: "%02x" % x, mac))
404
405         # Use ganeti's VNC port range for a random vnc port
406         vnc_port = random.randint(11000, 14999)
407         display = vnc_port - 5900
408
409         vm = kvm(
410             '-smp', '1', '-m', '1024', '-drive',
411             'file=%s,format=raw,cache=unsafe,if=virtio' % self.image.device,
412             '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
413             '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(),
414             '-vnc', ':%d' % display, '-serial', 'file:%s' % monitor, _bg=True)
415
416         return vm, display
417
418     def _destroy_vm(self, vm):
419         """Destroy a VM previously created by _create_vm"""
420         if vm.process.alive:
421             vm.terminate()
422
423     def _shutdown(self):
424         """Shuts down the windows VM"""
425         self._guest_exec(r'shutdown /s /t 5')
426
427     def _wait_on_file(self, fname, msg):
428         """Wait until a message appears on a file"""
429
430         for i in range(BOOT_TIMEOUT):
431             time.sleep(1)
432             with open(fname) as f:
433                 for line in f:
434                     if line.startswith(msg):
435                         return True
436         return False
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         user = "Administrator%" + self.sysprep_params['password']
689         addr = 'localhost'
690         runas = '--runas=%s' % user
691         winexe = subprocess.Popen(
692             ['winexe', '-U', user, runas, "--uninstall", "//%s" % addr,
693              command], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
694
695         stdout, stderr = winexe.communicate()
696         rc = winexe.poll()
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 :