Statistics
| Branch: | Tag: | Revision:

root / image_creator / os_type / windows.py @ 7dc92081

History | View | Annotate | Download (26.2 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

    
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 responces"""
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. Reason: %s" %
701
                            (command, reason))
702
            raise FatalError("Command: `%s' failed. Reason: %s" %
703
                             (command, reason))
704

    
705
        return (stdout, stderr, rc)
706

    
707
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :