Statistics
| Branch: | Tag: | Revision:

root / image_creator / os_type / windows.py @ 6342c1ab

History | View | Annotate | Download (26.3 kB)

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 :