diff --git a/ci/autopkg_debian.sh b/ci/autopkg_debian.sh
new file mode 100755
index 0000000000000000000000000000000000000000..8a46c1401e0c289ef036a5efb15f20884dd566a7
--- /dev/null
+++ b/ci/autopkg_debian.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env sh
+set -e
+
+PACKAGES_DIR=$1
+
+shift
+
+TEMP_DIR=$(mktemp -d /tmp/devflow_autopkg_XXXXXXX)
+
+# Create the packages
+devflow-autopkg snapshot -b $TEMP_DIR $@
+
+# MOVE the packages
+mkdir -p $PACKAGES_DIR
+mv -n $TEMP_DIR/* $PACKAGES_DIR
+
+echo "Moved packages to: $(pwd)/$PACKAGES_DIR"
+
diff --git a/ci/make_docs.sh b/ci/make_docs.sh
new file mode 100755
index 0000000000000000000000000000000000000000..61a53d990e41bdcf932b6b341adf442435556fdd
--- /dev/null
+++ b/ci/make_docs.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+set -e
+
+DOCS_DIR=$1
+
+cd docs
+make html
+cd -
+
+mkdir -p $DOCS_DIR
+mv -n docs/_build/html/* $DOCS_DIR
+
+echo "Moved docs to to: $(pwd)/$DOCS_DIR"
diff --git a/ci/pep8.sh b/ci/pep8.sh
new file mode 100755
index 0000000000000000000000000000000000000000..e9786a70ca056edb571d567be416b24d7e09fdab
--- /dev/null
+++ b/ci/pep8.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+pep8 image_creator
+
diff --git a/ci/pylint.sh b/ci/pylint.sh
new file mode 100755
index 0000000000000000000000000000000000000000..a200be125452fb975ae4ad9576a31d295d43e896
--- /dev/null
+++ b/ci/pylint.sh
@@ -0,0 +1,3 @@
+!/bin/sh
+
+pylint image_creator
diff --git a/docs/install.rst b/docs/install.rst
index dfd0dd486ff213fc3b7bf28881479d46bbca369a..a7f303169bcc545f0f6539df461bebdd8150fccc 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -32,11 +32,41 @@ method you choose. There are two installation methods available:
 Install snf-image-creator using packages
 ========================================
 
+Debian
+------
+
+For *Debian 7.0 (wheezy)* you can use our official packages found in our
+development repository.
+
+Add the following line to */etc/apt/sources.list*:
+
+.. code-block:: console
+
+   deb http://apt.dev.grnet.gr wheezy/
+
+And resynchronize the package index files from their sources:
+
+.. code-block:: console
+
+   $ sudo apt-get update
+
+You should be able to list the package by calling:
+
+.. code-block:: console
+
+   $ apt-cache showpkg snf-image-creator
+
+And install the package with this command:
+
+.. code-block:: console
+
+   $ apt-get install snf-image-creator
+
 Ubuntu
 ------
 
-For *Ubuntu 12.04 LTS* and *12.10* systems, you can use our official packages
-found in *grnet/synnefo* Lauchpad PPA.
+For *Ubuntu 12.04 LTS*, *12.10* and *13.04* systems, you can use our official
+packages found in *grnet/synnefo* Lauchpad PPA.
 
 Add the synnefo PPA in your system:
 
@@ -47,7 +77,7 @@ Add the synnefo PPA in your system:
 
 If *apt-add-repository* is missing, first install:
 
-*software-properties-common* (Ubuntu 12.10):
+*software-properties-common* (Ubuntu 12.10 & 13.04):
 
 .. code-block:: console
 
@@ -76,14 +106,6 @@ Install the package by issuing:
    If you are asked during the installation to create/update a
    "supermin appliance", choose "Yes".
 
-.. warning::
-   In *Ubuntu 12.10* the current package of libguestfs (1.18-2) is broken. Take
-   a look at the open `bug report <https://bugs.launchpad.net/ubuntu/quantal/+source/libguestfs/+bug/1086974>`_.
-   Until version 1.18-2ubunut1 is out, you may workaround this problem by
-   creating a symlink like this:
-
-   *sudo ln -s /usr/lib/guestfs /usr/lib/x86_64-linux-gnu/guestfs*
-
 Fedora
 ------
 
@@ -113,7 +135,7 @@ CentOS
 ------
 
 For *CentOS 6* you can use our official packages hosted at the *synnefo*
-repository of the openSUSE Build Service.
+repository of the OpenSUSE Build Service.
 
 Add the *synnefo* repository for *CentOS 6* to the yum repositories list:
 
@@ -124,6 +146,38 @@ Add the *synnefo* repository for *CentOS 6* to the yum repositories list:
 
 Check the `Fedora <#fedora>`_ instructions on how to install the software.
 
+OpenSUSE
+--------
+
+For *OpenSUSE 12.3* you can use our official packages hosted at the *synnefo*
+repository of the OpenSUSE Build Service.
+
+Add the *Virtualization* repository for *OpenSUSE 12.3* to *YaST* with the
+*Zypper* package manager:
+
+.. code-block:: console
+
+   $ zypper ar -f http://download.opensuse.org/repositories/Virtualization/openSUSE_12.3/Virtualization.repo
+
+Add the *synnefo* repository:
+
+.. code-block:: console
+
+   $ zypper ar -f http://download.opensuse.org/repositories/home:/GRNET:/synnefo/openSUSE_12.3/home:GRNET:synnefo.repo
+
+To list the *snf-image-creator* package use the following command:
+
+.. code-block:: console
+
+   $ zypper se snf-image-creator
+
+Install the package by issuing:
+
+.. code-block:: console
+
+   $ zypper in snf-image-creator
+
+
 Arch Linux
 ----------
 
diff --git a/docs/overview.rst b/docs/overview.rst
index 2cd3a19236f16cccf3ee3d238bef1f184bdb1cb6..0d57e0ef6fab740d73b4f86172a6db353afe9e41 100644
--- a/docs/overview.rst
+++ b/docs/overview.rst
@@ -35,6 +35,7 @@ deployment as private or public image.
 Image Format
 ============
 
-The extracted images are in diskdump format. This is the recommended format for
+The extracted images are in diskdump format, which is a raw dump of a disk
+device (or file). This is the recommended format for
 `snf-image <https://code.grnet.gr/projects/snf-image>`_, the Ganeti OS
 Definition used by `Synnefo <https://code.grnet.gr/projects/synnefo>`_.
diff --git a/docs/usage.rst b/docs/usage.rst
index 679b319a8ea4476262c9cc44e5461f7f1e1de59b..5dd73d46c7aa912226a5ddb8ad9ae45c860bc7e2 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -17,42 +17,47 @@ snf-image-creator receives the following options:
 
 .. code-block:: console
 
-   $ snf-image-creator --help
-   Usage: snf-image-creator [options] <input_media>
-   
-   Options:
-     --version             show program's version number and exit
-     -h, --help            show this help message and exit
-     -o FILE, --outfile=FILE
-                           dump image to FILE
-     -f, --force           overwrite output files if they exist
-     -s, --silent          output only errors
-     -u FILENAME, --upload=FILENAME
-                           upload the image to the storage service with name FILENAME
-     -r IMAGENAME, --register=IMAGENAME
-                           register the image with the compute service as IMAGENAME
-     -m KEY=VALUE, --metadata=KEY=VALUE
-                           add custom KEY=VALUE metadata to the image
-     -t TOKEN, --token=TOKEN
-                           use this authentication token when
-                           uploading/registering images
-     -a URL, --authentication-url=URL
-                           use this authentication URL when uploading/registering
-                           images
-     -c CLOUD, --cloud=CLOUD
-                           use this saved cloud account to authenticate against a
-                           cloud when uploading/registering images
-     --print-sysprep       print the enabled and disabled system preparation
-                           operations for this input media
-     --enable-sysprep=SYSPREP
-                           run SYSPREP operation on the input media
-     --disable-sysprep=SYSPREP
-                           prevent SYSPREP operation from running on the input
-                           media
-     --no-sysprep          don't perform any system preparation operation
-     --no-shrink           don't shrink any partition
-     --public              register image with the compute service as public
-     --tmpdir=DIR          create large temporary image files under DIR
+  $ snf-image-creator --help
+  Usage: snf-image-creator [options] <input_media>
+
+  Options:
+    --version             show program's version number and exit
+    -h, --help            show this help message and exit
+    -o FILE, --outfile=FILE
+                          dump image to FILE
+    -f, --force           overwrite output files if they exist
+    -s, --silent          output only errors
+    -u FILENAME, --upload=FILENAME
+                          upload the image to the cloud with name FILENAME
+    -r IMAGENAME, --register=IMAGENAME
+                          register the image with a cloud as IMAGENAME
+    -m KEY=VALUE, --metadata=KEY=VALUE
+                          add custom KEY=VALUE metadata to the image
+    -t TOKEN, --token=TOKEN
+                          use this authentication token when
+                          uploading/registering images
+    -a URL, --authentication-url=URL
+                          use this authentication URL when uploading/registering
+                          images
+    -c CLOUD, --cloud=CLOUD
+                          use this saved cloud account to authenticate against a
+                          cloud when uploading/registering images
+    --print-syspreps      print the enabled and disabled system preparation
+                          operations for this input media
+    --enable-sysprep=SYSPREP
+                          run SYSPREP operation on the input media
+    --disable-sysprep=SYSPREP
+                          prevent SYSPREP operation from running on the input
+                          media
+    --print-sysprep-params
+                          print the needed sysprep parameters for this input
+                          media
+    --sysprep-param=SYSPREP_PARAMS
+                          Add KEY=VALUE system preparation parameter
+    --no-sysprep          don't perform any system preparation operation
+    --no-shrink           don't shrink any partition
+    --public              register image with the cloud as public
+    --tmpdir=DIR          create large temporary image files under DIR
 
 Most input options are self-describing. If you want to save a local copy of
 the image you create, provide a filename using the *-o* option. To upload the
@@ -298,10 +303,11 @@ Limitations
 Supported operating systems
 ---------------------------
 
-*snf-image-creator* can only fully function on input media hosting *Linux*
-systems. The program will detect the needed metadata and you may use it to
-upload and register other *Unix* or *Windows* images, but you cannot use it to
-shrink them or perform system preparation operations.
+*snf-image-creator* can only fully function on input media hosting *Linux*,
+*FreeBSD* (tested on version 9.1) and *Windows* (Server 2008 R2 and Server
+2012) systems. The program will detect the needed metadata and you may use it
+to upload and register other *Unix* images, but you cannot use it to shrink
+them or perform system preparation operations.
 
 Logical Volumes
 ---------------
@@ -330,6 +336,13 @@ if a system can boot with para-virtualized disk controller by launching it with
 kvm using the *if=virtio* option (see the kvm command in the
 `Creating a new image`_ section).
 
+For Windows and FreeBSD systems, the needed drivers need to be manually
+downloaded and installed on the media before the image creation process takes
+place. For *FreeBSD* the virtio drivers can be found
+`here <http://people.freebsd.org/~kuriyama/virtio/>`_. For Windows the drivers
+are hosted by the
+`Fedora Project <http://alt.fedoraproject.org/pub/alt/virtio-win/latest/images/>`_.
+
 Some caveats on image creation
 ==============================
 
diff --git a/image_creator/bundle_volume.py b/image_creator/bundle_volume.py
index eec084f847f58c967a68da67858b0a417f817ae2..772e5043d070abea089f0c92f21ac54c480c6444 100644
--- a/image_creator/bundle_volume.py
+++ b/image_creator/bundle_volume.py
@@ -267,8 +267,11 @@ class BundleVolume(object):
         name = os.path.basename(dev) + "_" + uuid.uuid4().hex
         tablefd, table = tempfile.mkstemp()
         try:
-            size = end - start + 1
-            os.write(tablefd, "0 %d linear %s %d" % (size, dev, start))
+            try:
+                size = end - start + 1
+                os.write(tablefd, "0 %d linear %s %d" % (size, dev, start))
+            finally:
+                os.close(tablefd)
             dmsetup('create', "%sp%d" % (name, num), table)
         finally:
             os.unlink(table)
@@ -301,7 +304,7 @@ class BundleVolume(object):
         mpoints = []
         for entry in self._read_fstable('/proc/mounts'):
             if entry.mpoint.startswith(os.path.abspath(target)):
-                    mpoints.append(entry.mpoint)
+                mpoints.append(entry.mpoint)
 
         mpoints.sort()
         for mpoint in reversed(mpoints):
@@ -333,10 +336,9 @@ class BundleVolume(object):
                 continue
 
             dirname = mpoint
-            basename = ''
             found_ancestor = False
             while dirname != '/':
-                (dirname, basename) = os.path.split(dirname)
+                (dirname, _) = os.path.split(dirname)
                 if dirname in excluded:
                     found_ancestor = True
                     break
diff --git a/image_creator/dialog_menu.py b/image_creator/dialog_menu.py
index 11dc1a72e91d1bb629b5bc5a5dc640a56807117c..351f0b9687c17960064fc13b8f66a14225ead735 100644
--- a/image_creator/dialog_menu.py
+++ b/image_creator/dialog_menu.py
@@ -68,6 +68,8 @@ CONFIGURATION_TASKS = [
     ("File injection", ["EnforcePersonality"], ["windows", "linux"])
 ]
 
+SYSPREP_PARAM_MAXLEN = 20
+
 
 class MetadataMonitor(object):
     """Monitors image metadata chages"""
@@ -157,7 +159,7 @@ def upload_image(session):
         session['upload'] = filename
         break
 
-    gauge = GaugeOutput(d, "Image Upload", "Uploading...")
+    gauge = GaugeOutput(d, "Image Upload", "Uploading ...")
     try:
         out = image.out
         out.add(gauge)
@@ -175,7 +177,7 @@ def upload_image(session):
                                       "Calculating block hashes",
                                       "Uploading missing blocks")
                 # Upload md5sum file
-                out.output("Uploading md5sum file...")
+                out.output("Uploading md5sum file ...")
                 md5str = "%s %s\n" % (session['checksum'], filename)
                 kamaki.upload(StringIO.StringIO(md5str), size=len(md5str),
                               remote_path="%s.md5sum" % filename)
@@ -243,26 +245,27 @@ def register_image(session):
             metadata[key] = 'yes'
 
     img_type = "public" if is_public else "private"
-    gauge = GaugeOutput(d, "Image Registration", "Registering image...")
+    gauge = GaugeOutput(d, "Image Registration", "Registering image ...")
     try:
         out = session['image'].out
         out.add(gauge)
         try:
             try:
-                out.output("Registering %s image with the cloud..." % img_type)
+                out.output("Registering %s image with the cloud ..." %
+                           img_type)
                 kamaki = Kamaki(session['account'], out)
                 result = kamaki.register(name, session['pithos_uri'], metadata,
                                          is_public)
                 out.success('done')
                 # Upload metadata file
-                out.output("Uploading metadata file...")
+                out.output("Uploading metadata file ...")
                 metastring = unicode(json.dumps(result, ensure_ascii=False))
                 kamaki.upload(StringIO.StringIO(metastring),
                               size=len(metastring),
                               remote_path="%s.meta" % session['upload'])
                 out.success("done")
                 if is_public:
-                    out.output("Sharing metadata and md5sum files...")
+                    out.output("Sharing metadata and md5sum files ...")
                     kamaki.share("%s.meta" % session['upload'])
                     kamaki.share("%s.md5sum" % session['upload'])
                     out.success('done')
@@ -396,9 +399,9 @@ def kamaki_menu(session):
                 if len(Kamaki.get_clouds()):
                     default_item = "Cloud"
                 else:
-                    default_time = "Add/Edit"
+                    default_item = "Add/Edit"
             else:
-                default_time = "Delete"
+                default_item = "Delete"
         elif choice == "Cloud":
             default_item = "Cloud"
             clouds = Kamaki.get_clouds()
@@ -618,6 +621,75 @@ def exclude_tasks(session):
     return True
 
 
+def sysprep_params(session):
+    """Collect the needed sysprep parameters"""
+    d = session['dialog']
+    image = session['image']
+
+    available = image.os.sysprep_params
+    needed = image.os.needed_sysprep_params
+
+    if len(needed) == 0:
+        return True
+
+    def print_form(names, extra_button=False):
+        """print the dialog form providing sysprep_params"""
+        fields = []
+        for name in names:
+            param = needed[name]
+            default = str(available[name]) if name in available else ""
+            fields.append(("%s: " % param.description, default,
+                           SYSPREP_PARAM_MAXLEN))
+
+        kwargs = {}
+        if extra_button:
+            kwargs['extra_button'] = 1
+            kwargs['extra_label'] = "Advanced"
+
+        txt = "Please provide the following system preparation parameters:"
+        return d.form(txt, height=13, width=WIDTH, form_height=len(fields),
+                      fields=fields, **kwargs)
+
+    def check_params(names, values):
+        """check if the provided sysprep parameters have leagal values"""
+        for i in range(len(names)):
+            param = needed[names[i]]
+            try:
+                normalized = param.type(values[i])
+                if param.validate(normalized):
+                    image.os.sysprep_params[names[i]] = normalized
+                    continue
+            except ValueError:
+                pass
+
+            d.msgbox("Invalid value for parameter: `%s'" % names[i],
+                     width=SMALL_WIDTH)
+            return False
+        return True
+
+    simple_names = [k for k, v in needed.items() if v.default is None]
+    advanced_names = [k for k, v in needed.items() if v.default is not None]
+
+    while 1:
+        code, output = print_form(simple_names, extra_button=True)
+
+        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
+            return False
+        if code == d.DIALOG_EXTRA:
+            while 1:
+                code, output = print_form(advanced_names)
+                if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
+                    break
+                if check_params(advanced_names, output):
+                    break
+            continue
+
+        if check_params(simple_names, output):
+            break
+
+    return True
+
+
 def sysprep(session):
     """Perform various system preperation tasks on the image"""
     d = session['dialog']
@@ -634,9 +706,6 @@ def sysprep(session):
 
     wrapper = textwrap.TextWrapper(width=WIDTH - 5)
 
-    help_title = "System Preperation Tasks"
-    sysprep_help = "%s\n%s\n\n" % (help_title, '=' * len(help_title))
-
     syspreps = image.os.list_syspreps()
 
     if len(syspreps) == 0:
@@ -647,6 +716,10 @@ def sysprep(session):
     while 1:
         choices = []
         index = 0
+
+        help_title = "System Preperation Tasks"
+        sysprep_help = "%s\n%s\n\n" % (help_title, '=' * len(help_title))
+
         for sysprep in syspreps:
             name, descr = image.os.sysprep_info(sysprep)
             display_name = name.replace('-', ' ').capitalize()
@@ -681,6 +754,9 @@ def sysprep(session):
                          title="System Preperation", width=SMALL_WIDTH)
                 continue
 
+            if not sysprep_params(session):
+                continue
+
             infobox = InfoBoxOutput(d, "Image Configuration")
             try:
                 image.out.add(infobox)
@@ -804,7 +880,7 @@ def main_menu(session):
                 break
         elif choice == "Reset":
             if confirm_reset(d):
-                d.infobox("Resetting snf-image-creator. Please wait...",
+                d.infobox("Resetting snf-image-creator. Please wait ...",
                           width=SMALL_WIDTH)
                 raise Reset
         elif choice == "Help":
diff --git a/image_creator/dialog_wizard.py b/image_creator/dialog_wizard.py
index 8abcfed6e90dbb718572bc6080e4fcaf194ec42d..0856d8ae1a7768cfaa6d03324ea6991b94497ed6 100644
--- a/image_creator/dialog_wizard.py
+++ b/image_creator/dialog_wizard.py
@@ -49,6 +49,7 @@ from image_creator.dialog_util import extract_image, update_background_title, \
 
 PAGE_WIDTH = 70
 PAGE_HEIGHT = 10
+SYSPREP_PARAM_MAXLEN = 20
 
 
 class WizardExit(Exception):
@@ -208,6 +209,36 @@ class WizardInputPage(WizardPage):
         return self.NEXT
 
 
+class WizardFormPage(WizardPage):
+    """Represents a Form in a wizard"""
+
+    def __init__(self, name, display_name, text, fields, **kargs):
+        super(WizardFormPage, self).__init__(name, display_name, text, **kargs)
+        self.fields = fields
+
+    def run(self, session, title):
+        d = session['dialog']
+        w = session['wizard']
+
+        field_lenght = len(self.fields())
+        form_height = field_lenght if field_lenght < PAGE_HEIGHT - 4 \
+            else PAGE_HEIGHT - 4
+
+        (code, output) = d.form(
+            self.text, width=PAGE_WIDTH, height=PAGE_HEIGHT,
+            form_height=form_height, ok_label="Next", cancel="Back",
+            fields=self.fields(), title=title)
+
+        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
+            return self.PREV
+
+        w[self.name] = self.validate(output)
+        self.default = output
+        self.info = "%s: %s" % (self.display_name, self.display(w[self.name]))
+
+        return self.NEXT
+
+
 class WizardMenuPage(WizardPageWthChoices):
     """Represents a menu dialog with available choices in a wizard"""
 
@@ -250,9 +281,11 @@ class WizardMenuPage(WizardPageWthChoices):
 def start_wizard(session):
     """Run the image creation wizard"""
 
-    distro = session['image'].distro
-    ostype = session['image'].ostype
+    image = session['image']
+    distro = image.distro
+    ostype = image.ostype
 
+    # Create Cloud Wizard Page
     def cloud_choices():
         choices = []
         for (name, cloud) in Kamaki.get_clouds().items():
@@ -279,7 +312,7 @@ def start_wizard(session):
                 if edit_cloud(session, cloud):
                     return cloud
 
-            raise WizardInvalidData
+            raise WizardReloadPage
 
         return cloud
 
@@ -289,16 +322,59 @@ def start_wizard(session):
         choices=cloud_choices, extra_label="Add", extra=cloud_add,
         title="Clouds", validate=cloud_validate, fallback=cloud_none_available)
 
+    # Create Image Name Wizard Page
     name = WizardInputPage(
         "ImageName", "Image Name", "Please provide a name for the image:",
         title="Image Name", default=ostype if distro == "unknown" else distro)
 
+    # Create Image Description Wizard Page
     descr = WizardInputPage(
         "ImageDescription", "Image Description",
         "Please provide a description for the image:",
         title="Image Description", default=session['metadata']['DESCRIPTION']
         if 'DESCRIPTION' in session['metadata'] else '')
 
+    # Create Sysprep Params Wizard Page
+    needed = image.os.needed_sysprep_params
+    # Only show the parameters that don't have default values
+    param_names = [param for param in needed if needed[param].default is None]
+
+    def sysprep_params_fields():
+        fields = []
+        available = image.os.sysprep_params
+        for name in param_names:
+            text = needed[name].description
+            default = str(available[name]) if name in available else ""
+            fields.append(("%s: " % text, default, SYSPREP_PARAM_MAXLEN))
+        return fields
+
+    def sysprep_params_validate(answer):
+        params = {}
+        for i in range(len(answer)):
+            try:
+                value = needed[param_names[i]].type(answer[i])
+                if needed[param_names[i]].validate(value):
+                    params[param_names[i]] = value
+                    continue
+            except ValueError:
+                pass
+
+            session['dialog'].msgbox("Invalid value for parameter `%s'" %
+                                     param_names[i])
+            raise WizardReloadPage
+        return params
+
+    def sysprep_params_display(params):
+        return ",".join(["%s=%s" % (key, val) for key, val in params.items()])
+
+    sysprep_params = WizardFormPage(
+        "SysprepParams", "Sysprep Parameters",
+        "Prease fill in the following system preparation parameters:",
+        title="System Preparation Parameters", fields=sysprep_params_fields,
+        display=sysprep_params_display, validate=sysprep_params_validate
+    ) if len(needed) != 0 else None
+
+    # Create Image Registration Wizard Page
     def registration_choices():
         return [("Private", "Image is accessible only by this user"),
                 ("Public", "Everyone can create VMs from this image")]
@@ -313,6 +389,8 @@ def start_wizard(session):
     w.add_page(cloud)
     w.add_page(name)
     w.add_page(descr)
+    if sysprep_params is not None:
+        w.add_page(sysprep_params)
     w.add_page(registration)
 
     if w.run():
@@ -336,6 +414,7 @@ def create_image(session):
         out.clear()
 
         #Sysprep
+        image.os.sysprep_params.update(wizard['SysprepParams'])
         image.os.do_sysprep()
         metadata = image.os.meta
 
diff --git a/image_creator/disk.py b/image_creator/disk.py
index 76a53aa4102a9054a459aa7214c3108786d7365c..e4b6bc8913ed3e3664823ebd1f8eb6da0a0fdb03 100644
--- a/image_creator/disk.py
+++ b/image_creator/disk.py
@@ -174,20 +174,23 @@ class Disk(object):
         snapshot = uuid.uuid4().hex
         tablefd, table = tempfile.mkstemp()
         try:
-            os.write(tablefd, "0 %d snapshot %s %s n 8" %
-                              (int(size), sourcedev, cowdev))
+            try:
+                os.write(tablefd, "0 %d snapshot %s %s n 8" %
+                                  (int(size), sourcedev, cowdev))
+            finally:
+                os.close(tablefd)
+
             dmsetup('create', snapshot, table)
             self._add_cleanup(try_fail_repeat, dmsetup, 'remove', snapshot)
-
         finally:
             os.unlink(table)
         self.out.success('done')
         return "/dev/mapper/%s" % snapshot
 
-    def get_image(self, media):
+    def get_image(self, media, **kargs):
         """Returns a newly created Image instance."""
 
-        image = Image(media, self.out)
+        image = Image(media, self.out, **kargs)
         self._images.append(image)
         image.enable()
         return image
diff --git a/image_creator/image.py b/image_creator/image.py
index b27c1ebcd5af51c9aea36157d235eb19b02edeff..686fad621f7e7897c8a44e560b9ee4060fd43299 100644
--- a/image_creator/image.py
+++ b/image_creator/image.py
@@ -45,53 +45,46 @@ from sendfile import sendfile
 class Image(object):
     """The instances of this class can create images out of block devices."""
 
-    def __init__(self, device, output, bootable=True, meta={}):
+    def __init__(self, device, output, **kargs):
         """Create a new Image instance"""
 
         self.device = device
         self.out = output
-        self.bootable = bootable
-        self.meta = meta
+
+        self.meta = kargs['meta'] if 'meta' in kargs else {}
+        self.sysprep_params = \
+            kargs['sysprep_params'] if 'sysprep_params' in kargs else {}
+
         self.progress_bar = None
         self.guestfs_device = None
         self.size = 0
 
         self.g = guestfs.GuestFS()
-        self.g.add_drive_opts(self.device, readonly=0, format="raw")
+        self.guestfs_enabled = False
+        self.guestfs_version = self.g.version()
 
-        # Before version 1.17.14 the recovery process, which is a fork of the
-        # original process that called libguestfs, did not close its inherited
-        # file descriptors. This can cause problems especially if the parent
-        # process has opened pipes. Since the recovery process is an optional
-        # feature of libguestfs, it's better to disable it.
-        self.g.set_recovery_proc(0)
-        version = self.g.version()
-        if version['major'] > 1 or \
-            (version['major'] == 1 and (version['minor'] >= 18 or
-                                        (version['minor'] == 17 and
-                                         version['release'] >= 14))):
-            self.g.set_recovery_proc(1)
-            self.out.output("Enabling recovery proc")
+    def check_guestfs_version(self, major, minor, release):
+        """Checks if the version of the used libguestfs is smaller, equal or
+        greater than the one specified by the major, minor and release triplet
 
-        #self.g.set_trace(1)
-        #self.g.set_verbose(1)
+        Returns:
+            < 0 if the installed version is smaller than the specified one
+            = 0 if they are equal
+            > 0 if the installed one is greater than the specified one
+        """
 
-        self.guestfs_enabled = False
+        for (a, b) in (self.guestfs_version['major'], major), \
+                (self.guestfs_version['minor'], minor), \
+                (self.guestfs_version['release'], release):
+            if a != b:
+                return a - b
+
+        return 0
 
     def enable(self):
         """Enable a newly created Image instance"""
 
-        self.out.output('Launching helper VM (may take a while) ...', False)
-        # self.progressbar = self.out.Progress(100, "Launching helper VM",
-        #                                     "percent")
-        # eh = self.g.set_event_callback(self.progress_callback,
-        #                               guestfs.EVENT_PROGRESS)
-        self.g.launch()
-        self.guestfs_enabled = True
-        # self.g.delete_event_callback(eh)
-        # self.progressbar.success('done')
-        # self.progressbar = None
-        self.out.success('done')
+        self.enable_guestfs()
 
         self.out.output('Inspecting Operating System ...', False)
         roots = self.g.inspect_os()
@@ -112,6 +105,70 @@ class Image(object):
             'found a(n) %s system' %
             self.ostype if self.distro == "unknown" else self.distro)
 
+    def enable_guestfs(self):
+        """Enable the guestfs handler"""
+
+        if self.guestfs_enabled:
+            self.out.warn("Guestfs is already enabled")
+            return
+
+        # Before version 1.18.4 the behaviour of kill_subprocess was different
+        # and you need to reset the guestfs handler to relaunch a previously
+        # shut down qemu backend
+        if self.check_guestfs_version(1, 18, 4) < 0:
+            self.g = guestfs.GuestFS()
+
+        self.g.add_drive_opts(self.device, readonly=0, format="raw")
+
+        # Before version 1.17.14 the recovery process, which is a fork of the
+        # original process that called libguestfs, did not close its inherited
+        # file descriptors. This can cause problems especially if the parent
+        # process has opened pipes. Since the recovery process is an optional
+        # feature of libguestfs, it's better to disable it.
+        if self.check_guestfs_version(1, 17, 14) >= 0:
+            self.out.output("Enabling recovery proc")
+            self.g.set_recovery_proc(1)
+        else:
+            self.g.set_recovery_proc(0)
+
+        #self.g.set_trace(1)
+        #self.g.set_verbose(1)
+
+        self.out.output('Launching helper VM (may take a while) ...', False)
+        # self.progressbar = self.out.Progress(100, "Launching helper VM",
+        #                                     "percent")
+        # eh = self.g.set_event_callback(self.progress_callback,
+        #                               guestfs.EVENT_PROGRESS)
+        self.g.launch()
+        self.guestfs_enabled = True
+        # self.g.delete_event_callback(eh)
+        # self.progressbar.success('done')
+        # self.progressbar = None
+
+        if self.check_guestfs_version(1, 18, 4) < 0:
+            self.g.inspect_os()  # some calls need this
+
+        self.out.success('done')
+
+    def disable_guestfs(self):
+        """Disable the guestfs handler"""
+
+        if not self.guestfs_enabled:
+            self.out.warn("Guestfs is already disabled")
+            return
+
+        self.out.output("Shutting down helper VM ...", False)
+        self.g.sync()
+        # guestfs_shutdown which is the prefered way to shutdown the backend
+        # process was introduced in version 1.19.16
+        if self.check_guestfs_version(1, 19, 16) >= 0:
+            self.g.shutdown()
+        else:
+            self.g.kill_subprocess()
+
+        self.guestfs_enabled = False
+        self.out.success('done')
+
     def _get_os(self):
         """Return an OS class instance for this image"""
         if hasattr(self, "_os"):
@@ -121,7 +178,7 @@ class Image(object):
             self.enable()
 
         cls = os_cls(self.distro, self.ostype)
-        self._os = cls(self.root, self.g, self.out)
+        self._os = cls(self, sysprep_params=self.sysprep_params)
 
         self._os.collect_metadata()
 
diff --git a/image_creator/main.py b/image_creator/main.py
index bff95d964fcf18cdebb843ea6c42ab7c671ff5dd..c3281460e0bdeffe2fda1f74cf5e731ba4d4a3e9 100644
--- a/image_creator/main.py
+++ b/image_creator/main.py
@@ -108,7 +108,7 @@ def parse_options(input_args):
                       "authenticate against a cloud when "
                       "uploading/registering images")
 
-    parser.add_option("--print-sysprep", dest="print_sysprep", default=False,
+    parser.add_option("--print-syspreps", dest="print_syspreps", default=False,
                       help="print the enabled and disabled system preparation "
                       "operations for this input media", action="store_true")
 
@@ -121,6 +121,14 @@ def parse_options(input_args):
                       "input media", default=[], action="append",
                       metavar="SYSPREP")
 
+    parser.add_option("--print-sysprep-params", dest="print_sysprep_params",
+                      default=False, help="print the needed sysprep parameters"
+                      " for this input media", action="store_true")
+
+    parser.add_option("--sysprep-param", dest="sysprep_params", default=[],
+                      help="Add KEY=VALUE system preparation parameter",
+                      action="append")
+
     parser.add_option("--no-sysprep", dest="sysprep", default=True,
                       help="don't perform any system preparation operation",
                       action="store_false")
@@ -170,6 +178,16 @@ def parse_options(input_args):
         meta[key] = value
     options.metadata = meta
 
+    sysprep_params = {}
+    for p in options.sysprep_params:
+        try:
+            key, value = p.split('=', 1)
+        except ValueError:
+            raise FatalError("Sysprep parameter optiont: `%s' is not in "
+                             "KEY=VALUE format." % p)
+        sysprep_params[key] = value
+    options.sysprep_params = sysprep_params
+
     return options
 
 
@@ -177,9 +195,9 @@ def image_creator():
     options = parse_options(sys.argv[1:])
 
     if options.outfile is None and not options.upload and not \
-            options.print_sysprep:
-        raise FatalError("At least one of `-o', `-u' or `--print-sysprep' "
-                         "must be set")
+            options.print_syspreps and not options.print_sysprep_params:
+        raise FatalError("At least one of `-o', `-u', `--print-syspreps' or "
+                         "`--print-sysprep-params' must be set")
 
     if options.silent:
         out = SilentOutput()
@@ -253,7 +271,7 @@ def image_creator():
     try:
         snapshot = disk.snapshot()
 
-        image = disk.get_image(snapshot)
+        image = disk.get_image(snapshot, sysprep_params=options.sysprep_params)
 
         for sysprep in options.disabled_syspreps:
             image.os.disable_sysprep(image.os.get_sysprep_by_name(sysprep))
@@ -261,10 +279,14 @@ def image_creator():
         for sysprep in options.enabled_syspreps:
             image.os.enable_sysprep(image.os.get_sysprep_by_name(sysprep))
 
-        if options.print_sysprep:
+        if options.print_syspreps:
             image.os.print_syspreps()
             out.output()
 
+        if options.print_sysprep_params:
+            image.os.print_sysprep_params()
+            out.output()
+
         if options.outfile is None and not options.upload:
             return 0
 
diff --git a/image_creator/os_type/__init__.py b/image_creator/os_type/__init__.py
index f3a33d94025d4b962df15aa724bf47a15205dfbe..19e63fc520bef1164fa90702a7b758c747842ff5 100644
--- a/image_creator/os_type/__init__.py
+++ b/image_creator/os_type/__init__.py
@@ -41,6 +41,8 @@ from image_creator.util import FatalError
 
 import textwrap
 import re
+from collections import namedtuple
+from functools import wraps
 
 
 def os_cls(distro, osfamily):
@@ -60,35 +62,85 @@ def os_cls(distro, osfamily):
 
 
 def add_prefix(target):
+    """Decorator that adds a prefix to the result of a function"""
     def wrapper(self, *args):
         prefix = args[0]
-        return map(lambda x: prefix + x, target(self, *args))
+        return [prefix + path for path in target(self, *args)]
     return wrapper
 
 
-def sysprep(enabled=True):
+def sysprep(message, enabled=True, **kwargs):
     """Decorator for system preparation tasks"""
+    def wrapper(method):
+        method.sysprep = True
+        method.enabled = enabled
+        method.executed = False
+
+        for key, val in kwargs.items():
+            setattr(method, key, val)
+
+        @wraps(method)
+        def inner(self, print_message=True):
+            if print_message:
+                self.out.output(message)
+            return method(self)
+
+        return inner
+    return wrapper
+
+
+def add_sysprep_param(name, type, default, descr, validate=lambda x: True):
+    """Decorator for __init__ that adds the definition for a system preparation
+    parameter in an instance of a os_type class
+    """
+    def wrapper(init):
+        @wraps(init)
+        def inner(self, *args, **kwargs):
+            init(self, *args, **kwargs)
+            self.needed_sysprep_params[name] = \
+                self.SysprepParam(type, default, descr, validate)
+            if default is not None:
+                self.sysprep_params[name] = default
+        return inner
+    return wrapper
+
+
+def del_sysprep_param(name):
+    """Decorator for __init__ that deletes a previously added sysprep parameter
+    definition from an instance of a os_type class.
+    """
     def wrapper(func):
-        func.sysprep = True
-        func.enabled = enabled
-        func.executed = False
-        return func
+        @wraps(func)
+        def inner(self, *args, **kwargs):
+            del self.needed_sysprep_params[name]
+            func(self, *args, **kwargs)
+        return inner
     return wrapper
 
 
 class OSBase(object):
     """Basic operating system class"""
 
-    def __init__(self, rootdev, ghandler, output):
-        self.root = rootdev
-        self.g = ghandler
-        self.out = output
+    SysprepParam = namedtuple('SysprepParam',
+                              ['type', 'default', 'description', 'validate'])
+
+    def __init__(self, image, **kargs):
+        self.image = image
+
+        self.root = image.root
+        self.out = image.out
+
+        self.needed_sysprep_params = {}
+        self.sysprep_params = \
+            kargs['sysprep_params'] if 'sysprep_params' in kargs else {}
+
         self.meta = {}
+        self.mounted = False
 
         # Many guestfs compilations don't support scrub
         self._scrub_support = True
         try:
-            self.g.available(['scrub'])
+            self.image.g.available(['scrub'])
         except RuntimeError:
             self._scrub_support = False
 
@@ -117,7 +169,10 @@ class OSBase(object):
         """Returns information about a sysprep object"""
         assert self._is_sysprep(obj), "Object is not a sysprep"
 
-        return (obj.__name__.replace('_', '-'), textwrap.dedent(obj.__doc__))
+        SysprepInfo = namedtuple("SysprepInfo", "name description")
+
+        return SysprepInfo(obj.__name__.replace('_', '-'),
+                           textwrap.dedent(obj.__doc__))
 
     def get_sysprep_by_name(self, name):
         """Returns the sysprep object with the given name"""
@@ -148,8 +203,8 @@ class OSBase(object):
         """Print enabled and disabled system preparation operations."""
 
         syspreps = self.list_syspreps()
-        enabled = filter(lambda x: x.enabled, syspreps)
-        disabled = filter(lambda x: not x.enabled, syspreps)
+        enabled = [sysprep for sysprep in syspreps if sysprep.enabled]
+        disabled = [sysprep for sysprep in syspreps if not sysprep.enabled]
 
         wrapper = textwrap.TextWrapper()
         wrapper.subsequent_indent = '\t'
@@ -174,6 +229,21 @@ class OSBase(object):
                 descr = wrapper.fill(textwrap.dedent(sysprep.__doc__))
                 self.out.output('    %s:\n%s\n' % (name, descr))
 
+    def print_sysprep_params(self):
+        """Print the system preparation parameter the user may use"""
+
+        self.out.output("Needed system preparation parameters:")
+
+        if len(self.needed_sysprep_params) == 0:
+            self.out.output("(none)")
+            return
+
+        for name, param in self.needed_sysprep_params.items():
+            self.out.output("\t%s (%s): %s" %
+                            (param.description, name,
+                             self.sysprep_params[name] if name in
+                             self.sysprep_params else "(none)"))
+
     def do_sysprep(self):
         """Prepare system for image creation."""
 
@@ -183,8 +253,7 @@ class OSBase(object):
 
             self.out.output('Preparing system for image creation:')
 
-            tasks = self.list_syspreps()
-            enabled = filter(lambda x: x.enabled, tasks)
+            enabled = [task for task in self.list_syspreps() if task.enabled]
 
             size = len(enabled)
             cnt = 0
@@ -218,7 +287,7 @@ class OSBase(object):
         """Umount all mounted filesystems."""
 
         self.out.output("Umounting the media ...", False)
-        self.g.umount_all()
+        self.image.g.umount_all()
         self.mounted = False
         self.out.success('done')
 
@@ -229,12 +298,12 @@ class OSBase(object):
     @add_prefix
     def _ls(self, directory):
         """List the name of all files under a directory"""
-        return self.g.ls(directory)
+        return self.image.g.ls(directory)
 
     @add_prefix
     def _find(self, directory):
         """List the name of all files recursively under a directory"""
-        return self.g.find(directory)
+        return self.image.g.find(directory)
 
     def _foreach_file(self, directory, action, **kargs):
         """Perform an action recursively on all files under a directory.
@@ -263,7 +332,7 @@ class OSBase(object):
         ftype = None if 'ftype' not in kargs else kargs['ftype']
         has_ftype = lambda x, y: y is None and True or x['ftyp'] == y
 
-        for f in self.g.readdir(directory):
+        for f in self.image.g.readdir(directory):
             if f['name'] in ('.', '..'):
                 continue
 
@@ -280,17 +349,20 @@ class OSBase(object):
 
     def _do_collect_metadata(self):
         """helper method for collect_metadata"""
-        self.meta['ROOT_PARTITION'] = "%d" % self.g.part_to_partnum(self.root)
-        self.meta['OSFAMILY'] = self.g.inspect_get_type(self.root)
-        self.meta['OS'] = self.g.inspect_get_distro(self.root)
+        self.meta['ROOT_PARTITION'] = \
+            "%d" % self.image.g.part_to_partnum(self.root)
+        self.meta['OSFAMILY'] = self.image.g.inspect_get_type(self.root)
+        self.meta['OS'] = self.image.g.inspect_get_distro(self.root)
         if self.meta['OS'] == "unknown":
             self.meta['OS'] = self.meta['OSFAMILY']
-        self.meta['DESCRIPTION'] = self.g.inspect_get_product_name(self.root)
+        self.meta['DESCRIPTION'] = \
+            self.image.g.inspect_get_product_name(self.root)
 
     def _do_mount(self, readonly):
         """helper method for mount"""
         try:
-            self.g.mount_options('ro' if readonly else 'rw', self.root, '/')
+            self.image.g.mount_options(
+                'ro' if readonly else 'rw', self.root, '/')
         except RuntimeError as msg:
             self.out.warn("unable to mount the root partition: %s" % msg)
             return False
diff --git a/image_creator/os_type/freebsd.py b/image_creator/os_type/freebsd.py
index 02bd8dc8ae2a9747cc810ba447bb6b1f9ce5adda..5f814818eb8a243a26b141be7d70c9284ab60446 100644
--- a/image_creator/os_type/freebsd.py
+++ b/image_creator/os_type/freebsd.py
@@ -42,20 +42,14 @@ import re
 
 class Freebsd(Unix):
     """OS class for FreeBSD Unix-like os"""
-    def __init__(self, rootdev, ghandler, output):
-        super(Freebsd, self).__init__(rootdev, ghandler, output)
 
-    @sysprep()
-    def cleanup_password(self, print_header=True):
+    @sysprep("Cleaning up passwords & locking all user accounts")
+    def cleanup_password(self):
         """Remove all passwords and lock all user accounts"""
 
-        if print_header:
-            self.out.output("Cleaning up passwords & locking all user "
-                            "accounts")
-
         master_passwd = []
 
-        for line in self.g.cat('/etc/master.passwd').splitlines():
+        for line in self.image.g.cat('/etc/master.passwd').splitlines():
 
             # Check for empty or comment lines
             if len(line.split('#')[0]) == 0:
@@ -68,10 +62,11 @@ class Freebsd(Unix):
 
             master_passwd.append(":".join(fields))
 
-        self.g.write('/etc/master.passwd', "\n".join(master_passwd) + '\n')
+        self.image.g.write(
+            '/etc/master.passwd', "\n".join(master_passwd) + '\n')
 
         # Make sure no one can login on the system
-        self.g.rm_rf('/etc/spwd.db')
+        self.image.g.rm_rf('/etc/spwd.db')
 
     def _do_collect_metadata(self):
         """Collect metadata about the OS"""
@@ -94,7 +89,7 @@ class Freebsd(Unix):
             '^([^:]+):((?:![^:]+)|(?:[^!*][^:]+)|):(?:[^:]*:){7}(?:[^:]*)'
         )
 
-        for line in self.g.cat('/etc/master.passwd').splitlines():
+        for line in self.image.g.cat('/etc/master.passwd').splitlines():
             line = line.split('#')[0]
             match = regexp.match(line)
             if not match:
@@ -116,7 +111,7 @@ class Freebsd(Unix):
         # libguestfs can't handle correct freebsd partitions on a GUID
         # Partition Table. We have to do the translation to linux device names
         # ourselves
-        guid_device = re.compile('^/dev/((?:ada)|(?:vtbd))(\d+)p(\d+)$')
+        guid_device = re.compile(r'^/dev/((?:ada)|(?:vtbd))(\d+)p(\d+)$')
 
         mopts = "ufstype=ufs2,%s" % ('ro' if readonly else 'rw')
         for mp, dev in self._mountpoints():
@@ -126,7 +121,7 @@ class Freebsd(Unix):
                 group3 = int(match.group(3))
                 dev = '/dev/sd%c%d' % (chr(ord('a') + group2), group3)
             try:
-                self.g.mount_vfs(mopts, 'ufs', dev, mp)
+                self.image.g.mount_vfs(mopts, 'ufs', dev, mp)
             except RuntimeError as msg:
                 if mp in critical_mpoints:
                     self.out.warn('unable to mount %s. Reason: %s' % (mp, msg))
diff --git a/image_creator/os_type/linux.py b/image_creator/os_type/linux.py
index 47243368f1fadcf41178341813818dbf6c52878e..31b660f01fcedf275f336671073109bc707917ff 100644
--- a/image_creator/os_type/linux.py
+++ b/image_creator/os_type/linux.py
@@ -43,19 +43,15 @@ import time
 
 class Linux(Unix):
     """OS class for Linux"""
-    def __init__(self, rootdev, ghandler, output):
-        super(Linux, self).__init__(rootdev, ghandler, output)
+    def __init__(self, image, **kargs):
+        super(Linux, self).__init__(image, **kargs)
         self._uuid = dict()
         self._persistent = re.compile('/dev/[hsv]d[a-z][1-9]*')
 
-    @sysprep(enabled=False)
-    def remove_user_accounts(self, print_header=True):
+    @sysprep('Removing user accounts with id greater that 1000', enabled=False)
+    def remove_user_accounts(self):
         """Remove all user accounts with id greater than 1000"""
 
-        if print_header:
-            self.out.output("Removing all user accounts with id greater than "
-                            "1000")
-
         if 'USERS' not in self.meta:
             return
 
@@ -63,7 +59,7 @@ class Linux(Unix):
         passwd = []
         removed_users = {}
         metadata_users = self.meta['USERS'].split()
-        for line in self.g.cat('/etc/passwd').splitlines():
+        for line in self.image.g.cat('/etc/passwd').splitlines():
             fields = line.split(':')
             if int(fields[2]) > 1000:
                 removed_users[fields[0]] = fields
@@ -79,78 +75,71 @@ class Linux(Unix):
         if not len(self.meta['USERS']):
             del self.meta['USERS']
 
-        self.g.write('/etc/passwd', '\n'.join(passwd) + '\n')
+        self.image.g.write('/etc/passwd', '\n'.join(passwd) + '\n')
 
         # Remove the corresponding /etc/shadow entries
         shadow = []
-        for line in self.g.cat('/etc/shadow').splitlines():
+        for line in self.image.g.cat('/etc/shadow').splitlines():
             fields = line.split(':')
             if fields[0] not in removed_users:
                 shadow.append(':'.join(fields))
 
-        self.g.write('/etc/shadow', "\n".join(shadow) + '\n')
+        self.image.g.write('/etc/shadow', "\n".join(shadow) + '\n')
 
         # Remove the corresponding /etc/group entries
         group = []
-        for line in self.g.cat('/etc/group').splitlines():
+        for line in self.image.g.cat('/etc/group').splitlines():
             fields = line.split(':')
             # Remove groups tha have the same name as the removed users
             if fields[0] not in removed_users:
                 group.append(':'.join(fields))
 
-        self.g.write('/etc/group', '\n'.join(group) + '\n')
+        self.image.g.write('/etc/group', '\n'.join(group) + '\n')
 
         # Remove home directories
         for home in [field[5] for field in removed_users.values()]:
-            if self.g.is_dir(home) and home.startswith('/home/'):
-                self.g.rm_rf(home)
+            if self.image.g.is_dir(home) and home.startswith('/home/'):
+                self.image.g.rm_rf(home)
 
-    @sysprep()
-    def cleanup_passwords(self, print_header=True):
+    @sysprep('Cleaning up password & locking all user accounts')
+    def cleanup_passwords(self):
         """Remove all passwords and lock all user accounts"""
 
-        if print_header:
-            self.out.output("Cleaning up passwords & locking all user "
-                            "accounts")
-
         shadow = []
 
-        for line in self.g.cat('/etc/shadow').splitlines():
+        for line in self.image.g.cat('/etc/shadow').splitlines():
             fields = line.split(':')
             if fields[1] not in ('*', '!'):
                 fields[1] = '!'
 
             shadow.append(":".join(fields))
 
-        self.g.write('/etc/shadow', "\n".join(shadow) + '\n')
+        self.image.g.write('/etc/shadow', "\n".join(shadow) + '\n')
 
-    @sysprep()
-    def fix_acpid(self, print_header=True):
+    @sysprep('Fixing acpid powerdown action')
+    def fix_acpid(self):
         """Replace acpid powerdown action scripts to immediately shutdown the
         system without checking if a GUI is running.
         """
 
-        if print_header:
-            self.out.output('Fixing acpid powerdown action')
-
         powerbtn_action = '#!/bin/sh\n\nPATH=/sbin:/bin:/usr/bin\n' \
                           'shutdown -h now "Power button pressed"\n'
 
         events_dir = '/etc/acpi/events'
-        if not self.g.is_dir(events_dir):
+        if not self.image.g.is_dir(events_dir):
             self.out.warn("No acpid event directory found")
             return
 
         event_exp = re.compile('event=(.+)', re.I)
         action_exp = re.compile('action=(.+)', re.I)
-        for events_file in self.g.readdir(events_dir):
+        for events_file in self.image.g.readdir(events_dir):
             if events_file['ftyp'] != 'r':
                 continue
 
             fullpath = "%s/%s" % (events_dir, events_file['name'])
             event = ""
             action = ""
-            for line in self.g.cat(fullpath).splitlines():
+            for line in self.image.g.cat(fullpath).splitlines():
                 match = event_exp.match(line)
                 if match:
                     event = match.group(1)
@@ -162,14 +151,14 @@ class Linux(Unix):
 
             if event.strip() in ("button[ /]power", "button/power.*"):
                 if action:
-                    if not self.g.is_file(action):
+                    if not self.image.g.is_file(action):
                         self.out.warn("Acpid action file: %s does not exist" %
                                       action)
                         return
-                    self.g.copy_file_to_file(action,
-                                             "%s.orig.snf-image-creator-%d" %
-                                             (action, time.time()))
-                    self.g.write(action, powerbtn_action)
+                    self.image.g.copy_file_to_file(
+                        action, "%s.orig.snf-image-creator-%d" %
+                        (action, time.time()))
+                    self.image.g.write(action, powerbtn_action)
                     return
                 else:
                     self.out.warn("Acpid event file %s does not contain and "
@@ -185,33 +174,27 @@ class Linux(Unix):
 
         self.out.warn("No acpi power button event found!")
 
-    @sysprep()
-    def remove_persistent_net_rules(self, print_header=True):
+    @sysprep('Removing persistent network interface names')
+    def remove_persistent_net_rules(self):
         """Remove udev rules that will keep network interface names persistent
         after hardware changes and reboots. Those rules will be created again
         the next time the image runs.
         """
 
-        if print_header:
-            self.out.output('Removing persistent network interface names')
-
         rule_file = '/etc/udev/rules.d/70-persistent-net.rules'
-        if self.g.is_file(rule_file):
-            self.g.rm(rule_file)
+        if self.image.g.is_file(rule_file):
+            self.image.g.rm(rule_file)
 
-    @sysprep()
-    def remove_swap_entry(self, print_header=True):
+    @sysprep('Removing swap entry from fstab')
+    def remove_swap_entry(self):
         """Remove swap entry from /etc/fstab. If swap is the last partition
         then the partition will be removed when shrinking is performed. If the
         swap partition is not the last partition in the disk or if you are not
         going to shrink the image you should probably disable this.
         """
 
-        if print_header:
-            self.out.output('Removing swap entry from fstab')
-
         new_fstab = ""
-        fstab = self.g.cat('/etc/fstab')
+        fstab = self.image.g.cat('/etc/fstab')
         for line in fstab.splitlines():
 
             entry = line.split('#')[0].strip().split()
@@ -220,18 +203,14 @@ class Linux(Unix):
 
             new_fstab += "%s\n" % line
 
-        self.g.write('/etc/fstab', new_fstab)
+        self.image.g.write('/etc/fstab', new_fstab)
 
-    @sysprep()
-    def use_persistent_block_device_names(self, print_header=True):
+    @sysprep('Replacing fstab & grub non-persistent device references')
+    def use_persistent_block_device_names(self):
         """Scan fstab & grub configuration files and replace all non-persistent
         device references with UUIDs.
         """
 
-        if print_header:
-            self.out.output("Replacing fstab & grub non-persistent device "
-                            "references")
-
         # convert all devices in fstab to persistent
         persistent_root = self._persistent_fstab()
 
@@ -242,32 +221,33 @@ class Linux(Unix):
         """Replaces non-persistent device name occurencies with persistent
         ones in GRUB1 configuration files.
         """
-        if self.g.is_file('/boot/grub/menu.lst'):
+        if self.image.g.is_file('/boot/grub/menu.lst'):
             grub1 = '/boot/grub/menu.lst'
-        elif self.g.is_file('/etc/grub.conf'):
+        elif self.image.g.is_file('/etc/grub.conf'):
             grub1 = '/etc/grub.conf'
         else:
             return
 
-        self.g.aug_init('/', 0)
+        self.image.g.aug_init('/', 0)
         try:
-            roots = self.g.aug_match('/files%s/title[*]/kernel/root' % grub1)
+            roots = self.image.g.aug_match(
+                '/files%s/title[*]/kernel/root' % grub1)
             for root in roots:
-                dev = self.g.aug_get(root)
+                dev = self.image.g.aug_get(root)
                 if not self._is_persistent(dev):
                     # This is not always correct. Grub may contain root entries
                     # for other systems, but we only support 1 OS per hard
                     # disk, so this shouldn't harm.
-                    self.g.aug_set(root, new_root)
+                    self.image.g.aug_set(root, new_root)
         finally:
-            self.g.aug_save()
-            self.g.aug_close()
+            self.image.g.aug_save()
+            self.image.g.aug_close()
 
     def _persistent_fstab(self):
         """Replaces non-persistent device name occurencies in /etc/fstab with
         persistent ones.
         """
-        mpoints = self.g.mountpoints()
+        mpoints = self.image.g.mountpoints()
         if len(mpoints) == 0:
             pass  # TODO: error handling
 
@@ -275,7 +255,7 @@ class Linux(Unix):
 
         root_dev = None
         new_fstab = ""
-        fstab = self.g.cat('/etc/fstab')
+        fstab = self.image.g.cat('/etc/fstab')
         for line in fstab.splitlines():
 
             line, dev, mpoint = self._convert_fstab_line(line, device_dict)
@@ -284,7 +264,7 @@ class Linux(Unix):
             if mpoint == '/':
                 root_dev = dev
 
-        self.g.write('/etc/fstab', new_fstab)
+        self.image.g.write('/etc/fstab', new_fstab)
         if root_dev is None:
             pass  # TODO: error handling
 
@@ -331,9 +311,9 @@ class Linux(Unix):
     def _get_passworded_users(self):
         """Returns a list of non-locked user accounts"""
         users = []
-        regexp = re.compile('(\S+):((?:!\S+)|(?:[^!*]\S+)|):(?:\S*:){6}')
+        regexp = re.compile(r'(\S+):((?:!\S+)|(?:[^!*]\S+)|):(?:\S*:){6}')
 
-        for line in self.g.cat('/etc/shadow').splitlines():
+        for line in self.image.g.cat('/etc/shadow').splitlines():
             match = regexp.match(line)
             if not match:
                 continue
@@ -355,7 +335,7 @@ class Linux(Unix):
         if dev in self._uuid:
             return self._uuid[dev]
 
-        uuid = self.g.vfs_uuid(dev)
+        uuid = self.image.g.vfs_uuid(dev)
         assert len(uuid)
         self._uuid[dev] = uuid
         return uuid
diff --git a/image_creator/os_type/slackware.py b/image_creator/os_type/slackware.py
index e8f5a9ee7085d666f7e431accfc2c65fc7e46f15..91c4a37195c732c0ad34b5cc97d662ec6a7101f5 100644
--- a/image_creator/os_type/slackware.py
+++ b/image_creator/os_type/slackware.py
@@ -40,17 +40,14 @@ from image_creator.os_type.linux import Linux, sysprep
 
 class Slackware(Linux):
     """OS class for Slackware Linux"""
-    @sysprep()
-    def cleanup_log(self, print_header=True):
+    @sysprep("Emptying all files under /var/log")
+    def cleanup_log(self):
         """Empty all files under /var/log"""
 
-        if print_header:
-            self.out.output('Emptying all files under /var/log')
-
         # In slackware the metadata about installed packages are
         # stored in /var/log/packages. Clearing all /var/log files
         # will destroy the package management system.
-        self._foreach_file('/var/log', self.g.truncate, ftype='r',
+        self._foreach_file('/var/log', self.image.g.truncate, ftype='r',
                            exclude='/var/log/packages')
 
 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
diff --git a/image_creator/os_type/ubuntu.py b/image_creator/os_type/ubuntu.py
index b5706fcefc87ea6aa65df6ed101761103350216b..0647c6fab48c4555f23311d0df01b992e6dce0ee 100644
--- a/image_creator/os_type/ubuntu.py
+++ b/image_creator/os_type/ubuntu.py
@@ -40,14 +40,12 @@ from image_creator.os_type.linux import Linux
 
 class Ubuntu(Linux):
     """OS class for Ubuntu Linux variants"""
-    def __init__(self, rootdev, ghandler, output):
-        super(Ubuntu, self).__init__(rootdev, ghandler, output)
 
     def _do_collect_metadata(self):
         """Collect metadata about the OS"""
 
         super(Ubuntu, self)._do_collect_metadata()
-        apps = self.g.inspect_list_applications(self.root)
+        apps = self.image.g.inspect_list_applications(self.root)
         for app in apps:
             if app['app_name'] == 'kubuntu-desktop':
                 self.meta['OS'] = 'kubuntu'
diff --git a/image_creator/os_type/unix.py b/image_creator/os_type/unix.py
index 001c90d70e7530bbdefb2e0cee38df738b9aaf25..8779b76632e5ab9b0d4db06297c5bada9c57fb9d 100644
--- a/image_creator/os_type/unix.py
+++ b/image_creator/os_type/unix.py
@@ -35,8 +35,6 @@
 
 """This module hosts OS-specific code common to all Unix-like OSs."""
 
-import re
-
 from image_creator.os_type import OSBase, sysprep
 
 
@@ -55,7 +53,7 @@ class Unix(OSBase):
         """Return mountpoints in the correct order.
         / should be mounted before /boot or /usr, /usr befor /usr/bin ...
         """
-        mps = self.g.inspect_get_mountpoints(self.root)
+        mps = self.image.g.inspect_get_mountpoints(self.root)
 
         def compare(a, b):
             if len(a[0]) > len(b[0]):
@@ -77,7 +75,7 @@ class Unix(OSBase):
         mopts = 'ro' if readonly else 'rw'
         for mp, dev in self._mountpoints():
             try:
-                self.g.mount_options(mopts, dev, mp)
+                self.image.g.mount_options(mopts, dev, mp)
             except RuntimeError as msg:
                 if mp in critical_mpoints:
                     self.out.warn('unable to mount %s. Reason: %s' % (mp, msg))
@@ -87,69 +85,55 @@ class Unix(OSBase):
 
         return True
 
-    @sysprep()
-    def cleanup_cache(self, print_header=True):
+    @sysprep('Removing files under /var/cache')
+    def cleanup_cache(self):
         """Remove all regular files under /var/cache"""
 
-        if print_header:
-            self.out.output('Removing files under /var/cache')
-
-        self._foreach_file('/var/cache', self.g.rm, ftype='r')
+        self._foreach_file('/var/cache', self.image.g.rm, ftype='r')
 
-    @sysprep()
-    def cleanup_tmp(self, print_header=True):
+    @sysprep('Removing files under /tmp and /var/tmp')
+    def cleanup_tmp(self):
         """Remove all files under /tmp and /var/tmp"""
 
-        if print_header:
-            self.out.output('Removing files under /tmp and /var/tmp')
+        self._foreach_file('/tmp', self.image.g.rm_rf, maxdepth=1)
+        self._foreach_file('/var/tmp', self.image.g.rm_rf, maxdepth=1)
 
-        self._foreach_file('/tmp', self.g.rm_rf, maxdepth=1)
-        self._foreach_file('/var/tmp', self.g.rm_rf, maxdepth=1)
-
-    @sysprep()
-    def cleanup_log(self, print_header=True):
+    @sysprep('Emptying all files under /var/log')
+    def cleanup_log(self):
         """Empty all files under /var/log"""
 
-        if print_header:
-            self.out.output('Emptying all files under /var/log')
-
-        self._foreach_file('/var/log', self.g.truncate, ftype='r')
+        self._foreach_file('/var/log', self.image.g.truncate, ftype='r')
 
-    @sysprep(enabled=False)
-    def cleanup_mail(self, print_header=True):
+    @sysprep('Removing files under /var/mail & /var/spool/mail', enabled=False)
+    def cleanup_mail(self):
         """Remove all files under /var/mail and /var/spool/mail"""
 
-        if print_header:
-            self.out.output('Removing files under /var/mail & /var/spool/mail')
+        if self.image.g.is_dir('/var/spool/mail'):
+            self._foreach_file('/var/spool/mail', self.image.g.rm_rf,
+                               maxdepth=1)
 
-        if self.g.is_dir('/var/spool/mail'):
-            self._foreach_file('/var/spool/mail', self.g.rm_rf, maxdepth=1)
+        self._foreach_file('/var/mail', self.image.g.rm_rf, maxdepth=1)
 
-        self._foreach_file('/var/mail', self.g.rm_rf, maxdepth=1)
-
-    @sysprep()
-    def cleanup_userdata(self, print_header=True):
+    @sysprep('Removing sensitive user data')
+    def cleanup_userdata(self):
         """Delete sensitive userdata"""
 
         homedirs = ['/root']
-        if self.g.is_dir('/home/'):
+        if self.image.g.is_dir('/home/'):
             homedirs += self._ls('/home/')
 
-        if print_header:
-            self.out.output("Removing sensitive user data under %s" %
-                            " ".join(homedirs))
-
-        action = self.g.rm_rf
+        action = self.image.g.rm_rf
         if self._scrub_support:
-            action = self.g.scrub_file
+            action = self.image.g.scrub_file
         else:
             self.out.warn("Sensitive data won't be scrubbed (not supported)")
+
         for homedir in homedirs:
             for data in self.sensitive_userdata:
                 fname = "%s/%s" % (homedir, data)
-                if self.g.is_file(fname):
+                if self.image.g.is_file(fname):
                     action(fname)
-                elif self.g.is_dir(fname):
+                elif self.image.g.is_dir(fname):
                     self._foreach_file(fname, action, ftype='r')
 
 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
diff --git a/image_creator/os_type/windows.py b/image_creator/os_type/windows.py
index bf237c2b3f5ecbd7052d9151e416b4aec534c3de..96efdab53e2140e5c65d4c3d1e0c95e0aefa21ee 100644
--- a/image_creator/os_type/windows.py
+++ b/image_creator/os_type/windows.py
@@ -36,15 +36,608 @@
 """This module hosts OS-specific code common for the various Microsoft
 Windows OSs."""
 
-from image_creator.os_type import OSBase
+from image_creator.os_type import OSBase, sysprep, add_sysprep_param
+from image_creator.util import FatalError, get_kvm_binary
+from image_creator.winexe import WinEXE, WinexeTimeout
 
 import hivex
 import tempfile
 import os
+import signal
+import time
+import random
+import string
+import subprocess
+import struct
+
+# For more info see: http://technet.microsoft.com/en-us/library/jj612867.aspx
+KMS_CLIENT_SETUP_KEYS = {
+    "Windows 8 Professional": "NG4HW-VH26C-733KW-K6F98-J8CK4",
+    "Windows 8 Professional N": "XCVCF-2NXM9-723PB-MHCB7-2RYQQ",
+    "Windows 8 Enterprise": "32JNW-9KQ84-P47T8-D8GGY-CWCK7",
+    "Windows 8 Enterprise N": "JMNMF-RHW7P-DMY6X-RF3DR-X2BQT",
+    "Windows Server 2012 Core": "BN3D2-R7TKB-3YPBD-8DRP2-27GG4",
+    "Windows Server 2012 Core N": "8N2M2-HWPGY-7PGT9-HGDD8-GVGGY",
+    "Windows Server 2012 Core Single Language":
+    "2WN2H-YGCQR-KFX6K-CD6TF-84YXQ",
+    "Windows Server 2012 Core Country Specific":
+    "4K36P-JN4VD-GDC6V-KDT89-DYFKP",
+    "Windows Server 2012 Server Standard": "XC9B7-NBPP2-83J2H-RHMBY-92BT4",
+    "Windows Server 2012 Standard Core": "XC9B7-NBPP2-83J2H-RHMBY-92BT4",
+    "Windows Server 2012 MultiPoint Standard": "HM7DN-YVMH3-46JC3-XYTG7-CYQJJ",
+    "Windows Server 2012 MultiPoint Premium": "XNH6W-2V9GX-RGJ4K-Y8X6F-QGJ2G",
+    "Windows Server 2012 Datacenter": "48HP8-DN98B-MYWDG-T2DCC-8W83P",
+    "Windows Server 2012 Datacenter Core": "48HP8-DN98B-MYWDG-T2DCC-8W83P",
+    "Windows 7 Professional": "FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4",
+    "Windows 7 Professional N": "MRPKT-YTG23-K7D7T-X2JMM-QY7MG",
+    "Windows 7 Professional E": "W82YF-2Q76Y-63HXB-FGJG9-GF7QX",
+    "Windows 7 Enterprise": "33PXH-7Y6KF-2VJC9-XBBR8-HVTHH",
+    "Windows 7 Enterprise N": "YDRBP-3D83W-TY26F-D46B2-XCKRJ",
+    "Windows 7 Enterprise E": "C29WB-22CC8-VJ326-GHFJW-H9DH4",
+    "Windows Server 2008 R2 Web": "6TPJF-RBVHG-WBW2R-86QPH-6RTM4",
+    "Windows Server 2008 R2 HPC edition": "TT8MH-CG224-D3D7Q-498W2-9QCTX",
+    "Windows Server 2008 R2 Standard": "YC6KT-GKW9T-YTKYR-T4X34-R7VHC",
+    "Windows Server 2008 R2 Enterprise": "489J6-VHDMP-X63PK-3K798-CPX3Y",
+    "Windows Server 2008 R2 Datacenter": "74YFP-3QFB3-KQT8W-PMXWJ-7M648",
+    "Windows Server 2008 R2 for Itanium-based Systems":
+    "GT63C-RJFQ3-4GMB6-BRFB9-CB83V",
+    "Windows Vista Business": "YFKBB-PQJJV-G996G-VWGXY-2V3X8",
+    "Windows Vista Business N": "HMBQG-8H2RH-C77VX-27R82-VMQBT",
+    "Windows Vista Enterprise": "VKK3X-68KWM-X2YGT-QR4M6-4BWMV",
+    "Windows Vista Enterprise N": "VTC42-BM838-43QHV-84HX6-XJXKV",
+    "Windows Web Server 2008": "WYR28-R7TFJ-3X2YQ-YCY4H-M249D",
+    "Windows Server 2008 Standard": "TM24T-X9RMF-VWXK6-X8JC9-BFGM2",
+    "Windows Server 2008 Standard without Hyper-V":
+    "W7VD6-7JFBR-RX26B-YKQ3Y-6FFFJ",
+    "Windows Server 2008 Enterprise":
+    "YQGMW-MPWTJ-34KDK-48M3W-X4Q6V",
+    "Windows Server 2008 Enterprise without Hyper-V":
+    "39BXF-X8Q23-P2WWT-38T2F-G3FPG",
+    "Windows Server 2008 HPC": "RCTX3-KWVHP-BR6TB-RB6DM-6X7HP",
+    "Windows Server 2008 Datacenter": "7M67G-PC374-GR742-YH8V4-TCBY3",
+    "Windows Server 2008 Datacenter without Hyper-V":
+    "22XQ2-VRXRG-P8D42-K34TD-G3QQC",
+    "Windows Server 2008 for Itanium-Based Systems":
+    "4DWFP-JF3DJ-B7DTH-78FJB-PDRHK"}
+
+_POSINT = lambda x: type(x) == int and x >= 0
 
 
 class Windows(OSBase):
     """OS class for Windows"""
+    @add_sysprep_param(
+        'shutdown_timeout', int, 120, "Shutdown Timeout (seconds)", _POSINT)
+    @add_sysprep_param(
+        'boot_timeout', int, 300, "Boot Timeout (seconds)", _POSINT)
+    @add_sysprep_param(
+        'connection_retries', int, 5, "Connection Retries", _POSINT)
+    @add_sysprep_param('password', str, None, 'Image Administrator Password')
+    def __init__(self, image, **kargs):
+        super(Windows, self).__init__(image, **kargs)
+
+        # 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.last_drive = None
+        self.system_drive = None
+
+        for drive, part in self.image.g.inspect_get_drive_mappings(self.root):
+            if part == "%s%d" % (device, self.last_part_num):
+                self.last_drive = drive
+            if part == self.root:
+                self.system_drive = drive
+
+        assert self.system_drive
+
+        self.product_name = self.image.g.inspect_get_product_name(self.root)
+        self.syspreped = False
+
+    @sysprep('Disabling IPv6 privacy extensions')
+    def disable_ipv6_privacy_extensions(self):
+        """Disable IPv6 privacy extensions"""
+
+        self._guest_exec('netsh interface ipv6 set global '
+                         'randomizeidentifiers=disabled store=persistent')
+
+    @sysprep('Disabling Teredo interface')
+    def disable_teredo(self):
+        """Disable Teredo interface"""
+
+        self._guest_exec('netsh interface teredo set state disabled')
+
+    @sysprep('Disabling ISATAP Adapters')
+    def disable_isatap(self):
+        """Disable ISATAP Adapters"""
+
+        self._guest_exec('netsh interface isa set state disabled')
+
+    @sysprep('Enabling ping responses')
+    def enable_pings(self):
+        """Enable ping responses"""
+
+        self._guest_exec('netsh firewall set icmpsetting 8')
+
+    @sysprep('Disabling hibernation support')
+    def disable_hibernation(self):
+        """Disable hibernation support and remove the hibernation file"""
+
+        self._guest_exec(r'powercfg.exe /hibernate off')
+
+    @sysprep('Setting the system clock to UTC')
+    def utc(self):
+        """Set the hardware clock to UTC"""
+
+        path = r'HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
+        self._guest_exec(
+            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._guest_exec(
+            r"cmd /q /c for /f %l in ('wevtutil el') do wevtutil cl %l")
+
+    @sysprep('Executing Sysprep on the image (may take more that 10 minutes)')
+    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._guest_exec(r'C:\Windows\system32\sysprep\sysprep '
+                         r'/quiet /generalize /oobe /shutdown')
+        self.syspreped = True
+
+    @sysprep('Converting the image into a KMS client', enabled=False)
+    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._guest_exec(
+            r"cscript \Windows\system32\slmgr.vbs /ipk %s" % setup_key)
+
+    @sysprep('Shrinking the last filesystem')
+    def shrink(self):
+        """Shrink the last filesystem. Make sure the filesystem is defragged"""
+
+        # Query for the maximum number of reclaimable bytes
+        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 QUERYMAX >> %SCRIPT% & ' +
+            r'ECHO EXIT >> %SCRIPT% & ' +
+            r'DISKPART /S %SCRIPT% & ' +
+            r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
+            r'DEL /Q %SCRIPT%"')
+
+        stdout, stderr, rc = self._guest_exec(cmd)
+
+        querymax = None
+        for line in stdout.splitlines():
+            # diskpart will return something like this:
+            #
+            #   The maximum number of reclaimable bytes is: xxxx MB
+            #
+            if line.find('reclaimable') >= 0:
+                querymax = line.split(':')[1].split()[0].strip()
+                assert querymax.isdigit(), \
+                    "Number of reclaimable bytes not a number"
+
+        if querymax is None:
+            FatalError("Error in shrinking! "
+                       "Couldn't find the max number of reclaimable bytes!")
+
+        querymax = int(querymax)
+        # 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.
+        # I'll give 100MB extra space just to be sure
+        querymax -= 100
+
+        if querymax < 0:
+            self.out.warn("Not enought available space to shrink the image!")
+            return
+
+        self.out.output("\tReclaiming %dMB ..." % querymax)
+
+        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 +
+            'ECHO SHRINK DESIRED=%d >> %%SCRIPT%% & ' % querymax +
+            r'ECHO EXIT >> %SCRIPT% & ' +
+            r'DISKPART /S %SCRIPT% & ' +
+            r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
+            r'DEL /Q %SCRIPT%"')
+
+        stdout, stderr, rc = self._guest_exec(cmd, False)
+
+        if rc != 0:
+            FatalError("Shrinking failed. Please make sure the media is "
+                       "defraged with a command like this: "
+                       "`Defrag.exe /U /X /W'")
+        for line in stdout.splitlines():
+            if line.find('shrunk') >= 0:
+                self.out.output(line)
+
+    def do_sysprep(self):
+        """Prepare system for image creation."""
+
+        if getattr(self, 'syspreped', False):
+            raise FatalError("Image is already syspreped!")
+
+        txt = "System preparation parameter: `%s' is needed but missing!"
+        for name, param in self.needed_sysprep_params.items():
+            if name not in self.sysprep_params:
+                raise FatalError(txt % name)
+
+        self.mount(readonly=False)
+        try:
+            disabled_uac = self._update_uac_remote_setting(1)
+            token = self._enable_os_monitor()
+
+            # disable the firewalls
+            firewall_states = self._update_firewalls(0, 0, 0)
+
+            # Delete the pagefile. It will be recreated when the system boots
+            systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
+            try:
+                pagefile = "%s/pagefile.sys" % systemroot
+                self.image.g.rm_rf(self.image.g.case_sensitive_path(pagefile))
+            except RuntimeError:
+                pass
+
+        finally:
+            self.umount()
+
+        self.image.disable_guestfs()
+
+        vm = None
+        monitor = None
+        try:
+            self.out.output("Starting windows VM ...", False)
+            monitorfd, monitor = tempfile.mkstemp()
+            os.close(monitorfd)
+            vm = _VM(self.image.device, monitor, self.sysprep_params)
+            self.out.success("started (console on vnc display: %d)." %
+                             vm.display)
+
+            self.out.output("Waiting for OS to boot ...", False)
+            self._wait_vm_boot(vm, monitor, token)
+            self.out.success('done')
+
+            self.out.output("Checking connectivity to the VM ...", False)
+            self._check_connectivity()
+            self.out.success('done')
+
+            self.out.output("Disabling automatic logon ...", False)
+            self._disable_autologon()
+            self.out.success('done')
+
+            self.out.output('Preparing system for image creation:')
+
+            tasks = self.list_syspreps()
+            enabled = [task for task in tasks if task.enabled]
+            size = len(enabled)
+
+            # Make sure shrink runs in the end, before ms sysprep
+            enabled = [task for task in enabled if
+                       self.sysprep_info(task).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 = [task for task in enabled if
+                       self.sysprep_info(task).name != 'microsoft-sysprep']
+
+            ms_sysprep_enabled = False
+            if len(enabled) != size:
+                enabled.append(self.microsoft_sysprep)
+                ms_sysprep_enabled = True
+
+            cnt = 0
+            for task in enabled:
+                cnt += 1
+                self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False)
+                task()
+                setattr(task.im_func, 'executed', True)
+
+            self.out.output("Sending shut down command ...", False)
+            if not ms_sysprep_enabled:
+                self._shutdown()
+            self.out.success("done")
+
+            self.out.output("Waiting for windows to shut down ...", False)
+            vm.wait(self.sysprep_params['shutdown_timeout'])
+            self.out.success("done")
+        finally:
+            if monitor is not None:
+                os.unlink(monitor)
+
+            try:
+                if vm is not None:
+                    self.out.output("Destroying windows VM ...", False)
+                    vm.destroy()
+                    self.out.success("done")
+            finally:
+                self.image.enable_guestfs()
+
+                self.mount(readonly=False)
+                try:
+                    if disabled_uac:
+                        self._update_uac_remote_setting(0)
+
+                    self._update_firewalls(*firewall_states)
+                finally:
+                    self.umount()
+
+    def _shutdown(self):
+        """Shuts down the windows VM"""
+        self._guest_exec(r'shutdown /s /t 5')
+
+    def _wait_vm_boot(self, vm, fname, msg):
+        """Wait until a message appears on a file or the vm process dies"""
+
+        for _ in range(self.sysprep_params['boot_timeout']):
+            time.sleep(1)
+            with open(fname) as f:
+                for line in f:
+                    if line.startswith(msg):
+                        return True
+            if not vm.isalive():
+                raise FatalError("Windows VM died unexpectedly!")
+
+        raise FatalError("Windows VM booting timed out!")
+
+    def _disable_autologon(self):
+        """Disable automatic logon on the windows image"""
+
+        winlogon = \
+            r'"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"'
+
+        self._guest_exec('REG DELETE %s /v DefaultUserName /f' % winlogon)
+        self._guest_exec('REG DELETE %s /v DefaultPassword /f' % winlogon)
+        self._guest_exec('REG DELETE %s /v AutoAdminLogon /f' % winlogon)
+
+    def _registry_file_path(self, regfile):
+        """Retrieves the case sensitive path to a registry file"""
+
+        systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
+        path = "%s/system32/config/%s" % (systemroot, regfile)
+        try:
+            path = self.image.g.case_sensitive_path(path)
+        except RuntimeError as error:
+            raise FatalError("Unable to retrieve registry file: %s. Reason: %s"
+                             % (regfile, str(error)))
+        return path
+
+    def _enable_os_monitor(self):
+        """Add a script in the registry that will send a random string to the
+        first serial port when the windows image finishes booting.
+        """
+
+        token = "".join(random.choice(string.ascii_letters) for x in range(16))
+
+        path = self._registry_file_path('SOFTWARE')
+        softwarefd, software = tempfile.mkstemp()
+        try:
+            os.close(softwarefd)
+            self.image.g.download(path, software)
+
+            h = hivex.Hivex(software, write=True)
+
+            # Enable automatic logon.
+            # This is needed because we need to execute a script that we add in
+            # the RunOnce registry entry and those programs 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.
+
+            winlogon = h.root()
+            for child in ('Microsoft', 'Windows NT', 'CurrentVersion',
+                          'Winlogon'):
+                winlogon = h.node_get_child(winlogon, child)
+
+            h.node_set_value(
+                winlogon,
+                {'key': 'DefaultUserName', 't': 1,
+                 'value': "Administrator".encode('utf-16le')})
+            h.node_set_value(
+                winlogon,
+                {'key': 'DefaultPassword', 't': 1,
+                 'value':  self.sysprep_params['password'].encode('utf-16le')})
+            h.node_set_value(
+                winlogon,
+                {'key': 'AutoAdminLogon', 't': 1,
+                 'value': "1".encode('utf-16le')})
+
+            key = h.root()
+            for child in ('Microsoft', 'Windows', 'CurrentVersion'):
+                key = h.node_get_child(key, child)
+
+            runonce = h.node_get_child(key, "RunOnce")
+            if runonce is None:
+                runonce = h.node_add_child(key, "RunOnce")
+
+            value = (
+                r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe '
+                r'-ExecutionPolicy RemoteSigned '
+                r'"&{$port=new-Object System.IO.Ports.SerialPort COM1,9600,'
+                r'None,8,one;$port.open();$port.WriteLine(\"' + token + r'\");'
+                r'$port.Close()}"').encode('utf-16le')
+
+            h.node_set_value(runonce,
+                             {'key': "BootMonitor", 't': 1, 'value': value})
+
+            value = (
+                r'REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion'
+                r'\policies\system /v LocalAccountTokenFilterPolicy'
+                r' /t REG_DWORD /d 1 /f').encode('utf-16le')
+
+            h.node_set_value(runonce,
+                             {'key': "UpdateRegistry", 't': 1, 'value': value})
+
+            h.commit(None)
+
+            self.image.g.upload(software, path)
+        finally:
+            os.unlink(software)
+
+        return token
+
+    def _update_firewalls(self, domain, public, standard):
+        """Enables or disables the firewall for the Domain, the Public and the
+        Standard profile. Returns a triplete with the old values.
+
+        1 will enable a firewall and 0 will disable it
+        """
+
+        if domain not in (0, 1):
+            raise ValueError("Valid values for domain parameter are 0 and 1")
+
+        if public not in (0, 1):
+            raise ValueError("Valid values for public parameter are 0 and 1")
+
+        if standard not in (0, 1):
+            raise ValueError("Valid values for standard parameter are 0 and 1")
+
+        path = self._registry_file_path("SYSTEM")
+        systemfd, system = tempfile.mkstemp()
+        try:
+            os.close(systemfd)
+            self.image.g.download(path, system)
+
+            h = hivex.Hivex(system, write=True)
+
+            select = h.node_get_child(h.root(), 'Select')
+            current_value = h.node_get_value(select, 'Current')
+
+            # expecting a little endian dword
+            assert h.value_type(current_value)[1] == 4
+            current = "%03d" % h.value_dword(current_value)
+
+            firewall_policy = h.root()
+            for child in ('ControlSet%s' % current, 'services', 'SharedAccess',
+                          'Parameters', 'FirewallPolicy'):
+                firewall_policy = h.node_get_child(firewall_policy, child)
+
+            old_values = []
+            new_values = [domain, public, standard]
+            for profile in ('Domain', 'Public', 'Standard'):
+                node = h.node_get_child(firewall_policy, '%sProfile' % profile)
+
+                old_value = h.node_get_value(node, 'EnableFirewall')
+
+                # expecting a little endian dword
+                assert h.value_type(old_value)[1] == 4
+                old_values.append(h.value_dword(old_value))
+
+                h.node_set_value(
+                    node, {'key': 'EnableFirewall', 't': 4L,
+                           'value': struct.pack("<I", new_values.pop(0))})
+
+            h.commit(None)
+            self.image.g.upload(system, path)
+
+        finally:
+            os.unlink(system)
+
+        return old_values
+
+    def _update_uac_remote_setting(self, value):
+        """Updates the registry key value:
+        [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
+        \System]"LocalAccountTokenFilterPolicy"
+
+        value = 1 will disable the UAC remote restrictions
+        value = 0 will enable the UAC remote restrictions
+
+        For more info see here: http://support.microsoft.com/kb/951016
+
+        Returns:
+            True if the key is changed
+            False if the key is unchanged
+        """
+
+        if value not in (0, 1):
+            raise ValueError("Valid values for value parameter are 0 and 1")
+
+        path = self._registry_file_path('SOFTWARE')
+        softwarefd, software = tempfile.mkstemp()
+        try:
+            os.close(softwarefd)
+            self.image.g.download(path, software)
+
+            h = hivex.Hivex(software, write=True)
+
+            key = h.root()
+            for child in ('Microsoft', 'Windows', 'CurrentVersion', 'Policies',
+                          'System'):
+                key = h.node_get_child(key, child)
+
+            policy = None
+            for val in h.node_values(key):
+                if h.value_key(val) == "LocalAccountTokenFilterPolicy":
+                    policy = val
+
+            if policy is not None:
+                dword = h.value_dword(policy)
+                if dword == value:
+                    return False
+            elif value == 0:
+                return False
+
+            new_value = {'key': "LocalAccountTokenFilterPolicy", 't': 4L,
+                         'value': struct.pack("<I", value)}
+
+            h.node_set_value(key, new_value)
+            h.commit(None)
+
+            self.image.g.upload(software, path)
+
+        finally:
+            os.unlink(software)
+
+        return True
 
     def _do_collect_metadata(self):
         """Collect metadata about the OS"""
@@ -55,25 +648,187 @@ class Windows(OSBase):
         """Returns a list of users found in the images"""
         samfd, sam = tempfile.mkstemp()
         try:
-            systemroot = self.g.inspect_get_windows_systemroot(self.root)
-            path = "%s/system32/config/sam" % systemroot
-            path = self.g.case_sensitive_path(path)
-            self.g.download(path, sam)
+            os.close(samfd)
+            self.image.g.download(self._registry_file_path('SAM'), sam)
 
             h = hivex.Hivex(sam)
 
-            key = h.root()
+            # Navigate to /SAM/Domains/Account/Users
+            users_node = h.root()
+            for child in ('SAM', 'Domains', 'Account', 'Users'):
+                users_node = h.node_get_child(users_node, child)
 
             # Navigate to /SAM/Domains/Account/Users/Names
-            for child in ('SAM', 'Domains', 'Account', 'Users', 'Names'):
-                key = h.node_get_child(key, child)
+            names_node = h.node_get_child(users_node, 'Names')
+
+            # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\%RID%
+            # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\Names\%Username%
+            #
+            # The RID (relative identifier) of each user is stored as the type!
+            # (not the value) of the default key of the node under Names whose
+            # name is the user's username. Under the RID node, there in a F
+            # value that contains information about this user account.
+            #
+            # See sam.h of the chntpw project on how to translate the F value
+            # of an account in the registry. Bytes 56 & 57 are the account type
+            # and status flags. The first bit is the 'account disabled' bit
+            disabled = lambda f: int(f[56].encode('hex'), 16) & 0x01
 
-            users = [h.node_name(x) for x in h.node_children(key)]
+            users = []
+            for user_node in h.node_children(names_node):
+                username = h.node_name(user_node)
+                rid = h.value_type(h.node_get_value(user_node, ""))[0]
+                # if RID is 500 (=0x1f4), the corresponding node name under
+                # Users is '000001F4'
+                key = ("%8.x" % rid).replace(' ', '0').upper()
+                rid_node = h.node_get_child(users_node, key)
+                f_value = h.value_value(h.node_get_value(rid_node, 'F'))[1]
+
+                if disabled(f_value):
+                    self.out.warn("Found disabled `%s' account!" % username)
+                    continue
+
+                users.append(username)
 
         finally:
             os.unlink(sam)
 
         # Filter out the guest account
-        return filter(lambda x: x != "Guest", users)
+        return users
+
+    def _check_connectivity(self):
+        """Check if winexe works on the Windows VM"""
+
+        retries = self.sysprep_params['connection_retries']
+        # If the connection_retries parameter is set to 0 disable the
+        # connectivity check
+        if retries == 0:
+            return True
+
+        passwd = self.sysprep_params['password']
+        winexe = WinEXE('Administrator', passwd, 'localhost')
+        winexe.uninstall().debug(9)
+
+        for i in range(retries):
+            (stdout, stderr, rc) = winexe.run('cmd /C')
+            if rc == 0:
+                return True
+            log = tempfile.NamedTemporaryFile(delete=False)
+            try:
+                log.file.write(stdout)
+            finally:
+                log.close()
+            self.out.output("failed! See: `%s' for the full output" % log.name)
+            if i < retries - 1:
+                self.out.output("retrying ...", False)
+
+        raise FatalError("Connection to the Windows VM failed after %d retries"
+                         % retries)
+
+    def _guest_exec(self, command, fatal=True):
+        """Execute a command on a windows VM"""
+
+        passwd = self.sysprep_params['password']
+
+        winexe = WinEXE('Administrator', passwd, 'localhost')
+        winexe.runas('Administrator', passwd).uninstall()
+
+        try:
+            (stdout, stderr, rc) = winexe.run(command)
+        except WinexeTimeout:
+            FatalError("Command: `%s' timeout out." % command)
+
+        if rc != 0 and fatal:
+            reason = stderr if len(stderr) else stdout
+            self.out.output("Command: `%s' failed (rc=%d). Reason: %s" %
+                            (command, rc, reason))
+            raise FatalError("Command: `%s' failed (rc=%d). Reason: %s" %
+                             (command, rc, reason))
+
+        return (stdout, stderr, rc)
+
+
+class _VM(object):
+    """Windows Virtual Machine"""
+    def __init__(self, disk, serial, params):
+        """Create _VM instance
+
+            disk: VM's hard disk
+            serial: File to save the output of the serial port
+        """
+
+        self.disk = disk
+        self.serial = serial
+        self.params = params
+
+        def random_mac():
+            """creates a random mac address"""
+            mac = [0x00, 0x16, 0x3e,
+                   random.randint(0x00, 0x7f),
+                   random.randint(0x00, 0xff),
+                   random.randint(0x00, 0xff)]
+
+            return ':'.join(['%02x' % x for x in mac])
+
+        # Use ganeti's VNC port range for a random vnc port
+        self.display = random.randint(11000, 14999) - 5900
+
+        kvm, needed_args = get_kvm_binary()
+
+        if kvm is None:
+            FatalError("Can't find the kvm binary")
+
+        args = [kvm]
+        args.extend(needed_args)
+
+        args.extend([
+            '-smp', '1', '-m', '1024', '-drive',
+            'file=%s,format=raw,cache=unsafe,if=virtio' % self.disk,
+            '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
+            '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(),
+            '-vnc', ':%d' % self.display, '-serial', 'file:%s' % self.serial,
+            '-monitor', 'stdio'])
+
+        self.process = subprocess.Popen(args, stdin=subprocess.PIPE,
+                                        stdout=subprocess.PIPE)
+
+    def isalive(self):
+        """Check if the VM is still alive"""
+        return self.process.poll() is None
+
+    def destroy(self):
+        """Destroy the VM"""
+
+        if not self.isalive():
+            return
+
+        def handler(signum, frame):
+            self.process.terminate()
+            time.sleep(1)
+            if self.isalive():
+                self.process.kill()
+            self.process.wait()
+            raise FatalError("VM destroy timed-out")
+
+        signal.signal(signal.SIGALRM, handler)
+
+        signal.alarm(self.params['shutdown_timeout'])
+        self.process.communicate(input="system_powerdown\n")
+        signal.alarm(0)
+
+    def wait(self, timeout=0):
+        """Wait for the VM to terminate"""
+
+        def handler(signum, frame):
+            self.destroy()
+            raise FatalError("VM wait timed-out.")
+
+        signal.signal(signal.SIGALRM, handler)
+
+        signal.alarm(timeout)
+        stdout, stderr = self.process.communicate()
+        signal.alarm(0)
+
+        return (stdout, stderr, self.process.poll())
 
 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
diff --git a/image_creator/util.py b/image_creator/util.py
index fb0d0fa0b319c3c52c25a262de23eee1695f499e..4e1b7062098e678dc99e5c4c202c8baec7992fc6 100644
--- a/image_creator/util.py
+++ b/image_creator/util.py
@@ -41,6 +41,7 @@ import sh
 import hashlib
 import time
 import os
+import re
 
 
 class FatalError(Exception):
@@ -63,6 +64,26 @@ def get_command(command):
         return find_sbin_command(command, e)
 
 
+def get_kvm_binary():
+    """Returns the path to the kvm binary and some extra arguments if needed"""
+
+    uname = get_command('uname')
+    which = get_command('which')
+
+    machine = str(uname('-m')).strip()
+    if re.match('i[3-6]86', machine):
+        machine = 'i386'
+
+    binary = which('qemu-system-%s' % machine)
+
+    needed_args = "--enable-kvm",
+
+    if binary is None:
+        return which('kvm'), tuple()
+
+    return binary, needed_args
+
+
 def try_fail_repeat(command, *args):
     """Execute a command multiple times until it succeeds"""
     times = (0.1, 0.5, 1, 2)
diff --git a/image_creator/version.py b/image_creator/version.py
index 6e462d9a80d0f0982d5e3371ffa3b31c4a6961f5..dc31a71b9c8051ec474da51c286fd24c7d1fb4eb 100644
--- a/image_creator/version.py
+++ b/image_creator/version.py
@@ -1,7 +1,8 @@
-__version__ = "0.4.4"
-__version_info__ = ['0', '4', '4']
+
+__version__ = "0.4.4next"
 __version_vcs_info__ = {
-    'branch': 'hotfix-0.4.4',
-    'revid': 'ce66ae3',
-    'revno': 322}
-__version_user_info__ = "skalkoto@darkstar.admin.grnet.gr"
+    'branch': 'develop',
+    'revid': 'c5effe0',
+    'revno': 370}
+__version_user_email__ = "skalkoto@grnet.gr"
+__version_user_name__ = "Nikos Skalkotos"
diff --git a/image_creator/winexe.py b/image_creator/winexe.py
new file mode 100644
index 0000000000000000000000000000000000000000..ebac40931b0671cc08fea8518d58411b13faeb1c
--- /dev/null
+++ b/image_creator/winexe.py
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2013 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+"""This module provides an interface for the WinEXE utility"""
+
+import subprocess
+import time
+import signal
+
+from image_creator.util import FatalError
+
+
+class WinexeTimeout(FatalError):
+    """Raised when a WinExE command times-out"""
+    pass
+
+
+class WinEXE:
+    """Wrapper class for the winexe command"""
+
+    def __init__(self, username, password, hostname, program='winexe'):
+        self._host = hostname
+        self._user = username
+        self._pass = password
+        self._prog = program
+
+        # -U USERNAME[%PASSWORD]
+        user = '%s%s' % (self._user, '%%%s' % self._pass if self._pass else "")
+        self._opts = ['-U', user]
+
+    def reset(self):
+        """Reset all winexe options"""
+
+        # -U USERNAME[%PASSWORD]
+        user = '%s%s' % (self._user, '%%%s' % self._pass if self._pass else "")
+        self._opts = ['-U', user]
+
+    def runas(self, username, password):
+        """Run command as this user"""
+        self._opts.append('--runas=%s%%%s' % (username, password))
+        return self
+
+    def system(self):
+        """Use SYSTEM account"""
+        self._opts.append('--system')
+        return self
+
+    def uninstall(self):
+        """Uninstall winexe service after remote execution"""
+        self._opts.append('--uninstall')
+        return self
+
+    def reinstall(self):
+        """Reinstall winexe service before remote execution"""
+        self._opts.append('--reinstall')
+        return self
+
+    def debug(self, level):
+        """Set debug level"""
+        self._opts.append('--debuglevel=%d' % level)
+        return self
+
+    def debug_stderr(self):
+        """Send debug output to STDERR"""
+        self._opts.append('--debug-stderr')
+
+    def run(self, command, timeout=0):
+        """Run a command on a remote windows system"""
+
+        args = [self._prog] + self._opts + ["//%s" % self._host] + [command]
+        run = subprocess.Popen(args, stdout=subprocess.PIPE,
+                               stderr=subprocess.PIPE)
+
+        def handler(signum, frame):
+            run.terminate()
+            time.sleep(1)
+            run.poll()
+            if run.returncode is None:
+                run.kill()
+            run.wait()
+            raise WinexeTimeout("Command: `%s' timed-out" % " ".join(args))
+
+        signal.signal(signal.SIGALRM, handler)
+        signal.alarm(timeout)
+        stdout, stderr = run.communicate()
+        rc = run.poll()
+        signal.alarm(0)
+
+        return (stdout, stderr, rc)
+
+# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
diff --git a/version b/version
index 6f2743d65dc06508954334e88edb660bf8efea20..15c226a41b0f7714998ef238e8de5d7a3326dc74 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-0.4.4
+0.4.4next