Statistics
| Branch: | Tag: | Revision:

root / image_creator / os_type / windows.py @ 12c97404

History | View | Annotate | Download (10.1 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 subprocess
48

    
49
kvm = get_command('kvm')
50

    
51

    
52
class Windows(OSBase):
53
    """OS class for Windows"""
54

    
55
    def needed_sysprep_params(self):
56
        """Returns a list of needed sysprep parameters. Each element in the
57
        list is a SysprepParam object.
58
        """
59

    
60
        password = self.SysprepParam(
61
            'password', 'Image Administrator Password', 20, lambda x: True)
62

    
63
        return [password]
64

    
65
    @sysprep(enabled=True)
66
    def disable_ipv6_privacy_extensions(self, print_header=True):
67
        """Disable IPv6 privacy extensions"""
68

    
69
        if print_header:
70
            self.out.output("Disabling IPv6 privacy extensions")
71

    
72
        out, err, rc = self._guest_exec(
73
            'netsh interface ipv6 set global randomizeidentifiers=disabled '
74
            'store=persistent')
75

    
76
        if rc != 0:
77
            raise FatalError("Unable to disable IPv6 privacy extensions: %s" %
78
                             err)
79

    
80
    @sysprep(enabled=True)
81
    def microsoft_sysprep(self, print_header=True):
82
        """Run the Micorsoft System Preparation Tool on the Image. After this
83
        runs, no other task may run.
84
        """
85

    
86
        if print_header:
87
            self.out.output("Executing sysprep on the image (may take more "
88
                            "than 10 minutes)")
89

    
90
        out, err, rc = self._guest_exec(r'C:\windows\system32\sysprep\sysprep '
91
                                        r'/quiet /generalize /oobe /shutdown')
92
        self.syspreped = True
93
        if rc != 0:
94
            raise FatalError("Unable to perform sysprep: %s" % err)
95

    
96
    def do_sysprep(self):
97
        """Prepare system for image creation."""
98

    
99
        if getattr(self, 'syspreped', False):
100
            raise FatalError("Image is already syspreped!")
101

    
102
        txt = "System preparation parameter: `%s' is needed but missing!"
103
        for param in self.needed_sysprep_params():
104
            if param[0] not in self.sysprep_params:
105
                raise FatalError(txt % param[0])
106

    
107
        self.mount(readonly=False)
108
        try:
109
            disabled_uac = self._update_uac_remote_setting(1)
110
        finally:
111
            self.umount()
112

    
113
        self.out.output("Shutting down helper VM ...", False)
114
        self.g.sync()
115
        # guestfs_shutdown which is the prefered way to shutdown the backend
116
        # process was introduced in version 1.19.16
117
        if check_guestfs_version(self.g, 1, 19, 16) >= 0:
118
            ret = self.g.shutdown()
119
        else:
120
            ret = self.g.kill_subprocess()
121

    
122
        self.out.success('done')
123
        try:
124
            self.out.output("Starting windows VM ...", False)
125

    
126
            def random_mac():
127
                mac = [0x00, 0x16, 0x3e,
128
                       random.randint(0x00, 0x7f),
129
                       random.randint(0x00, 0xff),
130
                       random.randint(0x00, 0xff)]
131
                return ':'.join(map(lambda x: "%02x" % x, mac))
132

    
133
            vm = kvm('-smp', '1', '-m', '1024', '-drive',
134
                     'file=%s,format=raw,cache=none,if=virtio' %
135
                     self.image.device,
136
                     '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
137
                     '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' %
138
                     random_mac(), '-vnc', ':0', _bg=True)
139
            time.sleep(60)
140
            self.out.success('done')
141

    
142
            tasks = self.list_syspreps()
143
            enabled = filter(lambda x: x.enabled, tasks)
144

    
145
            size = len(enabled)
146

    
147
            # Make sure the ms sysprep is the last task to run if it is enabled
148
            enabled = filter(
149
                lambda x: x.im_func.func_name != 'microsoft_sysprep', enabled)
150

    
151
            ms_sysprep_enabled = False
152
            if len(enabled) != size:
153
                enabled.append(self.ms_sysprep)
154
                ms_sysprep_enabled = True
155

    
156
            cnt = 0
157
            for task in enabled:
158
                cnt += 1
159
                self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False)
160
                task()
161
                setattr(task.im_func, 'executed', True)
162

    
163
            if not ms_sysprep_enabled:
164
                self._shutdown()
165

    
166
            vm.wait()
167
        finally:
168
            if vm.process.alive:
169
                vm.terminate()
170

    
171
            self.out.output("Relaunching helper VM (may take a while) ...",
172
                            False)
173
            self.g.launch()
174
            self.out.success('done')
175

    
176
        if disabled_uac:
177
            self._update_uac_remote_setting(0)
178

    
179
    def _shutdown(self):
180
        """Shuts down the windows VM"""
181

    
182
        self.out.output("Shutting down windows VM ...", False)
183
        out, err, rc = self._guest_exec(r'shutdown /s /t 5')
184

    
185
        if rc != 0:
186
            raise FatalError("Unable to perform shutdown: %s" % err)
187

    
188
        self.out.success('done')
189

    
190
    def _registry_file_path(self, regfile):
191
        """Retrieves the case sensitive path to a registry file"""
192

    
193
        systemroot = self.g.inspect_get_windows_systemroot(self.root)
194
        path = "%s/system32/config/%s" % (systemroot, regfile)
195
        try:
196
            path = self.g.case_sensitive_path(path)
197
        except RuntimeError as e:
198
            raise FatalError("Unable to retrieve registry file: %s. Reason: %s"
199
                             % (regfile, str(e)))
200
        return path
201

    
202
    def _update_uac_remote_setting(self, value):
203
        """Updates the registry key value:
204
        [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
205
        \System]"LocalAccountTokenFilterPolicy"
206

207
        value = 1 will disable the UAC remote restrictions
208
        value = 0 will enable the UAC remote restrictions
209

210
        For more info see here: http://support.microsoft.com/kb/951016
211

212
        Returns:
213
            True if the key is changed
214
            False if the key is unchanged
215
        """
216

    
217
        if value not in (0, 1):
218
            raise ValueError("Valid values for value parameter are 0 and 1")
219

    
220
        path = self._registry_file_path('SOFTWARE')
221
        softwarefd, software = tempfile.mkstemp()
222
        try:
223
            os.close(softwarefd)
224
            self.g.download(path, software)
225

    
226
            h = hivex.Hivex(software, write=True)
227

    
228
            key = h.root()
229
            for child in ('Microsoft', 'Windows', 'CurrentVersion', 'Policies',
230
                          'System'):
231
                key = h.node_get_child(key, child)
232

    
233
            policy = None
234
            for val in h.node_values(key):
235
                if h.value_key(val) == "LocalAccountTokenFilterPolicy":
236
                    policy = val
237

    
238
            if policy is not None:
239
                dword = h.value_dword(policy)
240
                if dword == value:
241
                    return False
242
            elif value == 0:
243
                return False
244

    
245
            new_value = {
246
                'key': "LocalAccountTokenFilterPolicy", 't': 4L,
247
                'value': '%s\x00\x00\x00' % '\x00' if value == 0 else '\x01'}
248

    
249
            h.node_set_value(key, new_value)
250
            h.commit(None)
251

    
252
            self.g.upload(software, path)
253

    
254
        finally:
255
            os.unlink(software)
256

    
257
        return True
258

    
259
    def _do_collect_metadata(self):
260
        """Collect metadata about the OS"""
261
        super(Windows, self)._do_collect_metadata()
262
        self.meta["USERS"] = " ".join(self._get_users())
263

    
264
    def _get_users(self):
265
        """Returns a list of users found in the images"""
266
        path = self._registry_file_path('SAM')
267
        samfd, sam = tempfile.mkstemp()
268
        try:
269
            os.close(samfd)
270
            self.g.download(path, sam)
271

    
272
            h = hivex.Hivex(sam)
273

    
274
            key = h.root()
275
            # Navigate to /SAM/Domains/Account/Users/Names
276
            for child in ('SAM', 'Domains', 'Account', 'Users', 'Names'):
277
                key = h.node_get_child(key, child)
278

    
279
            users = [h.node_name(x) for x in h.node_children(key)]
280

    
281
        finally:
282
            os.unlink(sam)
283

    
284
        # Filter out the guest account
285
        return filter(lambda x: x != "Guest", users)
286

    
287
    def _guest_exec(self, command):
288
        user = "Administrator%" + self.sysprep_params['password']
289
        addr = 'localhost'
290
        runas = '--runas=%s' % user
291
        winexe = subprocess.Popen(
292
            ['winexe', '-U', user, "//%s" % addr, runas, command],
293
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
294

    
295
        result = winexe.communicate()
296
        rc = winexe.poll()
297

    
298
        return (result[0], result[1], rc)
299

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