Esempio n. 1
0
    def __init__(self, image, **kwargs):
        super(Windows, self).__init__(image, **kwargs)

        # The commit with the following message was added in
        # libguestfs 1.17.18 and was backported in version 1.16.11:
        #
        # When a Windows guest doesn't have a HKLM\SYSTEM\MountedDevices node,
        # inspection fails.  However inspection should not completely fail just
        # because we cannot get the drive letter mapping from a guest.
        #
        # Since Microsoft Sysprep removes the aforementioned key, image
        # creation for windows can only be supported if the installed guestfs
        # version is 1.17.18 or higher
        if self.image.check_guestfs_version(1, 17, 18) < 0 and \
                (self.image.check_guestfs_version(1, 17, 0) >= 0 or
                 self.image.check_guestfs_version(1, 16, 11) < 0):
            raise FatalError(
                'For windows support libguestfs 1.16.11 or above is required')

        device = self.image.g.part_to_dev(self.root)

        self.last_part_num = self.image.g.part_list(device)[-1]['part_num']

        self.product_name = self.image.g.inspect_get_product_name(self.root)
        self.systemroot = self.image.g.inspect_get_windows_systemroot(
            self.root)

        self.registry = Registry(self.image)

        with self.mount(readonly=True, silent=True):
            self.out.info("Checking media state ...", False)

            # Enumerate the windows users
            (self.usernames,
             self.active_users,
             self.admins) = self.registry.enum_users()
            assert ADMIN_RID in self.usernames, "Administrator account missing"

            self.virtio_state = self.compute_virtio_state()

            self.arch = self.image.g.inspect_get_arch(self.root)
            if self.arch == 'x86_64':
                self.arch = 'amd64'
            elif self.arch == 'i386':
                self.arch = 'x86'
            major = int(self.image.g.inspect_get_major_version(self.root))
            minor = int(self.image.g.inspect_get_minor_version(self.root))
            self.nt_version = (major, minor)

            # The get_setup_state() command does not work for old windows
            if self.nt_version[0] >= 6:
                # If the image is already sysprepped, we cannot further
                # customize it.
                self.sysprepped = self.registry.get_setup_state() > 0
            else:
                # Fallback to NO although we done know
                # TODO: Add support for detecting the setup state on XP
                self.sysprepped = False

            self.out.success("done")

        # If the image is sysprepped no driver mappings will be present.
        self.systemdrive = None
        for drive, root in self.image.g.inspect_get_drive_mappings(self.root):
            if root == self.root:
                self.systemdrive = drive

        active_admins = [u for u in self.admins if u in self.active_users]
        if ADMIN_RID in self.active_users or len(active_admins) == 0:
            admin = ADMIN_RID
        else:
            active_admins.sort()
            admin = active_admins[0]

        self.vm = VM(
            self.image.device, self.sysprep_params,
            namedtuple('User', 'rid name')(admin, self.usernames[admin]))
Esempio n. 2
0
    def __init__(self, image, **kwargs):
        super(Windows, self).__init__(image, **kwargs)

        # The commit with the following message was added in
        # libguestfs 1.17.18 and was backported in version 1.16.11:
        #
        # When a Windows guest doesn't have a HKLM\SYSTEM\MountedDevices node,
        # inspection fails.  However inspection should not completely fail just
        # because we cannot get the drive letter mapping from a guest.
        #
        # Since Microsoft Sysprep removes the aforementioned key, image
        # creation for windows can only be supported if the installed guestfs
        # version is 1.17.18 or higher
        if self.image.check_guestfs_version(1, 17, 18) < 0 and \
                (self.image.check_guestfs_version(1, 17, 0) >= 0 or
                 self.image.check_guestfs_version(1, 16, 11) < 0):
            raise FatalError(
                'For windows support libguestfs 1.16.11 or above is required')

        device = self.image.g.part_to_dev(self.root)

        self.last_part_num = self.image.g.part_list(device)[-1]['part_num']

        self.product_name = self.image.g.inspect_get_product_name(self.root)
        self.systemroot = self.image.g.inspect_get_windows_systemroot(
            self.root)

        self.registry = Registry(self.image)

        with self.mount(readonly=True, silent=True):
            self.out.info("Checking media state ...", False)

            # Enumerate the windows users
            (self.usernames, self.active_users,
             self.admins) = self.registry.enum_users()
            assert ADMIN_RID in self.usernames, "Administrator account missing"

            self.virtio_state = self.compute_virtio_state()

            self.arch = self.image.g.inspect_get_arch(self.root)
            if self.arch == 'x86_64':
                self.arch = 'amd64'
            elif self.arch == 'i386':
                self.arch = 'x86'
            major = int(self.image.g.inspect_get_major_version(self.root))
            minor = int(self.image.g.inspect_get_minor_version(self.root))
            self.nt_version = (major, minor)

            # The get_setup_state() command does not work for old windows
            if self.nt_version[0] >= 6:
                # If the image is already sysprepped, we cannot further
                # customize it.
                self.sysprepped = self.registry.get_setup_state() > 0
            else:
                # Fallback to NO although we done know
                # TODO: Add support for detecting the setup state on XP
                self.sysprepped = False

            self.out.success("done")

        # If the image is sysprepped no driver mappings will be present.
        self.systemdrive = None
        for drive, root in self.image.g.inspect_get_drive_mappings(self.root):
            if root == self.root:
                self.systemdrive = drive

        active_admins = [u for u in self.admins if u in self.active_users]
        if ADMIN_RID in self.active_users or len(active_admins) == 0:
            admin = ADMIN_RID
        else:
            active_admins.sort()
            admin = active_admins[0]

        self.vm = VM(
            self.image.device, self.sysprep_params,
            namedtuple('User', 'rid name')(admin, self.usernames[admin]))
Esempio n. 3
0
class Windows(OSBase):
    """OS class for Windows"""
    @add_sysprep_param('mem', "posint", 1024, DESCR['mem'])
    @add_sysprep_param('smp', "posint", 1, DESCR['smp'])
    @add_sysprep_param(
        'connection_retries', "posint", 5, DESCR['connection_retries'])
    @add_sysprep_param(
        'shutdown_timeout', "posint", 300, DESCR['shutdown_timeout'])
    @add_sysprep_param('boot_timeout', "posint", 600, DESCR['boot_timeout'])
    @add_sysprep_param('virtio', 'dir', "", DESCR['virtio'],
                       check=virtio_dir_check, hidden=True)
    @add_sysprep_param(
        'virtio_timeout', 'posint', 900, DESCR['virtio_timeout'])
    def __init__(self, image, **kwargs):
        super(Windows, self).__init__(image, **kwargs)

        # The commit with the following message was added in
        # libguestfs 1.17.18 and was backported in version 1.16.11:
        #
        # When a Windows guest doesn't have a HKLM\SYSTEM\MountedDevices node,
        # inspection fails.  However inspection should not completely fail just
        # because we cannot get the drive letter mapping from a guest.
        #
        # Since Microsoft Sysprep removes the aforementioned key, image
        # creation for windows can only be supported if the installed guestfs
        # version is 1.17.18 or higher
        if self.image.check_guestfs_version(1, 17, 18) < 0 and \
                (self.image.check_guestfs_version(1, 17, 0) >= 0 or
                 self.image.check_guestfs_version(1, 16, 11) < 0):
            raise FatalError(
                'For windows support libguestfs 1.16.11 or above is required')

        device = self.image.g.part_to_dev(self.root)

        self.last_part_num = self.image.g.part_list(device)[-1]['part_num']

        self.product_name = self.image.g.inspect_get_product_name(self.root)
        self.systemroot = self.image.g.inspect_get_windows_systemroot(
            self.root)

        self.registry = Registry(self.image)

        with self.mount(readonly=True, silent=True):
            self.out.info("Checking media state ...", False)

            # Enumerate the windows users
            (self.usernames,
             self.active_users,
             self.admins) = self.registry.enum_users()
            assert ADMIN_RID in self.usernames, "Administrator account missing"

            self.virtio_state = self.compute_virtio_state()

            self.arch = self.image.g.inspect_get_arch(self.root)
            if self.arch == 'x86_64':
                self.arch = 'amd64'
            elif self.arch == 'i386':
                self.arch = 'x86'
            major = int(self.image.g.inspect_get_major_version(self.root))
            minor = int(self.image.g.inspect_get_minor_version(self.root))
            self.nt_version = (major, minor)

            # The get_setup_state() command does not work for old windows
            if self.nt_version[0] >= 6:
                # If the image is already sysprepped, we cannot further
                # customize it.
                self.sysprepped = self.registry.get_setup_state() > 0
            else:
                # Fallback to NO although we done know
                # TODO: Add support for detecting the setup state on XP
                self.sysprepped = False

            self.out.success("done")

        # If the image is sysprepped no driver mappings will be present.
        self.systemdrive = None
        for drive, root in self.image.g.inspect_get_drive_mappings(self.root):
            if root == self.root:
                self.systemdrive = drive

        active_admins = [u for u in self.admins if u in self.active_users]
        if ADMIN_RID in self.active_users or len(active_admins) == 0:
            admin = ADMIN_RID
        else:
            active_admins.sort()
            admin = active_admins[0]

        self.vm = VM(
            self.image.device, self.sysprep_params,
            namedtuple('User', 'rid name')(admin, self.usernames[admin]))

    @sysprep('Disabling IPv6 privacy extensions',
             display="Disable IPv6 privacy extensions")
    def _disable_ipv6_privacy_extensions(self):
        """Disable IPv6 privacy extensions"""

        self.vm.rexec('netsh interface ipv6 set global '
                      'randomizeidentifiers=disabled store=persistent')

    @sysprep('Disabling Teredo interface', display="Disable Teredo")
    def _disable_teredo(self):
        """Disable Teredo interface"""

        self.vm.rexec('netsh interface teredo set state disabled')

    @sysprep('Disabling ISATAP Adapters', display="Disable ISATAP")
    def _disable_isatap(self):
        """Disable ISATAP Adapters"""

        self.vm.rexec('netsh interface isa set state disabled')

    @sysprep('Enabling ping responses')
    def _enable_pings(self):
        """Enable ping responses"""

        self.vm.rexec('netsh firewall set icmpsetting 8')

    @sysprep('Setting the system clock to UTC', display="UTC")
    def _utc(self):
        """Set the hardware clock to UTC"""

        path = r'HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
        self.vm.rexec(
            r'REG ADD %s /v RealTimeIsUniversal /t REG_DWORD /d 1 /f' % path)

    @sysprep('Clearing the event logs')
    def _clear_logs(self):
        """Clear all the event logs"""

        self.vm.rexec(
            "cmd /q /c for /f \"tokens=*\" %l in ('wevtutil el') do "
            "wevtutil cl \"%l\"")

    @sysprep('Executing Sysprep on the image (may take more that 10 min)',
             display="Microsoft Sysprep")
    def _microsoft_sysprep(self):
        """Run the Microsoft System Preparation Tool. This will remove
        system-specific data and will make the image ready to be deployed.
        After this no other task may run.
        """

        self.vm.rexec(r'C:\Windows\system32\sysprep\sysprep '
                      r'/quiet /generalize /oobe /shutdown', uninstall=True)
        self.sysprepped = True

    @sysprep('Converting the image into a KMS client', enabled=False,
             display="KMS client setup")
    def _kms_client_setup(self):
        """Install the appropriate KMS client setup key to the image to convert
        it to a KMS client. Computers that are running volume licensing
        editions of Windows 8, Windows Server 2012, Windows 7, Windows Server
        2008 R2, Windows Vista, and Windows Server 2008 are by default KMS
        clients with no additional configuration needed.
        """
        try:
            setup_key = KMS_CLIENT_SETUP_KEYS[self.product_name]
        except KeyError:
            self.out.warn(
                "Don't know the KMS client setup key for product: `%s'" %
                self.product_name)
            return

        self.vm.rexec(
            r"cscript \Windows\system32\slmgr.vbs /ipk %s" % setup_key)

    @sysprep('Shrinking file system on the last partition')
    def _shrink(self):
        """Shrink the last file system. Please make sure the file system is
        defragged.
        """

        # Shrink the volume as much as possible and then give 100MB back.
        # From ntfsresize:
        # Practically the smallest shrunken size generally is at around
        # "used space" + (20-200 MB). Please also take into account that
        # Windows might need about 50-100 MB free space left to boot safely.
        cmd = (
            r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
            r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
            'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
            r'ECHO SHRINK NOERR >> %SCRIPT% & ' +
            r'ECHO EXTEND SIZE=100 NOERR >> %SCRIPT% & ' +
            r'ECHO EXIT >> %SCRIPT% & ' +
            r'DISKPART /S %SCRIPT% & ' +
            r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
            r'DEL /Q %SCRIPT%"')

        stdout, _, rc = self.vm.rexec(cmd, fatal=False)

        if rc != 0:
            raise FatalError(
                "Shrinking failed. Please make sure the media is defragged.")

        for line in stdout.splitlines():
            line = line.strip()
            if not len(line):
                continue
            self.out.info(" %s" % line)

        self.shrinked = True

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

        self.out.info('Preparing system for image creation:')

        # Check if winexe is installed
        if not WinEXE.is_installed():
            raise FatalError(
                "Winexe not found! In order to be able to customize a Windows "
                "image you need to have Winexe installed.")

        if self.sysprepped:
            raise FatalError(
                "Microsoft's System Preparation Tool has ran on the media. "
                "Further image customization is not possible.")

        if len(self.virtio_state['viostor']) == 0:
            raise FatalError(
                "The media has no VirtIO SCSI controller driver installed. "
                "Further image customization is not possible.")

        if len(self.virtio_state['netkvm']) == 0:
            raise FatalError(
                "The media has no VirtIO Ethernet Adapter driver installed. "
                "Further image customization is not possible.")

        timeout = self.sysprep_params['boot_timeout'].value
        shutdown_timeout = self.sysprep_params['shutdown_timeout'].value

        self.out.info("Preparing media for boot ...", False)

        with self.mount(readonly=False, silent=True):

            if not self.registry.reset_account(self.vm.admin.rid):
                self._add_cleanup('sysprep', self.registry.reset_account,
                                  self.vm.admin.rid, False)

            old = self.registry.update_uac(0)
            if old != 0:
                self._add_cleanup('sysprep', self.registry.update_uac, old)

            old = self.registry.update_uac_remote_setting(1)
            if old != 1:
                self._add_cleanup('sysprep',
                                  self.registry.update_uac_remote_setting, old)

            def if_not_sysprepped(task, *args):
                """Only perform this if the image is not sysprepped"""
                if not self.sysprepped:
                    task(*args)

            # The next 2 registry values get completely removed by Microsoft
            # Sysprep. They should not be reverted if Sysprep gets executed.
            old = self.registry.update_noautoupdate(1)
            if old != 1:
                self._add_cleanup('sysprep', if_not_sysprepped,
                                  self.registry.update_noautoupdate, old)

            old = self.registry.update_auoptions(1)
            if old != 1:
                self._add_cleanup('sysprep', if_not_sysprepped,
                                  self.registry.update_auoptions, old)

            # disable the firewalls
            self._add_cleanup('sysprep', self.registry.update_firewalls,
                              *self.registry.update_firewalls(0, 0, 0))

            v_val = self.registry.reset_passwd(self.vm.admin.rid)

            self._add_boot_scripts()

            # Delete the pagefile. It will be recreated when the system boots
            try:
                pagefile = "%s/pagefile.sys" % self.systemroot
                self.image.g.rm_rf(self.image.g.case_sensitive_path(pagefile))
            except RuntimeError:
                pass

        self.out.success('done')

        self.image.disable_guestfs()
        booted = False
        try:
            self.out.info("Starting windows VM ...", False)
            self.vm.start()
            try:
                self.out.success("started (console on VNC display: %d)" %
                                 self.vm.display)

                self.out.info("Waiting for OS to boot ...", False)
                if not self.vm.wait_on_serial(timeout):
                    raise FatalError("Windows VM booting timed out!")
                self.out.success('done')
                booted = True

                # Since the password is reset when logging in, sleep a little
                # bit before checking the connectivity, to avoid race
                # conditions
                time.sleep(5)

                self.out.info("Checking connectivity to the VM ...", False)
                self._check_connectivity()
                # self.out.success('done')

                # self.out.info("Disabling automatic logon ...", False)
                self._disable_autologon()
                self.out.success('done')

                self._exec_sysprep_tasks()

                self.out.info("Waiting for windows to shut down ...", False)
                (_, stderr, rc) = self.vm.wait(shutdown_timeout)
                if rc != 0 or "terminating on signal" in stderr:
                    raise FatalError("Windows VM died unexpectedly!\n\n"
                                     "(rc=%d)\n%s" % (rc, stderr))
                self.out.success("done")
            finally:
                # if the VM is not already dead here, a Fatal Error will have
                # already been raised. There is no reason to make the command
                # fatal.
                self.vm.stop(shutdown_timeout if booted else 1, fatal=False)
        finally:
            self.image.enable_guestfs()

            self.out.info("Reverting media boot preparations ...", False)
            with self.mount(readonly=False, silent=True, fatal=False):

                if not self.ismounted:
                    self.out.warn("The boot changes cannot be reverted. "
                                  "The snapshot may be in a corrupted state.")
                else:
                    if not self.sysprepped:
                        # Reset the old password
                        self.registry.reset_passwd(self.vm.admin.rid, v_val)

                    self._cleanup('sysprep')
                    self.out.success("done")

        self.image.shrink(silent=True)

    def _exec_sysprep_tasks(self):
        """This function hosts the actual code for executing the enabled
        sysprep tasks. At the end of this method the VM is shut down if needed.
        """
        tasks = self.list_syspreps()
        enabled = [t for t in tasks if self.sysprep_enabled(t)]
        size = len(enabled)

        # Make sure shrink runs in the end, before ms sysprep
        enabled = [t for t in enabled if self.sysprep_info(t).name != 'shrink']
        if len(enabled) != size:
            enabled.append(self._shrink)

        # Make sure the ms sysprep is the last task to run if it is enabled
        enabled = [t for t in enabled
                   if self.sysprep_info(t).name != 'microsoft-sysprep']

        if len(enabled) != size:
            enabled.append(self._microsoft_sysprep)

        cnt = 0
        for task in enabled:
            cnt += 1
            self.out.info(('(%d/%d)' % (cnt, size)).ljust(7), False)
            task()
            del self._sysprep_tasks[task.__name__]

        self.out.info("Sending shut down command ...", False)
        if not self.sysprepped:
            self._shutdown()
        self.out.success("done")

    def _shutdown(self):
        """Shuts down the windows VM"""
        self.vm.rexec(r'shutdown /s /t 5', uninstall=True)

    def _disable_autologon(self):
        """Disable automatic logon on the windows image"""

        winlogon = \
            r'"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"'

        self.vm.rexec('REG DELETE %s /v DefaultUserName /f' % winlogon)
        self.vm.rexec('REG DELETE %s /v DefaultPassword /f' % winlogon)
        self.vm.rexec('REG DELETE %s /v AutoAdminLogon /f' % winlogon)

    def _add_boot_scripts(self):
        """Add various scripts in the registry that will be executed during the
        next boot.
        """

        commands = {}

        # Disable hibernation. This is not needed for a VM
        commands['hibernate'] = r'powercfg.exe /hibernate off'
        # This script will send a random string to the first serial port. This
        # can be used to determine when the OS has booted.
        commands['BootMonitor'] = "cmd /q /a /c echo " + TOKEN + " > COM1"

        # This will update the password of the admin user to self.vm.password
        commands["UpdatePassword"] = "******" % \
            (self.vm.admin.name, self.vm.password)

        # This is previously done with hivex when we executed
        # self.registry.update_uac_remote_setting(1).
        # Although the command above works on all windows version and the
        # UAC remote restrictions are disabled, on Windows 2012 the registry
        # value seems corrupted after we run the command. Maybe this has to do
        # with a bug or a limitation in hivex. As a workaround we re-update the
        # value from within Windows.
        commands["UpdateRegistry"] = \
            (r'REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion'
             r'\policies\system /v LocalAccountTokenFilterPolicy'
             r' /t REG_DWORD /d 1 /f')

        self.registry.runonce(commands)

        # Enable automatic logon.
        # This is needed in order for the scripts we added in the RunOnce
        # registry entry to get executed, since the RunOnce commands only get
        # executed when a user logs on. There is a RunServicesOnce registry
        # entry whose keys get executed in the background when the logon dialog
        # box first appears, but they seem to only work with services and not
        # arbitrary command line expressions :-(
        #
        # Instructions on how to turn on automatic logon in Windows can be
        # found here: http://support.microsoft.com/kb/324737
        #
        # Warning: Registry change will not work if the “Logon Banner” is
        # defined on the server either by a Group Policy object (GPO) or by a
        # local policy.

        self.registry.enable_autologon(self.vm.admin.name)

    def _do_inspect(self):
        """Run various diagnostics to check if the medium is supported"""

        self.out.info(
            'Checking if this version of Windows is supported ...', False)

        # TODO: Check if PowerShell is installed. By default this is installed
        # in every version after Windows Vista. Maybe we could support a
        # Windows Vista medium if it has PowerShell installed.
        if self.nt_version >= (6, 1):
            self.out.success('yes')
        else:
            self.out.info()
            self.image.set_unsupported(
                '%s is too old. Versions prior to Windows 7 are not supported.'
                % self.meta['DESCRIPTION'])

    def _do_collect_metadata(self):
        """Collect metadata about the OS"""
        super(Windows, self)._do_collect_metadata()

        # We only care for active users
        active = [self.usernames[a] for a in self.active_users]
        self.meta["USERS"] = " ".join(active)

        # Get RDP settings
        settings = self.registry.get_rdp_settings()

        if settings['disabled']:
            self.out.warn("RDP is disabled on the image")
        else:
            if 'REMOTE_CONNECTION' not in self.meta:
                self.meta['REMOTE_CONNECTION'] = ""
            else:
                self.meta['REMOTE_CONNECTION'] += " "

            port = settings['port']
            if len(active):
                rdp = ["rdp:port=%d,user=%s" % (port, user) for user in active]
                self.meta['REMOTE_CONNECTION'] += " ".join(rdp)
            else:
                self.meta['REMOTE_CONNECTION'] += "rdp:port=%d" % port
        self.meta["KERNEL"] = "Windows NT %d.%d" % self.nt_version
        self.meta['GUI'] = 'Windows'

        major, minor = self.nt_version
        self.meta['SORTORDER'] += (100 * major + minor) * 100

    def _check_connectivity(self):
        """Check if winexe works on the Windows VM"""

        retries = self.sysprep_params['connection_retries'].value
        timeout = [5]
        for i in xrange(1, retries - 1):
            timeout.insert(0, timeout[0] * 2)

        # If the connection_retries parameter is set to 0 disable the
        # connectivity check
        if retries == 0:
            return True

        for i in xrange(retries):
            (stdout, stderr, rc) = self.vm.rexec('cmd /C', fatal=False,
                                                 debug=True)
            if rc == 0:
                return True

            log = tempfile.NamedTemporaryFile(delete=False)
            try:
                log.file.write("STDOUT:\n%s\n" % stdout)
                log.file.write("STDERR:\n%s\n" % stderr)
            finally:
                log.close()
            self.out.info("failed! See: `%s' for the full output" % log.name)
            if i < retries - 1:
                wait = timeout.pop()
                self.out.info("retrying in %d seconds ..." % wait, False)
                time.sleep(wait)

        raise FatalError("Connection to the Windows VM failed after %d retries"
                         % retries)

    def compute_virtio_state(self, directory=None):
        """Returns information about the VirtIO drivers found either in a
        directory or the media itself if the directory is None.
        """
        state = {}
        for driver in VIRTIO:
            state[driver] = {}

        def oem_files():
            """Parse oem*.inf files under the %SystemRoot%/Inf directory"""
            path = self.image.g.case_sensitive_path("%s/inf" % self.systemroot)
            oem = re.compile(r'^oem\d+\.inf', flags=re.IGNORECASE)
            for name in [f['name'] for f in self.image.g.readdir(path)]:
                if not oem.match(name):
                    continue
                yield name, \
                    self.image.g.cat("%s/%s" % (path, name)).splitlines()

        def local_files():
            """Parse *.inf files under a local directory"""
            assert os.path.isdir(directory)
            inf = re.compile(r'^.+\.inf', flags=re.IGNORECASE)
            for name in os.listdir(directory):
                fullpath = os.path.join(directory, name)
                if inf.match(name) and os.path.isfile(fullpath):
                    with open(fullpath, 'r') as content:
                        yield name, content

        for name, txt in oem_files() if directory is None else local_files():
            driver, target, content = parse_inf(txt)

            if driver:
                content['TargetOSVersions'] = target
                state[driver][name] = content

        return state

    def _fetch_virtio_drivers(self, dirname):
        """Examines a directory for VirtIO drivers and returns only the drivers
        that are suitable for this media.
        """
        collection = self.compute_virtio_state(dirname)

        files = set([f.lower() for f in os.listdir(dirname)
                     if os.path.isfile(dirname + os.sep + f)])

        num = 0
        for drv_type, drvs in collection.items():
            for inf, content in drvs.items():
                valid = True
                found_match = False
                # Check if the driver is suitable for the input media
                for target in content['TargetOSVersions']:
                    match = TARGET_OS_VERSION.match(target)
                    if match:
                        arch = match.group(1).lower()
                        major = int(match.group(2)) if match.group(2) else 0
                        minor = int(match.group(3)) if match.group(3) else 0
                        if self.arch != arch:
                            continue
                        if self.nt_version >= (major, minor):
                            found_match = True
                            break
                if not found_match:  # Wrong Target
                    self.out.warn(
                        'Ignoring %s. Driver not targeted for this OS.' % inf)
                    valid = False
                elif 'CatalogFile' not in content:
                    self.out.warn(
                        'Ignoring %s. CatalogFile entry missing.' % inf)
                    valid = False
                elif content['CatalogFile'].lower() not in files:
                    self.out.warn('Ignoring %s. Catalog File not found.' % inf)
                    valid = False

                if not valid:
                    del collection[drv_type][inf]
                    continue

                num += 1
            if len(drvs) == 0:
                del collection[drv_type]

        self.out.info('Found %d valid driver%s' %
                      (num, "s" if num != 1 else ""))
        return collection

    def install_virtio_drivers(self, upgrade=True):
        """Install new VirtIO drivers on the input media. If upgrade is True,
        then the old drivers found in the media will be removed.
        """

        dirname = self.sysprep_params['virtio'].value
        if not dirname:
            raise FatalError('No directory hosting the VirtIO drivers defined')

        self.out.info('Installing VirtIO drivers:')

        valid_drvs = self._fetch_virtio_drivers(dirname)
        if not len(valid_drvs):
            self.out.warn('No suitable driver found to install!')
            return

        remove = []
        certs = []
        install = []
        add = []
        # Check which drivers we need to install, which to add to the database
        # and which to remove.
        for dtype in valid_drvs:
            versions = [v['DriverVer'] for k, v in valid_drvs[dtype].items()]
            certs.extend([v['CatalogFile'] for k, v in
                          valid_drvs[dtype].items() if 'CatalogFile' in v])
            installed = [(k, v['DriverVer']) for k, v in
                         self.virtio_state[dtype].items()]
            found = [d[0] for d in installed if d[1] in versions]
            not_found = [d[0] for d in installed if d[1] not in versions]

            for drvr in found:
                details = self.virtio_state[dtype][drvr]
                self.out.warn('%s driver with version %s is already installed!'
                              % (dtype, details['DriverVer']))
            if upgrade:
                remove.extend(not_found)

            if dtype == 'viostor':
                install.extend([d for d in valid_drvs[dtype]])
            else:
                add.extend([d for d in valid_drvs[dtype]])

        try:
            self._update_driver_database('virtio', upload=dirname, certs=certs,
                                         add=add, install=install,
                                         remove=remove)
        finally:
            with self.mount(readonly=False, silent=True, fatal=False):
                if not self.ismounted:
                    self.out.warn("The boot changes cannot be reverted. "
                                  "The image may be in a corrupted state.")
                else:
                    self._cleanup('virtio')

        self.out.success("VirtIO drivers were successfully installed")
        self.out.info()

    def _update_driver_database(self, namespace, **kwargs):
        """Upload a directory that contains the VirtIO drivers and add scripts
        for installing and removing specific drivers.

        Keyword arguments:
        namespace -- namespace for the cleanup entries
        upload  -- Host directory that contains drivers to upload
        add     -- List of drivers to add to the driver database
        install -- List of drivers to install to the system
        remove  -- List of drivers to remove from the system
        """

        upload = kwargs['upload'] if 'upload' in kwargs else None
        add = kwargs['add'] if 'add' in kwargs else []
        install = kwargs['install'] if 'install' in kwargs else []
        certs = kwargs['certs'] if 'certs' in kwargs else []
        remove = kwargs['remove'] if 'remove' in kwargs else []

        assert len(add) == 0 or upload is not None
        assert len(install) == 0 or upload is not None

        with self.mount(readonly=False, silent=True):
            # Reset admin password
            self._add_cleanup(namespace, self.registry.reset_passwd,
                              self.vm.admin.rid,
                              self.registry.reset_passwd(self.vm.admin.rid))

            # Enable admin account (if needed)
            self._add_cleanup(namespace, self.registry.reset_account,
                              self.vm.admin.rid,
                              self.registry.reset_account(self.vm.admin.rid))

            old = self.registry.update_uac(0)
            if old != 0:
                self._add_cleanup(namespace, self.registry.update_uac, old)

            old = self.registry.update_noautoupdate(1)
            if old != 1:
                self._add_cleanup(namespace,
                                  self.registry.update_noautoupdate, old)

            old = self.registry.update_auoptions(1)
            if old != 1:
                self._add_cleanup(namespace,
                                  self.registry.update_auoptions, old)

            # We disable this with powershell scripts
            self.registry.enable_autologon(self.vm.admin.name)

            # Disable first logon animation (if needed)
            self._add_cleanup(namespace,
                              self.registry.reset_first_logon_animation,
                              self.registry.reset_first_logon_animation(False))

            tmp = uuid.uuid4().hex
            self.image.g.mkdir_p("%s/%s" % (self.systemroot, tmp))

            # This is a hack. We create a function here and pass it to
            # _add_cleanup because self.image.g may change and the _add_cleanup
            # will cache it which is wrong. For older versions of the guestfs
            # library we recreate the g handler in enable_guestfs() and the
            # program will crash if cleanup retains an older value for the
            # guestfs handler.
            def remove_tmp():
                self.image.g.rm_rf("%s/%s" % (self.systemroot, tmp))

            self._add_cleanup(namespace, remove_tmp)

            if upload is not None:
                for fname in os.listdir(upload):
                    full_path = os.path.join(upload, fname)
                    if os.path.isfile(full_path):
                        self.image.g.upload(full_path, "%s/%s/%s" %
                                            (self.systemroot, tmp, fname))

            drvs_install = powershell.DRVINST_HEAD

            for cert in certs:
                drvs_install += powershell.ADD_CERTIFICATE % cert

            for driver in install:
                drvs_install += powershell.INSTALL_DRIVER % driver

            for driver in add:
                drvs_install += powershell.ADD_DRIVER % driver

            for driver in remove:
                drvs_install += powershell.REMOVE_DRIVER % driver

            if self.check_version(6, 1) <= 0:
                self._install_viostor_driver(upload)
                old = self.registry.update_devices_dirs("%SystemRoot%\\" + tmp)
                self._add_cleanup(
                    namespace, self.registry.update_devices_dirs, old, False)
                drvs_install += powershell.DISABLE_AUTOLOGON
            else:
                # In newer windows, in order to reduce the boot process the
                # boot drivers are cached. To be able to boot with viostor, we
                # need to reboot in safe mode.
                drvs_install += powershell.SAFEBOOT

            drvs_install += powershell.DRVINST_TAIL

            target = "%s/%s/InstallDrivers.ps1" % (self.systemroot, tmp)
            self.image.g.write(target, drvs_install.replace('\n', '\r\n'))

            # The -windowstyle option was introduced in PowerShell V2. We need
            # to have at least Windows NT 6.1 (Windows 7 or Windows 2008R2) to
            # make this work.
            hidden_support = self.check_version(6, 1) >= 0
            cmd = (
                '%(drive)s:%(root)s\\System32\\WindowsPowerShell\\v1.0\\'
                'powershell.exe -ExecutionPolicy RemoteSigned %(hidden)s '
                '-File %(drive)s:%(root)s\\%(tmp)s\\InstallDrivers.ps1 '
                '%(drive)s:%(root)s\\%(tmp)s' %
                {'root': self.systemroot.replace('/', '\\'),
                 'drive': self.systemdrive,
                 'tmp': tmp,
                 'hidden': '-windowstyle hidden' if hidden_support else ""})

            # The value name of RunOnce keys can be prefixed with an asterisk
            # (*) to force the program to run even in Safe mode.
            self.registry.runonce({'*InstallDrivers': cmd})

        # Boot the Windows VM to update the driver's database
        self._boot_virtio_vm()

    def _boot_virtio_vm(self):
        """Boot the media and install the VirtIO drivers"""

        old_windows = self.check_version(6, 1) <= 0
        self.image.disable_guestfs()
        try:
            timeout = self.sysprep_params['boot_timeout'].value
            shutdown_timeout = self.sysprep_params['shutdown_timeout'].value
            virtio_timeout = self.sysprep_params['virtio_timeout'].value
            self.out.info("Starting Windows VM ...", False)
            booted = False
            try:
                if old_windows:
                    self.vm.start()
                else:
                    self.vm.interface = 'ide'
                    self.vm.start(extra_disk=('/dev/null', 'virtio'))
                    self.vm.interface = 'virtio'

                self.out.success("started (console on VNC display: %d)" %
                                 self.vm.display)
                self.out.info("Waiting for Windows to boot ...", False)
                if not self.vm.wait_on_serial(timeout):
                    raise FatalError("Windows VM booting timed out!")
                self.out.success('done')
                booted = True
                self.out.info("Installing new drivers ...", False)
                if not self.vm.wait_on_serial(virtio_timeout):
                    raise FatalError("Windows VirtIO installation timed out!")
                self.out.success('done')
                self.out.info('Shutting down ...', False)
                (_, stderr, rc) = self.vm.wait(shutdown_timeout)
                if rc != 0 or "terminating on signal" in stderr:
                    raise FatalError("Windows VM died unexpectedly!\n\n"
                                     "(rc=%d)\n%s" % (rc, stderr))
                self.out.success('done')
            finally:
                self.vm.stop(shutdown_timeout if booted else 1, fatal=False)
        finally:
            self.image.enable_guestfs()

        with self.mount(readonly=True, silent=True):
            self.virtio_state = self.compute_virtio_state()
            viostor_service_found = self.registry.check_viostor_service()

        if not (len(self.virtio_state['viostor']) and viostor_service_found):
            raise FatalError("viostor was not successfully installed")

        if self.check_version(6, 1) > 0:
            # Hopefully restart in safe mode. Newer windows will not boot from
            # a viostor device unless we initially start them in safe mode
            try:
                self.out.info('Rebooting Windows VM in safe mode ...', False)
                self.vm.start()
                (_, stderr, rc) = self.vm.wait(timeout + shutdown_timeout)
                if rc != 0 or "terminating on signal" in stderr:
                    raise FatalError("Windows VM died unexpectedly!\n\n"
                                     "(rc=%d)\n%s" % (rc, stderr))
                self.out.success('done')
            finally:
                self.vm.stop(1, fatal=True)

    def _install_viostor_driver(self, dirname):
        """Quick and dirty installation of the VirtIO SCSI controller driver.
        It is done to make the image boot from the VirtIO disk.

        http://rwmj.wordpress.com/2010/04/30/
            tip-install-a-device-driver-in-a-windows-vm/
        """

        drivers_path = "%s/system32/drivers" % self.systemroot

        try:
            drivers_path = self.image.g.case_sensitive_path(drivers_path)
        except RuntimeError as err:
            raise FatalError("Unable to browse to directory: %s. Reason: %s" %
                             (drivers_path, str(err)))
        viostor = dirname + os.sep + 'viostor.sys'
        try:
            self.image.g.upload(viostor, drivers_path + '/viostor.sys')
        except RuntimeError as err:
            raise FatalError("Unable to upload file %s to %s. Reason: %s" %
                             (viostor, drivers_path, str(err)))

        self.registry.add_viostor()
Esempio n. 4
0
class Windows(OSBase):
    """OS class for Windows"""
    @add_sysprep_param('mem', "posint", 1024, DESCR['mem'])
    @add_sysprep_param('smp', "posint", 1, DESCR['smp'])
    @add_sysprep_param('connection_retries', "posint", 5,
                       DESCR['connection_retries'])
    @add_sysprep_param('shutdown_timeout', "posint", 300,
                       DESCR['shutdown_timeout'])
    @add_sysprep_param('boot_timeout', "posint", 600, DESCR['boot_timeout'])
    @add_sysprep_param('virtio',
                       'dir',
                       "",
                       DESCR['virtio'],
                       check=virtio_dir_check,
                       hidden=True)
    @add_sysprep_param('virtio_timeout', 'posint', 900,
                       DESCR['virtio_timeout'])
    def __init__(self, image, **kwargs):
        super(Windows, self).__init__(image, **kwargs)

        # The commit with the following message was added in
        # libguestfs 1.17.18 and was backported in version 1.16.11:
        #
        # When a Windows guest doesn't have a HKLM\SYSTEM\MountedDevices node,
        # inspection fails.  However inspection should not completely fail just
        # because we cannot get the drive letter mapping from a guest.
        #
        # Since Microsoft Sysprep removes the aforementioned key, image
        # creation for windows can only be supported if the installed guestfs
        # version is 1.17.18 or higher
        if self.image.check_guestfs_version(1, 17, 18) < 0 and \
                (self.image.check_guestfs_version(1, 17, 0) >= 0 or
                 self.image.check_guestfs_version(1, 16, 11) < 0):
            raise FatalError(
                'For windows support libguestfs 1.16.11 or above is required')

        device = self.image.g.part_to_dev(self.root)

        self.last_part_num = self.image.g.part_list(device)[-1]['part_num']

        self.product_name = self.image.g.inspect_get_product_name(self.root)
        self.systemroot = self.image.g.inspect_get_windows_systemroot(
            self.root)

        self.registry = Registry(self.image)

        with self.mount(readonly=True, silent=True):
            self.out.info("Checking media state ...", False)

            # Enumerate the windows users
            (self.usernames, self.active_users,
             self.admins) = self.registry.enum_users()
            assert ADMIN_RID in self.usernames, "Administrator account missing"

            self.virtio_state = self.compute_virtio_state()

            self.arch = self.image.g.inspect_get_arch(self.root)
            if self.arch == 'x86_64':
                self.arch = 'amd64'
            elif self.arch == 'i386':
                self.arch = 'x86'
            major = int(self.image.g.inspect_get_major_version(self.root))
            minor = int(self.image.g.inspect_get_minor_version(self.root))
            self.nt_version = (major, minor)

            # The get_setup_state() command does not work for old windows
            if self.nt_version[0] >= 6:
                # If the image is already sysprepped, we cannot further
                # customize it.
                self.sysprepped = self.registry.get_setup_state() > 0
            else:
                # Fallback to NO although we done know
                # TODO: Add support for detecting the setup state on XP
                self.sysprepped = False

            self.out.success("done")

        # If the image is sysprepped no driver mappings will be present.
        self.systemdrive = None
        for drive, root in self.image.g.inspect_get_drive_mappings(self.root):
            if root == self.root:
                self.systemdrive = drive

        active_admins = [u for u in self.admins if u in self.active_users]
        if ADMIN_RID in self.active_users or len(active_admins) == 0:
            admin = ADMIN_RID
        else:
            active_admins.sort()
            admin = active_admins[0]

        self.vm = VM(
            self.image.device, self.sysprep_params,
            namedtuple('User', 'rid name')(admin, self.usernames[admin]))

    @sysprep('Disabling IPv6 privacy extensions',
             display="Disable IPv6 privacy extensions")
    def _disable_ipv6_privacy_extensions(self):
        """Disable IPv6 privacy extensions"""

        self.vm.rexec('netsh interface ipv6 set global '
                      'randomizeidentifiers=disabled store=persistent')

    @sysprep('Disabling Teredo interface', display="Disable Teredo")
    def _disable_teredo(self):
        """Disable Teredo interface"""

        self.vm.rexec('netsh interface teredo set state disabled')

    @sysprep('Disabling ISATAP Adapters', display="Disable ISATAP")
    def _disable_isatap(self):
        """Disable ISATAP Adapters"""

        self.vm.rexec('netsh interface isa set state disabled')

    @sysprep('Enabling ping responses')
    def _enable_pings(self):
        """Enable ping responses"""

        self.vm.rexec('netsh firewall set icmpsetting 8')

    @sysprep('Setting the system clock to UTC', display="UTC")
    def _utc(self):
        """Set the hardware clock to UTC"""

        path = r'HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
        self.vm.rexec(
            r'REG ADD %s /v RealTimeIsUniversal /t REG_DWORD /d 1 /f' % path)

    @sysprep('Clearing the event logs')
    def _clear_logs(self):
        """Clear all the event logs"""

        self.vm.rexec("cmd /q /c for /f \"tokens=*\" %l in ('wevtutil el') do "
                      "wevtutil cl \"%l\"")

    @sysprep('Executing Sysprep on the image (may take more that 10 min)',
             display="Microsoft Sysprep")
    def _microsoft_sysprep(self):
        """Run the Microsoft System Preparation Tool. This will remove
        system-specific data and will make the image ready to be deployed.
        After this no other task may run.
        """

        self.vm.rexec(
            r'C:\Windows\system32\sysprep\sysprep '
            r'/quiet /generalize /oobe /shutdown',
            uninstall=True)
        self.sysprepped = True

    @sysprep('Converting the image into a KMS client',
             enabled=False,
             display="KMS client setup")
    def _kms_client_setup(self):
        """Install the appropriate KMS client setup key to the image to convert
        it to a KMS client. Computers that are running volume licensing
        editions of Windows 8, Windows Server 2012, Windows 7, Windows Server
        2008 R2, Windows Vista, and Windows Server 2008 are by default KMS
        clients with no additional configuration needed.
        """
        try:
            setup_key = KMS_CLIENT_SETUP_KEYS[self.product_name]
        except KeyError:
            self.out.warn(
                "Don't know the KMS client setup key for product: `%s'" %
                self.product_name)
            return

        self.vm.rexec(r"cscript \Windows\system32\slmgr.vbs /ipk %s" %
                      setup_key)

    @sysprep('Shrinking file system on the last partition')
    def _shrink(self):
        """Shrink the last file system. Please make sure the file system is
        defragged.
        """

        # Shrink the volume as much as possible and then give 100MB back.
        # From ntfsresize:
        # Practically the smallest shrunken size generally is at around
        # "used space" + (20-200 MB). Please also take into account that
        # Windows might need about 50-100 MB free space left to boot safely.
        cmd = (r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
               r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
               'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num
               + r'ECHO SHRINK NOERR >> %SCRIPT% & ' +
               r'ECHO EXTEND SIZE=100 NOERR >> %SCRIPT% & ' +
               r'ECHO EXIT >> %SCRIPT% & ' + r'DISKPART /S %SCRIPT% & ' +
               r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' + r'DEL /Q %SCRIPT%"')

        stdout, _, rc = self.vm.rexec(cmd, fatal=False)

        if rc != 0:
            raise FatalError(
                "Shrinking failed. Please make sure the media is defragged.")

        for line in stdout.splitlines():
            line = line.strip()
            if not len(line):
                continue
            self.out.info(" %s" % line)

        self.shrinked = True

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

        self.out.info('Preparing system for image creation:')

        # Check if winexe is installed
        if not WinEXE.is_installed():
            raise FatalError(
                "Winexe not found! In order to be able to customize a Windows "
                "image you need to have Winexe installed.")

        if self.sysprepped:
            raise FatalError(
                "Microsoft's System Preparation Tool has ran on the media. "
                "Further image customization is not possible.")

        if len(self.virtio_state['viostor']) == 0:
            raise FatalError(
                "The media has no VirtIO SCSI controller driver installed. "
                "Further image customization is not possible.")

        if len(self.virtio_state['netkvm']) == 0:
            raise FatalError(
                "The media has no VirtIO Ethernet Adapter driver installed. "
                "Further image customization is not possible.")

        timeout = self.sysprep_params['boot_timeout'].value
        shutdown_timeout = self.sysprep_params['shutdown_timeout'].value

        self.out.info("Preparing media for boot ...", False)

        with self.mount(readonly=False, silent=True):

            if not self.registry.reset_account(self.vm.admin.rid):
                self._add_cleanup('sysprep', self.registry.reset_account,
                                  self.vm.admin.rid, False)

            old = self.registry.update_uac(0)
            if old != 0:
                self._add_cleanup('sysprep', self.registry.update_uac, old)

            old = self.registry.update_uac_remote_setting(1)
            if old != 1:
                self._add_cleanup('sysprep',
                                  self.registry.update_uac_remote_setting, old)

            def if_not_sysprepped(task, *args):
                """Only perform this if the image is not sysprepped"""
                if not self.sysprepped:
                    task(*args)

            # The next 2 registry values get completely removed by Microsoft
            # Sysprep. They should not be reverted if Sysprep gets executed.
            old = self.registry.update_noautoupdate(1)
            if old != 1:
                self._add_cleanup('sysprep', if_not_sysprepped,
                                  self.registry.update_noautoupdate, old)

            old = self.registry.update_auoptions(1)
            if old != 1:
                self._add_cleanup('sysprep', if_not_sysprepped,
                                  self.registry.update_auoptions, old)

            # disable the firewalls
            self._add_cleanup('sysprep', self.registry.update_firewalls,
                              *self.registry.update_firewalls(0, 0, 0))

            v_val = self.registry.reset_passwd(self.vm.admin.rid)

            self._add_boot_scripts()

            # Delete the pagefile. It will be recreated when the system boots
            try:
                pagefile = "%s/pagefile.sys" % self.systemroot
                self.image.g.rm_rf(self.image.g.case_sensitive_path(pagefile))
            except RuntimeError:
                pass

        self.out.success('done')

        self.image.disable_guestfs()
        booted = False
        try:
            self.out.info("Starting windows VM ...", False)
            self.vm.start()
            try:
                self.out.success("started (console on VNC display: %d)" %
                                 self.vm.display)

                self.out.info("Waiting for OS to boot ...", False)
                if not self.vm.wait_on_serial(timeout):
                    raise FatalError("Windows VM booting timed out!")
                self.out.success('done')
                booted = True

                # Since the password is reset when logging in, sleep a little
                # bit before checking the connectivity, to avoid race
                # conditions
                time.sleep(5)

                self.out.info("Checking connectivity to the VM ...", False)
                self._check_connectivity()
                # self.out.success('done')

                # self.out.info("Disabling automatic logon ...", False)
                self._disable_autologon()
                self.out.success('done')

                self._exec_sysprep_tasks()

                self.out.info("Waiting for windows to shut down ...", False)
                (_, stderr, rc) = self.vm.wait(shutdown_timeout)
                if rc != 0 or "terminating on signal" in stderr:
                    raise FatalError("Windows VM died unexpectedly!\n\n"
                                     "(rc=%d)\n%s" % (rc, stderr))
                self.out.success("done")
            finally:
                # if the VM is not already dead here, a Fatal Error will have
                # already been raised. There is no reason to make the command
                # fatal.
                self.vm.stop(shutdown_timeout if booted else 1, fatal=False)
        finally:
            self.image.enable_guestfs()

            self.out.info("Reverting media boot preparations ...", False)
            with self.mount(readonly=False, silent=True, fatal=False):

                if not self.ismounted:
                    self.out.warn("The boot changes cannot be reverted. "
                                  "The snapshot may be in a corrupted state.")
                else:
                    if not self.sysprepped:
                        # Reset the old password
                        self.registry.reset_passwd(self.vm.admin.rid, v_val)

                    self._cleanup('sysprep')
                    self.out.success("done")

        self.image.shrink(silent=True)

    def _exec_sysprep_tasks(self):
        """This function hosts the actual code for executing the enabled
        sysprep tasks. At the end of this method the VM is shut down if needed.
        """
        tasks = self.list_syspreps()
        enabled = [t for t in tasks if self.sysprep_enabled(t)]
        size = len(enabled)

        # Make sure shrink runs in the end, before ms sysprep
        enabled = [t for t in enabled if self.sysprep_info(t).name != 'shrink']
        if len(enabled) != size:
            enabled.append(self._shrink)

        # Make sure the ms sysprep is the last task to run if it is enabled
        enabled = [
            t for t in enabled
            if self.sysprep_info(t).name != 'microsoft-sysprep'
        ]

        if len(enabled) != size:
            enabled.append(self._microsoft_sysprep)

        cnt = 0
        for task in enabled:
            cnt += 1
            self.out.info(('(%d/%d)' % (cnt, size)).ljust(7), False)
            task()
            del self._sysprep_tasks[task.__name__]

        self.out.info("Sending shut down command ...", False)
        if not self.sysprepped:
            self._shutdown()
        self.out.success("done")

    def _shutdown(self):
        """Shuts down the windows VM"""
        self.vm.rexec(r'shutdown /s /t 5', uninstall=True)

    def _disable_autologon(self):
        """Disable automatic logon on the windows image"""

        winlogon = \
            r'"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"'

        self.vm.rexec('REG DELETE %s /v DefaultUserName /f' % winlogon)
        self.vm.rexec('REG DELETE %s /v DefaultPassword /f' % winlogon)
        self.vm.rexec('REG DELETE %s /v AutoAdminLogon /f' % winlogon)

    def _add_boot_scripts(self):
        """Add various scripts in the registry that will be executed during the
        next boot.
        """

        commands = {}

        # Disable hibernation. This is not needed for a VM
        commands['hibernate'] = r'powercfg.exe /hibernate off'
        # This script will send a random string to the first serial port. This
        # can be used to determine when the OS has booted.
        commands['BootMonitor'] = "cmd /q /a /c echo " + TOKEN + " > COM1"

        # This will update the password of the admin user to self.vm.password
        commands["UpdatePassword"] = "******" % \
            (self.vm.admin.name, self.vm.password)

        # This is previously done with hivex when we executed
        # self.registry.update_uac_remote_setting(1).
        # Although the command above works on all windows version and the
        # UAC remote restrictions are disabled, on Windows 2012 the registry
        # value seems corrupted after we run the command. Maybe this has to do
        # with a bug or a limitation in hivex. As a workaround we re-update the
        # value from within Windows.
        commands["UpdateRegistry"] = \
            (r'REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion'
             r'\policies\system /v LocalAccountTokenFilterPolicy'
             r' /t REG_DWORD /d 1 /f')

        self.registry.runonce(commands)

        # Enable automatic logon.
        # This is needed in order for the scripts we added in the RunOnce
        # registry entry to get executed, since the RunOnce commands only get
        # executed when a user logs on. There is a RunServicesOnce registry
        # entry whose keys get executed in the background when the logon dialog
        # box first appears, but they seem to only work with services and not
        # arbitrary command line expressions :-(
        #
        # Instructions on how to turn on automatic logon in Windows can be
        # found here: http://support.microsoft.com/kb/324737
        #
        # Warning: Registry change will not work if the “Logon Banner” is
        # defined on the server either by a Group Policy object (GPO) or by a
        # local policy.

        self.registry.enable_autologon(self.vm.admin.name)

    def _do_inspect(self):
        """Run various diagnostics to check if the medium is supported"""

        self.out.info('Checking if this version of Windows is supported ...',
                      False)

        # TODO: Check if PowerShell is installed. By default this is installed
        # in every version after Windows Vista. Maybe we could support a
        # Windows Vista medium if it has PowerShell installed.
        if self.nt_version >= (6, 1):
            self.out.success('yes')
        else:
            self.out.info()
            self.image.set_unsupported(
                '%s is too old. Versions prior to Windows 7 are not supported.'
                % self.meta['DESCRIPTION'])

    def _do_collect_metadata(self):
        """Collect metadata about the OS"""
        super(Windows, self)._do_collect_metadata()

        # We only care for active users
        active = [self.usernames[a] for a in self.active_users]
        self.meta["USERS"] = " ".join(active)

        # Get RDP settings
        settings = self.registry.get_rdp_settings()

        if settings['disabled']:
            self.out.warn("RDP is disabled on the image")
        else:
            if 'REMOTE_CONNECTION' not in self.meta:
                self.meta['REMOTE_CONNECTION'] = ""
            else:
                self.meta['REMOTE_CONNECTION'] += " "

            port = settings['port']
            if len(active):
                rdp = ["rdp:port=%d,user=%s" % (port, user) for user in active]
                self.meta['REMOTE_CONNECTION'] += " ".join(rdp)
            else:
                self.meta['REMOTE_CONNECTION'] += "rdp:port=%d" % port
        self.meta["KERNEL"] = "Windows NT %d.%d" % self.nt_version
        self.meta['GUI'] = 'Windows'

        major, minor = self.nt_version
        self.meta['SORTORDER'] += (100 * major + minor) * 100

    def _check_connectivity(self):
        """Check if winexe works on the Windows VM"""

        retries = self.sysprep_params['connection_retries'].value
        timeout = [5]
        for i in xrange(1, retries - 1):
            timeout.insert(0, timeout[0] * 2)

        # If the connection_retries parameter is set to 0 disable the
        # connectivity check
        if retries == 0:
            return True

        for i in xrange(retries):
            (stdout, stderr, rc) = self.vm.rexec('cmd /C',
                                                 fatal=False,
                                                 debug=True)
            if rc == 0:
                return True

            log = tempfile.NamedTemporaryFile(delete=False)
            try:
                log.file.write("STDOUT:\n%s\n" % stdout)
                log.file.write("STDERR:\n%s\n" % stderr)
            finally:
                log.close()
            self.out.info("failed! See: `%s' for the full output" % log.name)
            if i < retries - 1:
                wait = timeout.pop()
                self.out.info("retrying in %d seconds ..." % wait, False)
                time.sleep(wait)

        raise FatalError(
            "Connection to the Windows VM failed after %d retries" % retries)

    def compute_virtio_state(self, directory=None):
        """Returns information about the VirtIO drivers found either in a
        directory or the media itself if the directory is None.
        """
        state = {}
        for driver in VIRTIO:
            state[driver] = {}

        def oem_files():
            """Parse oem*.inf files under the %SystemRoot%/Inf directory"""
            path = self.image.g.case_sensitive_path("%s/inf" % self.systemroot)
            oem = re.compile(r'^oem\d+\.inf', flags=re.IGNORECASE)
            for name in [f['name'] for f in self.image.g.readdir(path)]:
                if not oem.match(name):
                    continue
                yield name, \
                    self.image.g.cat("%s/%s" % (path, name)).splitlines()

        def local_files():
            """Parse *.inf files under a local directory"""
            assert os.path.isdir(directory)
            inf = re.compile(r'^.+\.inf', flags=re.IGNORECASE)
            for name in os.listdir(directory):
                fullpath = os.path.join(directory, name)
                if inf.match(name) and os.path.isfile(fullpath):
                    with open(fullpath, 'r') as content:
                        yield name, content

        for name, txt in oem_files() if directory is None else local_files():
            driver, target, content = parse_inf(txt)

            if driver:
                content['TargetOSVersions'] = target
                state[driver][name] = content

        return state

    def _fetch_virtio_drivers(self, dirname):
        """Examines a directory for VirtIO drivers and returns only the drivers
        that are suitable for this media.
        """
        collection = self.compute_virtio_state(dirname)

        files = set([
            f.lower() for f in os.listdir(dirname)
            if os.path.isfile(dirname + os.sep + f)
        ])

        num = 0
        for drv_type, drvs in collection.items():
            for inf, content in drvs.items():
                valid = True
                found_match = False
                # Check if the driver is suitable for the input media
                for target in content['TargetOSVersions']:
                    match = TARGET_OS_VERSION.match(target)
                    if match:
                        arch = match.group(1).lower()
                        major = int(match.group(2)) if match.group(2) else 0
                        minor = int(match.group(3)) if match.group(3) else 0
                        if self.arch != arch:
                            continue
                        if self.nt_version >= (major, minor):
                            found_match = True
                            break
                if not found_match:  # Wrong Target
                    self.out.warn(
                        'Ignoring %s. Driver not targeted for this OS.' % inf)
                    valid = False
                elif 'CatalogFile' not in content:
                    self.out.warn('Ignoring %s. CatalogFile entry missing.' %
                                  inf)
                    valid = False
                elif content['CatalogFile'].lower() not in files:
                    self.out.warn('Ignoring %s. Catalog File not found.' % inf)
                    valid = False

                if not valid:
                    del collection[drv_type][inf]
                    continue

                num += 1
            if len(drvs) == 0:
                del collection[drv_type]

        self.out.info('Found %d valid driver%s' %
                      (num, "s" if num != 1 else ""))
        return collection

    def install_virtio_drivers(self, upgrade=True):
        """Install new VirtIO drivers on the input media. If upgrade is True,
        then the old drivers found in the media will be removed.
        """

        dirname = self.sysprep_params['virtio'].value
        if not dirname:
            raise FatalError('No directory hosting the VirtIO drivers defined')

        self.out.info('Installing VirtIO drivers:')

        valid_drvs = self._fetch_virtio_drivers(dirname)
        if not len(valid_drvs):
            self.out.warn('No suitable driver found to install!')
            return

        remove = []
        certs = []
        install = []
        add = []
        # Check which drivers we need to install, which to add to the database
        # and which to remove.
        for dtype in valid_drvs:
            versions = [v['DriverVer'] for k, v in valid_drvs[dtype].items()]
            certs.extend([
                v['CatalogFile'] for k, v in valid_drvs[dtype].items()
                if 'CatalogFile' in v
            ])
            installed = [(k, v['DriverVer'])
                         for k, v in self.virtio_state[dtype].items()]
            found = [d[0] for d in installed if d[1] in versions]
            not_found = [d[0] for d in installed if d[1] not in versions]

            for drvr in found:
                details = self.virtio_state[dtype][drvr]
                self.out.warn(
                    '%s driver with version %s is already installed!' %
                    (dtype, details['DriverVer']))
            if upgrade:
                remove.extend(not_found)

            if dtype == 'viostor':
                install.extend([d for d in valid_drvs[dtype]])
            else:
                add.extend([d for d in valid_drvs[dtype]])

        try:
            self._update_driver_database('virtio',
                                         upload=dirname,
                                         certs=certs,
                                         add=add,
                                         install=install,
                                         remove=remove)
        finally:
            with self.mount(readonly=False, silent=True, fatal=False):
                if not self.ismounted:
                    self.out.warn("The boot changes cannot be reverted. "
                                  "The image may be in a corrupted state.")
                else:
                    self._cleanup('virtio')

        self.out.success("VirtIO drivers were successfully installed")
        self.out.info()

    def _update_driver_database(self, namespace, **kwargs):
        """Upload a directory that contains the VirtIO drivers and add scripts
        for installing and removing specific drivers.

        Keyword arguments:
        namespace -- namespace for the cleanup entries
        upload  -- Host directory that contains drivers to upload
        add     -- List of drivers to add to the driver database
        install -- List of drivers to install to the system
        remove  -- List of drivers to remove from the system
        """

        upload = kwargs['upload'] if 'upload' in kwargs else None
        add = kwargs['add'] if 'add' in kwargs else []
        install = kwargs['install'] if 'install' in kwargs else []
        certs = kwargs['certs'] if 'certs' in kwargs else []
        remove = kwargs['remove'] if 'remove' in kwargs else []

        assert len(add) == 0 or upload is not None
        assert len(install) == 0 or upload is not None

        with self.mount(readonly=False, silent=True):
            # Reset admin password
            self._add_cleanup(namespace, self.registry.reset_passwd,
                              self.vm.admin.rid,
                              self.registry.reset_passwd(self.vm.admin.rid))

            # Enable admin account (if needed)
            self._add_cleanup(namespace, self.registry.reset_account,
                              self.vm.admin.rid,
                              self.registry.reset_account(self.vm.admin.rid))

            old = self.registry.update_uac(0)
            if old != 0:
                self._add_cleanup(namespace, self.registry.update_uac, old)

            old = self.registry.update_noautoupdate(1)
            if old != 1:
                self._add_cleanup(namespace, self.registry.update_noautoupdate,
                                  old)

            old = self.registry.update_auoptions(1)
            if old != 1:
                self._add_cleanup(namespace, self.registry.update_auoptions,
                                  old)

            # We disable this with powershell scripts
            self.registry.enable_autologon(self.vm.admin.name)

            # Disable first logon animation (if needed)
            self._add_cleanup(namespace,
                              self.registry.reset_first_logon_animation,
                              self.registry.reset_first_logon_animation(False))

            tmp = uuid.uuid4().hex
            self.image.g.mkdir_p("%s/%s" % (self.systemroot, tmp))

            # This is a hack. We create a function here and pass it to
            # _add_cleanup because self.image.g may change and the _add_cleanup
            # will cache it which is wrong. For older versions of the guestfs
            # library we recreate the g handler in enable_guestfs() and the
            # program will crash if cleanup retains an older value for the
            # guestfs handler.
            def remove_tmp():
                self.image.g.rm_rf("%s/%s" % (self.systemroot, tmp))

            self._add_cleanup(namespace, remove_tmp)

            if upload is not None:
                for fname in os.listdir(upload):
                    full_path = os.path.join(upload, fname)
                    if os.path.isfile(full_path):
                        self.image.g.upload(
                            full_path,
                            "%s/%s/%s" % (self.systemroot, tmp, fname))

            drvs_install = powershell.DRVINST_HEAD

            for cert in certs:
                drvs_install += powershell.ADD_CERTIFICATE % cert

            for driver in install:
                drvs_install += powershell.INSTALL_DRIVER % driver

            for driver in add:
                drvs_install += powershell.ADD_DRIVER % driver

            for driver in remove:
                drvs_install += powershell.REMOVE_DRIVER % driver

            if self.check_version(6, 1) <= 0:
                self._install_viostor_driver(upload)
                old = self.registry.update_devices_dirs("%SystemRoot%\\" + tmp)
                self._add_cleanup(namespace, self.registry.update_devices_dirs,
                                  old, False)
                drvs_install += powershell.DISABLE_AUTOLOGON
            else:
                # In newer windows, in order to reduce the boot process the
                # boot drivers are cached. To be able to boot with viostor, we
                # need to reboot in safe mode.
                drvs_install += powershell.SAFEBOOT

            drvs_install += powershell.DRVINST_TAIL

            target = "%s/%s/InstallDrivers.ps1" % (self.systemroot, tmp)
            self.image.g.write(target, drvs_install.replace('\n', '\r\n'))

            # The -windowstyle option was introduced in PowerShell V2. We need
            # to have at least Windows NT 6.1 (Windows 7 or Windows 2008R2) to
            # make this work.
            hidden_support = self.check_version(6, 1) >= 0
            cmd = (
                '%(drive)s:%(root)s\\System32\\WindowsPowerShell\\v1.0\\'
                'powershell.exe -ExecutionPolicy RemoteSigned %(hidden)s '
                '-File %(drive)s:%(root)s\\%(tmp)s\\InstallDrivers.ps1 '
                '%(drive)s:%(root)s\\%(tmp)s' % {
                    'root': self.systemroot.replace('/', '\\'),
                    'drive': self.systemdrive,
                    'tmp': tmp,
                    'hidden': '-windowstyle hidden' if hidden_support else ""
                })

            # The value name of RunOnce keys can be prefixed with an asterisk
            # (*) to force the program to run even in Safe mode.
            self.registry.runonce({'*InstallDrivers': cmd})

        # Boot the Windows VM to update the driver's database
        self._boot_virtio_vm()

    def _boot_virtio_vm(self):
        """Boot the media and install the VirtIO drivers"""

        old_windows = self.check_version(6, 1) <= 0
        self.image.disable_guestfs()
        try:
            timeout = self.sysprep_params['boot_timeout'].value
            shutdown_timeout = self.sysprep_params['shutdown_timeout'].value
            virtio_timeout = self.sysprep_params['virtio_timeout'].value
            self.out.info("Starting Windows VM ...", False)
            booted = False
            try:
                if old_windows:
                    self.vm.start()
                else:
                    self.vm.interface = 'ide'
                    self.vm.start(extra_disk=('/dev/null', 'virtio'))
                    self.vm.interface = 'virtio'

                self.out.success("started (console on VNC display: %d)" %
                                 self.vm.display)
                self.out.info("Waiting for Windows to boot ...", False)
                if not self.vm.wait_on_serial(timeout):
                    raise FatalError("Windows VM booting timed out!")
                self.out.success('done')
                booted = True
                self.out.info("Installing new drivers ...", False)
                if not self.vm.wait_on_serial(virtio_timeout):
                    raise FatalError("Windows VirtIO installation timed out!")
                self.out.success('done')
                self.out.info('Shutting down ...', False)
                (_, stderr, rc) = self.vm.wait(shutdown_timeout)
                if rc != 0 or "terminating on signal" in stderr:
                    raise FatalError("Windows VM died unexpectedly!\n\n"
                                     "(rc=%d)\n%s" % (rc, stderr))
                self.out.success('done')
            finally:
                self.vm.stop(shutdown_timeout if booted else 1, fatal=False)
        finally:
            self.image.enable_guestfs()

        with self.mount(readonly=True, silent=True):
            self.virtio_state = self.compute_virtio_state()
            viostor_service_found = self.registry.check_viostor_service()

        if not (len(self.virtio_state['viostor']) and viostor_service_found):
            raise FatalError("viostor was not successfully installed")

        if self.check_version(6, 1) > 0:
            # Hopefully restart in safe mode. Newer windows will not boot from
            # a viostor device unless we initially start them in safe mode
            try:
                self.out.info('Rebooting Windows VM in safe mode ...', False)
                self.vm.start()
                (_, stderr, rc) = self.vm.wait(timeout + shutdown_timeout)
                if rc != 0 or "terminating on signal" in stderr:
                    raise FatalError("Windows VM died unexpectedly!\n\n"
                                     "(rc=%d)\n%s" % (rc, stderr))
                self.out.success('done')
            finally:
                self.vm.stop(1, fatal=True)

    def _install_viostor_driver(self, dirname):
        """Quick and dirty installation of the VirtIO SCSI controller driver.
        It is done to make the image boot from the VirtIO disk.

        http://rwmj.wordpress.com/2010/04/30/
            tip-install-a-device-driver-in-a-windows-vm/
        """

        drivers_path = "%s/system32/drivers" % self.systemroot

        try:
            drivers_path = self.image.g.case_sensitive_path(drivers_path)
        except RuntimeError as err:
            raise FatalError("Unable to browse to directory: %s. Reason: %s" %
                             (drivers_path, str(err)))
        viostor = dirname + os.sep + 'viostor.sys'
        try:
            self.image.g.upload(viostor, drivers_path + '/viostor.sys')
        except RuntimeError as err:
            raise FatalError("Unable to upload file %s to %s. Reason: %s" %
                             (viostor, drivers_path, str(err)))

        self.registry.add_viostor()