FELIX-1766: Refactor and enable AdminServiceMBean

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@828802 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/admin/core/NOTICE b/karaf/admin/core/NOTICE
new file mode 100644
index 0000000..2f69501
--- /dev/null
+++ b/karaf/admin/core/NOTICE
@@ -0,0 +1,21 @@
+Apache Felix Karaf

+Copyright 2009 The Apache Software Foundation

+

+

+I. Included Software

+

+This product includes software developed at

+The Apache Software Foundation (http://www.apache.org/).

+Licensed under the Apache License 2.0.

+

+

+II. Used Software

+

+This product uses software developed at

+The OSGi Alliance (http://www.osgi.org/).

+Copyright (c) OSGi Alliance (2000, 2009).

+Licensed under the Apache License 2.0.

+

+

+III. License Summary

+- Apache License 2.0

diff --git a/karaf/admin/core/pom.xml b/karaf/admin/core/pom.xml
new file mode 100644
index 0000000..86bff82
--- /dev/null
+++ b/karaf/admin/core/pom.xml
@@ -0,0 +1,227 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.felix.karaf.admin</groupId>
+        <artifactId>admin</artifactId>
+        <version>1.1.0-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.apache.felix.karaf.admin</groupId>
+    <artifactId>org.apache.felix.karaf.admin.core</artifactId>
+    <packaging>bundle</packaging>
+    <version>1.1.0-SNAPSHOT</version>
+    <name>Apache Felix Karaf :: Admin Core</name>
+
+    <properties>
+        <appendedResourcesDirectory>${basedir}/../../etc/appended-resources</appendedResourcesDirectory>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.bundlerepository</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix.karaf.shell</groupId>
+            <artifactId>org.apache.felix.karaf.shell.console</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix.karaf.shell</groupId>
+            <artifactId>org.apache.felix.karaf.shell.obr</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.osgi</groupId>
+            <artifactId>spring-osgi-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.servicemix.bundles</groupId>
+            <artifactId>org.apache.servicemix.bundles.junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-jdk14</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>${pom.basedir}/src/main/resources</directory>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+            </resource>
+        </resources>
+        <filters>
+            <filter>target/filter.txt</filter>
+        </filters>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-antrun-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>create-prop</id>
+                        <phase>initialize</phase>
+                        <configuration>
+                            <tasks>
+                                <taskdef resource="net/sf/antcontrib/antcontrib.properties" classpathref="maven.plugin.classpath" />
+                                <property name="ant.regexp.regexpimpl" value="org.apache.tools.ant.util.regexp.Jdk14RegexpRegexp" />
+                                <property name="mv" value="${project.version}" />
+                                <echo message="Maven version: ${mv}" />
+                                <propertyregex property="ov.p1" input="${mv}" regexp="(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:[^a-zA-Z0-9](.*))?" replace="\1" defaultValue="0" />
+                                <propertyregex property="ov.p2" input="${mv}" regexp="(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:[^a-zA-Z0-9](.*))?" replace=".\2" defaultValue=".0" />
+                                <propertyregex property="ov.p3" input="${mv}" regexp="(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:[^a-zA-Z0-9](.*))?" replace=".\3" defaultValue=".0" />
+                                <propertyregex property="ov.p4" input="${mv}" regexp="(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:[^a-zA-Z0-9](.*))?" replace=".\4" defaultValue="" />
+                                <propertyregex property="ov.p1a" input="${ov.p1}" regexp="(.+)" replace="\1" defaultValue="0" />
+                                <propertyregex property="ov.p2a" input="${ov.p2}" regexp="(\..+)" replace="\1" defaultValue=".0" />
+                                <propertyregex property="ov.p3a" input="${ov.p3}" regexp="(\..+)" replace="\1" defaultValue=".0" />
+                                <propertyregex property="ov.p4a" input="${ov.p4}" regexp="(\..+)" replace="\1" defaultValue="" />
+                                <property name="ov" value="${ov.p1a}${ov.p2a}${ov.p3a}${ov.p4a}" />
+                                <echo message="OSGi version: ${ov}" />
+                                <mkdir dir="target" />
+                                <echo message="karaf.osgi.version = ${ov}" file="target/filter.txt" />
+                            </tasks>
+                        </configuration>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-config-properties</id>
+                        <!-- here the phase you need -->
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${basedir}/target/classes/org/apache/felix/karaf/admin/etc</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>../../assembly/src/main/distribution/text/etc/</directory>
+                                    <includes>
+                                        <include>*.properties</include>
+                                        <include>*.cfg</include>
+                                    </includes>
+                                    <excludes>
+                                        <exclude>org.apache.felix.karaf.shell.cfg</exclude>
+                                        <exclude>org.ops4j.pax.url.mvn.cfg</exclude>
+                                        <exclude>system.properties</exclude>
+                                    </excludes>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>copy-filtered</id>
+                        <!-- here the phase you need -->
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${basedir}/target/classes/org/apache/felix/karaf/admin/etc</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>../../assembly/src/main/filtered-resources/etc</directory>
+                                    <filtering>true</filtering>
+                                    <includes>
+                                        <include>*.properties</include>
+                                        <include>*.cfg</include>
+                                    </includes>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
+                        <Export-Package>
+                            org.apache.felix.karaf.admin;version=${pom.version},
+                            org.apache.felix.karaf.jpm;version=${pom.version}
+                        </Export-Package>
+                        <Import-Package>
+                            !org.apache.felix.karaf.admin,
+                            !org.apache.felix.karaf.jpm,
+                            javax.management,
+                            javax.management.loading,
+                            org.osgi.service.command,
+                            org.apache.felix.gogo.commands,
+                            org.apache.felix.karaf.shell.console,
+                            *
+                        </Import-Package>
+                        <Private-Package>
+                            org.apache.felix.karaf.admin.internal,
+                            org.apache.felix.karaf.jpm.impl
+                        </Private-Package>
+                        <_versionpolicy>${bnd.version.policy}</_versionpolicy>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                  <excludes>
+                    <!-- this is not a unit test but an application used for testing -->
+                    <exclude>**/MainTest.java</exclude>
+                  </excludes>
+                </configuration>
+           </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/AdminService.java b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/AdminService.java
new file mode 100644
index 0000000..e8d1cb5
--- /dev/null
+++ b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/AdminService.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.admin;
+
+public interface AdminService {
+
+    Instance createInstance(String name, InstanceSettings settings) throws Exception;
+
+    Instance[] getInstances();
+
+    Instance getInstance(String name);    
+}
diff --git a/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/Instance.java b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/Instance.java
new file mode 100644
index 0000000..4f267c7
--- /dev/null
+++ b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/Instance.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.admin;
+
+public interface Instance {
+
+    String STOPPED = "Stopped";
+    String STARTING = "Starting";
+    String STARTED = "Started";
+    String ERROR = "Error";
+
+    String getName();
+
+    String getLocation();
+
+    int getPid();
+
+    int getPort();
+
+    void changePort(int port) throws Exception;
+
+    void start(String javaOpts) throws Exception;
+
+    void stop() throws Exception;
+
+    void destroy() throws Exception;
+
+    String getState() throws Exception;
+
+}
diff --git a/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/InstanceSettings.java b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/InstanceSettings.java
new file mode 100644
index 0000000..832e24f
--- /dev/null
+++ b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/InstanceSettings.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.admin;
+
+import java.util.List;
+
+public class InstanceSettings {
+    private final int port;
+    private final String location;
+    private final List<String> featureURLs;
+    private final List<String> features;
+
+    public InstanceSettings(int port, String location, List<String> featureURLs, List<String> features) {
+        this.port = port;
+        this.location = location;
+        this.featureURLs = featureURLs;
+        this.features = features;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public List<String> getFeatureURLs() {
+        return featureURLs;
+    }
+
+    public List<String> getFeatures() {
+        return features;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (!(o instanceof InstanceSettings)) {
+            return false;
+        }
+        InstanceSettings is = (InstanceSettings) o;
+        return is.port == port &&
+               (location == null ? is.location == null : location.equals(is.location)) &&
+               (featureURLs == null ? is.featureURLs == null : featureURLs.equals(is.featureURLs)) &&
+               (features == null ? is.features == null : features.equals(is.features));
+    }
+
+    @Override
+    public int hashCode() {
+        int rc = 17;
+        rc = 37 * port;
+        if (location != null) {
+            rc = 37 * location.hashCode();
+        }
+        if (featureURLs != null) {
+            rc = 37 * featureURLs.hashCode();
+        }
+        if (features != null) {
+            rc = 37 * features.hashCode();
+        }
+        return rc;
+    }
+    
+    
+}
diff --git a/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/AdminServiceImpl.java b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/AdminServiceImpl.java
new file mode 100644
index 0000000..df8d155
--- /dev/null
+++ b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/AdminServiceImpl.java
@@ -0,0 +1,347 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.admin.internal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Scanner;
+
+import org.apache.felix.karaf.admin.AdminService;
+import org.apache.felix.karaf.admin.Instance;
+import org.apache.felix.karaf.admin.InstanceSettings;
+import org.fusesource.jansi.Ansi;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AdminServiceImpl implements AdminService {
+    public static final String STORAGE_FILE = "instance.properties";
+    private static final String FEATURES_CFG = "etc/org.apache.felix.karaf.features.cfg";
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AdminServiceImpl.class);
+
+    private Map<String, Instance> instances = new HashMap<String, Instance>();
+
+    private int defaultPortStart = 8101;
+
+    private File storageLocation;
+
+    public File getStorageLocation() {
+        return storageLocation;
+    }
+
+    public void setStorageLocation(File storage) {
+        this.storageLocation = storage;
+    }
+
+    private Properties loadStorage(File location) throws IOException {
+        InputStream is = null;
+        try {
+            is = new FileInputStream(location);
+            Properties props = new Properties();
+            props.load(is);
+            return props;
+        } finally {
+            if (is != null) {
+                is.close();
+            }
+        }
+    }
+
+    private void saveStorage(Properties props, File location, String comment) throws IOException {
+        OutputStream os = null;
+        try {
+            os = new FileOutputStream(location);
+            props.store(os, comment);
+        } finally {
+            if (os != null) {
+                os.close();
+            }
+        }
+    }
+
+    public synchronized void init() throws Exception {
+        try {
+            File storageFile = new File(storageLocation, STORAGE_FILE);
+            if (!storageFile.isFile()) {
+                if (storageFile.exists()) {
+                    LOGGER.error("Instances storage location should be a file: " + storageFile);
+                }
+                return;
+            }
+            Properties storage = loadStorage(storageFile);
+            int count = Integer.parseInt(storage.getProperty("count", "0"));
+            defaultPortStart = Integer.parseInt(storage.getProperty("port", Integer.toString(defaultPortStart)));
+            Map<String, Instance> newInstances = new HashMap<String, Instance>();
+            for (int i = 0; i < count; i++) {
+                String name = storage.getProperty("item." + i + ".name", null);
+                String loc = storage.getProperty("item." + i + ".loc", null);
+                int pid = Integer.parseInt(storage.getProperty("item." + i + ".pid", "0"));
+                if (name != null) {
+                    InstanceImpl instance = new InstanceImpl(this, name, loc);
+                    if (pid > 0) {
+                        try {
+                            instance.attach(pid);
+                        } catch (IOException e) {
+                            // Ignore
+                        }
+                    }
+                    newInstances.put(name, instance);
+                }
+            }
+            instances = newInstances;
+        } catch (Exception e) {
+            LOGGER.warn("Unable to reload Karaf instance list", e);
+        }
+    }
+
+    public synchronized Instance createInstance(String name, InstanceSettings settings) throws Exception {
+        if (instances.get(name) != null) {
+            throw new IllegalArgumentException("Instance '" + name + "' already exists");
+        }
+        String loc = settings.getLocation() != null ? settings.getLocation() : name;
+        File karafBase = new File(loc);
+        if (!karafBase.isAbsolute()) {
+            karafBase = new File(storageLocation, loc);
+        }
+        int sshPort = settings.getPort();
+        if (sshPort <= 0) {
+            sshPort = ++defaultPortStart;
+        }
+        println(Ansi.ansi().a("Creating new instance on port ").a(sshPort).a(" at: ").a(Ansi.Attribute.INTENSITY_BOLD).a(karafBase).a(Ansi.Attribute.RESET).toString());
+
+        mkdir(karafBase, "bin");
+        mkdir(karafBase, "etc");
+        mkdir(karafBase, "system");
+        mkdir(karafBase, "deploy");
+        mkdir(karafBase, "data");
+
+        copyResourceToDir(karafBase, "etc/config.properties", true);
+        copyResourceToDir(karafBase, "etc/java.util.logging.properties", true);
+        copyResourceToDir(karafBase, "etc/org.apache.felix.karaf.log.cfg", true);
+        copyResourceToDir(karafBase, FEATURES_CFG, true);
+        copyResourceToDir(karafBase, "etc/org.apache.felix.karaf.management.cfg", true);
+        copyResourceToDir(karafBase, "etc/org.ops4j.pax.logging.cfg", true);
+        copyResourceToDir(karafBase, "etc/org.ops4j.pax.url.mvn.cfg", true);
+        copyResourceToDir(karafBase, "etc/startup.properties", true);
+        copyResourceToDir(karafBase, "etc/users.properties", true);
+
+        HashMap<String, String> props = new HashMap<String, String>();
+        props.put("${karaf.name}", name);
+        props.put("${karaf.home}", System.getProperty("karaf.home"));
+        props.put("${karaf.base}", karafBase.getPath());
+        props.put("${karaf.sshPort}", Integer.toString(sshPort));
+        copyFilteredResourceToDir(karafBase, "etc/system.properties", props);
+        copyFilteredResourceToDir(karafBase, "etc/org.apache.felix.karaf.shell.cfg", props);
+        if( System.getProperty("os.name").startsWith("Win") ) {
+            copyFilteredResourceToDir(karafBase, "bin/karaf.bat", props);
+            copyFilteredResourceToDir(karafBase, "bin/start.bat", props);
+            copyFilteredResourceToDir(karafBase, "bin/stop.bat", props);
+        } else {
+            copyFilteredResourceToDir(karafBase, "bin/karaf", props);
+            copyFilteredResourceToDir(karafBase, "bin/start", props);
+            copyFilteredResourceToDir(karafBase, "bin/stop", props);
+            chmod(new File(karafBase, "bin/karaf"), "a+x");
+            chmod(new File(karafBase, "bin/start"), "a+x");
+            chmod(new File(karafBase, "bin/stop"), "a+x");
+        }
+        
+        handleFeatures(new File(karafBase, FEATURES_CFG), settings);
+        
+        Instance instance = new InstanceImpl(this, name, karafBase.toString());
+        instances.put(name, instance);
+        saveState();
+        return instance;
+    }
+
+    void handleFeatures(File featuresCfg, InstanceSettings settings) throws IOException {
+        Properties p = loadStorage(featuresCfg);
+
+        appendToPropList(p, "featuresBoot", settings.getFeatures());
+        appendToPropList(p, "featuresRepositories", settings.getFeatureURLs());
+        saveStorage(p, featuresCfg, "Features Configuration");
+    }
+
+    private void appendToPropList(Properties p, String key, List<String> elements) {
+        if (elements == null) {
+            return;
+        }
+        StringBuilder sb = new StringBuilder(p.getProperty(key).trim());
+        for (String f : elements) {
+            if (sb.length() > 0) {
+                sb.append(',');
+            }
+            sb.append(f);
+        }
+        p.setProperty(key, sb.toString());
+    }
+    
+    public synchronized Instance[] getInstances() {
+        return instances.values().toArray(new Instance[0]);
+    }
+
+    public synchronized Instance getInstance(String name) {
+        return instances.get(name);
+    }
+
+    synchronized void forget(String name) {
+        instances.remove(name);
+    }
+
+    synchronized void saveState() throws IOException {
+        Properties storage = new Properties();
+        Instance[] data = getInstances();
+        storage.setProperty("port", Integer.toString(defaultPortStart));
+        storage.setProperty("count", Integer.toString(data.length));
+        for (int i = 0; i < data.length; i++) {
+            storage.setProperty("item." + i + ".name", data[i].getName());
+            storage.setProperty("item." + i + ".loc", data[i].getLocation());
+            storage.setProperty("item." + i + ".pid", Integer.toString(data[i].getPid()));
+        }
+        saveStorage(storage, new File(storageLocation, STORAGE_FILE), "Admin Service storage");
+    }
+
+    private void copyResourceToDir(File target, String resource, boolean text) throws Exception {
+        File outFile = new File(target, resource);
+        if( !outFile.exists() ) {
+            println(Ansi.ansi().a("Creating file: ").a(Ansi.Attribute.INTENSITY_BOLD).a(outFile.getPath()).a(Ansi.Attribute.RESET).toString());
+            InputStream is = getClass().getClassLoader().getResourceAsStream("org/apache/felix/karaf/admin/" + resource);
+            try {
+                if( text ) {
+                    // Read it line at a time so that we can use the platform line ending when we write it out.
+                    PrintStream out = new PrintStream(new FileOutputStream(outFile));
+                    try {
+                        Scanner scanner = new Scanner(is);
+                        while (scanner.hasNextLine() ) {
+                            String line = scanner.nextLine();
+                            out.println(line);
+                        }
+                    } finally {
+                        safeClose(out);
+                    }
+                } else {
+                    // Binary so just write it out the way it came in.
+                    FileOutputStream out = new FileOutputStream(new File(target, resource));
+                    try {
+                        int c=0;
+                        while((c=is.read())>=0) {
+                            out.write(c);
+                        }
+                    } finally {
+                        safeClose(out);
+                    }
+                }
+            } finally {
+                safeClose(is);
+            }
+        }
+    }
+
+    private void println(String st) {
+        System.out.println(st);
+    }
+
+    private void copyFilteredResourceToDir(File target, String resource, HashMap<String, String> props) throws Exception {
+        File outFile = new File(target, resource);
+        if( !outFile.exists() ) {
+            println(Ansi.ansi().a("Creating file: ").a(Ansi.Attribute.INTENSITY_BOLD).a(outFile.getPath()).a(Ansi.Attribute.RESET).toString());
+            InputStream is = getClass().getClassLoader().getResourceAsStream("org/apache/felix/karaf/admin/" + resource);
+            try {
+                // Read it line at a time so that we can use the platform line ending when we write it out.
+                PrintStream out = new PrintStream(new FileOutputStream(outFile));
+                try {
+                    Scanner scanner = new Scanner(is);
+                    while (scanner.hasNextLine() ) {
+                        String line = scanner.nextLine();
+                        line = filter(line, props);
+                        out.println(line);
+                    }
+                } finally {
+                    safeClose(out);
+                }
+            } finally {
+                safeClose(is);
+            }
+        }
+    }
+
+    private void safeClose(InputStream is) throws IOException {
+        if (is == null) {
+            return;
+        }
+        try {
+            is.close();
+        } catch (Throwable ignore) {
+        }
+    }
+
+    private void safeClose(OutputStream is) throws IOException {
+        if (is == null) {
+            return;
+        }
+        try {
+            is.close();
+        } catch (Throwable ignore) {
+        }
+    }
+
+    private String filter(String line, HashMap<String, String> props) {
+        for (Map.Entry<String, String> i : props.entrySet()) {
+            int p1 = line.indexOf(i.getKey());
+            if( p1 >= 0 ) {
+                String l1 = line.substring(0, p1);
+                String l2 = line.substring(p1+i.getKey().length());
+                line = l1+i.getValue()+l2;
+            }
+        }
+        return line;
+    }
+
+    private void mkdir(File karafBase, String path) {
+        File file = new File(karafBase, path);
+        if( !file.exists() ) {
+            println(Ansi.ansi().a("Creating dir:  ").a(Ansi.Attribute.INTENSITY_BOLD).a(file.getPath()).a(Ansi.Attribute.RESET).toString());
+            file.mkdirs();
+        }
+    }
+
+    private int chmod(File serviceFile, String mode) throws Exception {
+        ProcessBuilder builder = new ProcessBuilder();
+        builder.command("chmod", mode, serviceFile.getCanonicalPath());
+        Process p = builder.start();
+
+        // gnodet: Fix SMX4KNL-46: cpu goes to 100% after running the 'admin create' command
+        // Not sure exactly what happens, but commenting the process io redirection seems
+        // to work around the problem.
+        //
+        //PumpStreamHandler handler = new PumpStreamHandler(io.inputStream, io.outputStream, io.errorStream);
+        //handler.attach(p);
+        //handler.start();
+        int status = p.waitFor();
+        //handler.stop();
+        return status;
+    }
+
+}
diff --git a/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/InstanceImpl.java b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/InstanceImpl.java
new file mode 100644
index 0000000..40a76a5
--- /dev/null
+++ b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/InstanceImpl.java
@@ -0,0 +1,234 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.admin.internal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.util.Properties;
+
+import org.apache.felix.karaf.admin.Instance;
+import org.apache.felix.karaf.jpm.Process;
+import org.apache.felix.karaf.jpm.ProcessBuilderFactory;
+import org.apache.felix.karaf.jpm.impl.ScriptUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class InstanceImpl implements Instance {
+
+    private static final Logger LOG = LoggerFactory.getLogger(InstanceImpl.class);
+
+    private AdminServiceImpl service;
+    private String name;
+    private String location;
+    private Process process;
+
+    public InstanceImpl(AdminServiceImpl service, String name, String location) {
+        this.service = service;
+        this.name = name;
+        this.location = location;
+    }
+
+    public void attach(int pid) throws IOException {
+        checkProcess();
+        if (this.process != null) {
+            throw new IllegalStateException("Instance already started");
+        }
+        this.process = ProcessBuilderFactory.newInstance().newBuilder().attach(pid);
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public boolean exists() {
+        return new File(location).isDirectory();
+    }
+
+    public int getPid() {
+        checkProcess();
+        return this.process != null ? this.process.getPid() : 0;
+    }
+
+    public int getPort() {
+        InputStream is = null;
+        try {
+            File f = new File(location, "etc/org.apache.felix.karaf.shell.cfg");
+            is = new FileInputStream(f);
+            Properties props = new Properties();
+            props.load(is);
+            String loc = props.getProperty("sshPort");
+            return Integer.parseInt(loc);
+        } catch (Exception e) {
+            return 0;
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
+    public void changePort(int port) throws Exception {
+        checkProcess();
+        if (this.process != null) {
+            throw new IllegalStateException("Instance not stopped");
+        }
+        Properties props = new Properties();
+        File f = new File(location, "etc/org.apache.felix.karaf.shell.cfg");
+        InputStream is = new FileInputStream(f);
+        try {
+            props.load(is);
+        } finally {
+            is.close();
+        }
+        props.setProperty("sshPort", Integer.toString(port));
+        OutputStream os = new FileOutputStream(f);
+        try {
+            props.store(os, null);
+        } finally {
+            os.close();
+        }
+    }
+
+    public synchronized void start(String javaOpts) throws Exception {
+        checkProcess();
+        if (this.process != null) {
+            throw new IllegalStateException("Instance already started");
+        }
+        if (javaOpts == null) {
+            javaOpts = "-server -Xmx512M -Dcom.sun.management.jmxremote";
+        }
+        File libDir = new File(System.getProperty("karaf.home"), "lib");
+        File[] jars = libDir.listFiles(new FilenameFilter() {
+            public boolean accept(File dir, String name) {
+                return name.endsWith(".jar");
+            }
+        });
+        StringBuilder classpath = new StringBuilder();
+        for (File jar : jars) {
+            if (classpath.length() > 0) {
+                classpath.append(System.getProperty("path.separator"));
+            }
+            classpath.append(jar.getCanonicalPath());
+        }
+        String command = new File(System.getProperty("java.home"), ScriptUtils.isWindows() ? "bin\\java.exe" : "bin/java").getCanonicalPath()
+                + " " + javaOpts
+                + " -Djava.util.logging.config.file=\"" + new File(location, "etc/java.util.logging.properties").getCanonicalPath() + "\""
+                + " -Dkaraf.home=\"" + System.getProperty("karaf.home") + "\""
+                + " -Dkaraf.base=\"" + new File(location).getCanonicalPath() + "\""
+                + " -Dkaraf.startLocalConsole=false"
+                + " -Dkaraf.startRemoteShell=true"
+                + " -classpath " + classpath.toString()
+                + " org.apache.felix.karaf.main.Bootstrap";
+        LOG.debug("Starting instance " + name + " with command: " + command);
+        this.process = ProcessBuilderFactory.newInstance().newBuilder()
+                        .directory(new File(location))
+                        .command(command)
+                        .start();
+        this.service.saveState();
+    }
+
+    public synchronized void stop() throws Exception {
+        checkProcess();
+        if (this.process == null) {
+            throw new IllegalStateException("Instance not started");
+        }
+        this.process.destroy();
+    }
+
+    public synchronized void destroy() throws Exception {
+        checkProcess();
+        if (this.process != null) {
+            throw new IllegalStateException("Instance not stopped");
+        }
+        deleteFile(new File(location));
+        this.service.forget(name);
+        this.service.saveState();
+    }
+
+
+    public synchronized String getState() {
+        int port = getPort();
+        if (!exists() || port <= 0) {
+            return ERROR;
+        }
+        checkProcess();
+        if (this.process == null) {
+            return STOPPED;
+        } else {
+            try {
+                Socket s = new Socket("localhost", port);
+                s.close();
+                return STARTED;
+            } catch (Exception e) {
+                // ignore
+            }
+            return STARTING;
+        }
+    }
+
+    protected void checkProcess() {
+        if (this.process != null) {
+            try {
+                if (!this.process.isRunning()) {
+                    this.process = null;
+                }
+            } catch (IOException e) {
+            }
+        }
+    }
+
+    protected static boolean deleteFile(File fileToDelete) {
+        if (fileToDelete == null || !fileToDelete.exists()) {
+            return true;
+        }
+        boolean result = true;
+        if (fileToDelete.isDirectory()) {
+            File[] files = fileToDelete.listFiles();
+            if (files == null) {
+                result = false;
+            } else {
+                for (int i = 0; i < files.length; i++) {
+                    File file = files[i];
+                    if (file.getName().equals(".") || file.getName().equals("..")) {
+                        continue;
+                    }
+                    if (file.isDirectory()) {
+                        result &= deleteFile(file);
+                    } else {
+                        result &= file.delete();
+                    }
+                }
+            }
+        }
+        result &= fileToDelete.delete();
+        return result;
+    }
+}
diff --git a/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/Process.java b/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/Process.java
new file mode 100644
index 0000000..0644f50
--- /dev/null
+++ b/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/Process.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.jpm;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * Interface representing a process
+ */
+public interface Process extends Serializable {
+
+    /**
+     * Retrieves the PID of the process
+     * @return the pid
+     */
+    int getPid();
+
+    /**
+     * Check if this process is still running
+     * @return <code>true</code> if the process is running
+     * @throws IOException if an error occurs
+     */
+    boolean isRunning() throws IOException;
+
+    /**
+     * Destroy the process.
+     *
+     * @throws IOException
+     */
+    void destroy() throws IOException;
+
+}
diff --git a/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/ProcessBuilder.java b/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/ProcessBuilder.java
new file mode 100644
index 0000000..a81cf36
--- /dev/null
+++ b/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/ProcessBuilder.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.jpm;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.*;
+
+/**
+ * Interface used to create new processes.
+ */
+public interface ProcessBuilder {
+
+    /**
+     * Specified the current directory to run the command from
+     *
+     * @param dir the directory to run the command from
+     * @return the ProcessBuilder instance
+     */
+    ProcessBuilder directory(File dir);
+
+    /**
+     * Set the command to execute
+     *
+     * @param command the command to execute
+     * @return the ProcessBuilder instance
+     */
+    ProcessBuilder command(String command);
+
+    /**
+     * Create and start the process
+     *
+     * @return the process that has been started
+     * @throws IOException if the process can not be created
+     */
+    org.apache.felix.karaf.jpm.Process start() throws IOException;
+
+    /**
+     * Attach to an existing process
+     *
+     * @return the process that has been attached
+     * @throws IOException if the process can not be attached to
+     */
+    org.apache.felix.karaf.jpm.Process attach(int pid) throws IOException;
+
+}
diff --git a/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/ProcessBuilderFactory.java b/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/ProcessBuilderFactory.java
new file mode 100644
index 0000000..6c4980c
--- /dev/null
+++ b/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/ProcessBuilderFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.jpm;
+
+import java.lang.*;
+
+import org.apache.felix.karaf.jpm.impl.ProcessBuilderFactoryImpl;
+
+/**
+ * Factory for process builders.
+ */
+public abstract class ProcessBuilderFactory {
+
+    public static ProcessBuilderFactory newInstance() {
+        return new ProcessBuilderFactoryImpl();
+    }
+
+    public abstract ProcessBuilder newBuilder();
+
+}
diff --git a/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/impl/ProcessBuilderFactoryImpl.java b/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/impl/ProcessBuilderFactoryImpl.java
new file mode 100644
index 0000000..23ee364
--- /dev/null
+++ b/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/impl/ProcessBuilderFactoryImpl.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.jpm.impl;
+
+import org.apache.felix.karaf.jpm.ProcessBuilder;
+import org.apache.felix.karaf.jpm.ProcessBuilderFactory;
+
+public class ProcessBuilderFactoryImpl extends ProcessBuilderFactory {
+
+    public ProcessBuilder newBuilder() {
+        return new ProcessBuilderImpl();
+    }
+}
diff --git a/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/impl/ProcessBuilderImpl.java b/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/impl/ProcessBuilderImpl.java
new file mode 100644
index 0000000..c6b622e
--- /dev/null
+++ b/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/impl/ProcessBuilderImpl.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.jpm.impl;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.felix.karaf.jpm.Process;
+import org.apache.felix.karaf.jpm.ProcessBuilder;
+
+
+public class ProcessBuilderImpl implements ProcessBuilder {
+
+    private File dir;
+    private String command;
+
+    public ProcessBuilder directory(File dir) {
+        this.dir = dir;
+        return this;
+    }
+
+    public ProcessBuilder command(String command) {
+        this.command = command;
+        return this;
+    }
+
+    public Process start() throws IOException {
+        return ProcessImpl.create(dir, command);
+    }
+
+    public Process attach(int pid) throws IOException {
+        return ProcessImpl.attach(pid);
+    }
+}
diff --git a/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/impl/ProcessImpl.java b/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/impl/ProcessImpl.java
new file mode 100644
index 0000000..27dec49
--- /dev/null
+++ b/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/impl/ProcessImpl.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.jpm.impl;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.felix.karaf.jpm.Process;
+
+public class ProcessImpl implements Process {
+
+    private int pid;
+    //private File input;
+    //private File output;
+    //private File error;
+
+    public ProcessImpl(int pid/*, File input, File output, File error*/) {
+        this.pid = pid;
+        //this.input = input;
+        //this.output = output;
+        //this.error = error;
+    }
+
+    public int getPid() {
+        return pid;
+    }
+
+    public boolean isRunning() throws IOException {
+        if (ScriptUtils.isWindows()) {
+            Map<String, String> props = new HashMap<String, String>();
+            props.put("${pid}", Integer.toString(pid));
+            int ret = ScriptUtils.execute("running", props);
+            return ret == 0;
+        } else {
+            try {
+                java.lang.Process process = new java.lang.ProcessBuilder("ps", "-p", Integer.toString(pid)).start();
+                BufferedReader r = new BufferedReader(new InputStreamReader(process.getInputStream()));
+                r.readLine(); // skip headers
+                String s = r.readLine();
+                boolean running = s != null && s.length() > 0;
+                int ret = process.waitFor();
+                return running;
+            } catch (InterruptedException e) {
+                throw new InterruptedIOException();
+            }
+        }
+    }
+
+    public void destroy() throws IOException {
+        int ret;
+        if (ScriptUtils.isWindows()) {
+            Map<String, String> props = new HashMap<String, String>();
+            props.put("${pid}", Integer.toString(pid));
+            ret = ScriptUtils.execute("destroy", props);
+        } else {
+            ret = ScriptUtils.executeProcess(new java.lang.ProcessBuilder("kill", "-9", Integer.toString(pid)));
+        }
+        if (ret != 0) {
+            throw new IOException("Unable to destroy proces, it may be already terminated");
+        }
+    }
+
+    /*
+    public OutputStream getInputStream() throws FileNotFoundException {
+        return new FileOutputStream(input);
+    }
+
+    public InputStream getOutputStream() throws FileNotFoundException {
+        return new FileInputStream(output);
+    }
+
+    public InputStream getErrorStream() throws FileNotFoundException {
+        return new FileInputStream(error);
+    }
+    */
+
+    public int waitFor() throws InterruptedException {
+        return 0;
+    }
+
+    public int exitValue() {
+        return 0;
+    }
+
+    public static Process create(File dir, String command) throws IOException {
+        //File input = File.createTempFile("jpm.", ".input");
+        //File output = File.createTempFile("jpm.", ".output");
+        //File error = File.createTempFile("jpm.", ".error");
+        File pidFile = File.createTempFile("jpm.", ".pid");
+        try {
+            Map<String, String> props = new HashMap<String, String>();
+            //props.put("${in.file}", input.getCanonicalPath());
+            //props.put("${out.file}", output.getCanonicalPath());
+            //props.put("${err.file}", error.getCanonicalPath());
+            props.put("${pid.file}", pidFile.getCanonicalPath());
+            props.put("${dir}", dir != null ? dir.getCanonicalPath() : "");
+            if (ScriptUtils.isWindows()) {
+                command = command.replaceAll("\"", "\"\"");
+            }
+            props.put("${command}", command);
+            int ret = ScriptUtils.execute("start", props);
+            if (ret != 0) {
+                throw new IOException("Unable to create process (error code: " + ret + ")");
+            }
+            int pid = readPid(pidFile);
+            return new ProcessImpl(pid/*, input, output, error*/);
+        } finally {
+            pidFile.delete();
+        }
+    }
+
+    public static Process attach(int pid) throws IOException {
+        return new ProcessImpl(pid);
+    }
+
+    private static int readPid(File pidFile) throws IOException {
+        InputStream is = new FileInputStream(pidFile);
+        try {
+            BufferedReader r = new BufferedReader(new InputStreamReader(is));
+            String pidString = r.readLine();
+            return Integer.valueOf(pidString);
+        } finally {
+            try {
+                is.close();
+            } catch (IOException e) {}
+        }
+    }
+
+}
diff --git a/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/impl/ScriptUtils.java b/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/impl/ScriptUtils.java
new file mode 100644
index 0000000..0f29087
--- /dev/null
+++ b/karaf/admin/core/src/main/java/org/apache/felix/karaf/jpm/impl/ScriptUtils.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.jpm.impl;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Map;
+import java.util.Scanner;
+
+public class ScriptUtils {
+
+    public static int execute(String name, Map<String, String> props) throws IOException {
+        File script = File.createTempFile("jpm.", ".script");
+        try {
+            if (isWindows()) {
+                String res = "windows/" + name + ".vbs";
+                ScriptUtils.copyFilteredResource(res, script, props);
+                return executeProcess(new java.lang.ProcessBuilder("cscript",
+                                                                   "/NOLOGO",
+                                                                   "//E:vbs",
+                                                                   script.getCanonicalPath()));
+            } else {
+                String res = "unix/" + name + ".sh";
+                ScriptUtils.copyFilteredResource(res, script, props);
+                return executeProcess(new java.lang.ProcessBuilder("/bin/sh",
+                                                                   script.getCanonicalPath()));
+            }
+        } finally {
+            script.delete();
+        }
+    }
+
+    public static int executeProcess(java.lang.ProcessBuilder builder) throws IOException {
+        try {
+            java.lang.Process process = builder.start();
+            return process.waitFor();
+        } catch (InterruptedException e) {
+            throw new InterruptedIOException();
+        }
+    }
+
+    public static void copyFilteredResource(String resource, File outFile, Map<String, String> props) throws IOException {
+        InputStream is = null;
+        try {
+            is = ScriptUtils.class.getResourceAsStream(resource);
+            // Read it line at a time so that we can use the platform line ending when we write it out.
+            PrintStream out = new PrintStream(new FileOutputStream(outFile));
+            try {
+                Scanner scanner = new Scanner(is);
+                while (scanner.hasNextLine() ) {
+                    String line = scanner.nextLine();
+                    line = filter(line, props);
+                    out.println(line);
+                }
+            } finally {
+                safeClose(out);
+            }
+        } finally {
+            safeClose(is);
+        }
+    }
+
+    private static void safeClose(InputStream is) throws IOException {
+        if (is == null) {
+            return;
+        }
+        try {
+            is.close();
+        } catch (Throwable ignore) {
+        }
+    }
+
+    private static void safeClose(OutputStream is) throws IOException {
+        if (is == null) {
+            return;
+        }
+        try {
+            is.close();
+        } catch (Throwable ignore) {
+        }
+    }
+
+    private static String filter(String line, Map<String, String> props) {
+        for (Map.Entry<String, String> i : props.entrySet()) {
+            int p1 = line.indexOf(i.getKey());
+            if( p1 >= 0 ) {
+                String l1 = line.substring(0, p1);
+                String l2 = line.substring(p1+i.getKey().length());
+                line = l1+i.getValue()+l2;
+            }
+        }
+        return line;
+    }
+
+    private static final boolean windows;
+
+    static {
+        windows = System.getProperty("os.name").toLowerCase().indexOf("windows") != -1;
+    }
+
+    public static boolean isWindows() {
+        return windows;
+    }
+
+}
diff --git a/karaf/admin/core/src/main/resources/OSGI-INF/blueprint/admin-core.xml b/karaf/admin/core/src/main/resources/OSGI-INF/blueprint/admin-core.xml
new file mode 100644
index 0000000..f68a106
--- /dev/null
+++ b/karaf/admin/core/src/main/resources/OSGI-INF/blueprint/admin-core.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+           xmlns:ext="http://geronimo.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0"
+           default-activation="lazy">
+
+    <bean id="adminService" class="org.apache.felix.karaf.admin.internal.AdminServiceImpl" init-method="init">
+        <property name="storageLocation" value="${karaf.home}/instances" />
+    </bean>
+
+    <service ref="adminService" interface="org.apache.felix.karaf.admin.AdminService" />
+
+    <!-- Allow the use of system properties -->
+    <ext:property-placeholder />
+
+</blueprint>
diff --git a/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/karaf b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/karaf
new file mode 100644
index 0000000..7a97f92
--- /dev/null
+++ b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/karaf
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You under the Apache License, Version 2.0
+#    (the "License"); you may not use this file except in compliance with
+#    the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+# $Id: karaf 979 2005-11-30 22:50:55Z bsnyder $
+#
+
+KARAF_HOME=${karaf.home}
+KARAF_BASE=${karaf.base}
+
+export KARAF_BASE
+exec ${KARAF_HOME}/bin/karaf "$*"
diff --git a/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/karaf.bat b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/karaf.bat
new file mode 100644
index 0000000..b54a669
--- /dev/null
+++ b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/karaf.bat
@@ -0,0 +1,25 @@
+@ECHO OFF
+REM =========================================================================
+REM
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements.  See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License.  You may obtain a copy of the License at
+REM
+REM    http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+REM
+REM =========================================================================
+
+SETLOCAL
+SET KARAF_BASE=${karaf.base}
+SET KARAF_HOME=${karaf.home}
+
+%KARAF_HOME%\bin\karaf.bat %*
diff --git a/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/start b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/start
new file mode 100644
index 0000000..d4552b5
--- /dev/null
+++ b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/start
@@ -0,0 +1,24 @@
+#!/bin/sh
+################################################################################
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You under the Apache License, Version 2.0
+#    (the "License"); you may not use this file except in compliance with
+#    the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+################################################################################
+
+KARAF_HOME=${karaf.home}
+
+exec ${KARAF_HOME}/bin/admin start ${karaf.name} "$@"
+
diff --git a/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/start.bat b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/start.bat
new file mode 100644
index 0000000..2da0758
--- /dev/null
+++ b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/start.bat
@@ -0,0 +1,23 @@
+@ECHO OFF
+REM =========================================================================
+REM 
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements.  See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License.  You may obtain a copy of the License at
+REM 
+REM    http://www.apache.org/licenses/LICENSE-2.0
+REM 
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+REM 
+REM =========================================================================
+
+SET KARAF_HOME=${karaf.home}
+
+%KARAF_HOME%\bin\admin.bat start ${karaf.name}
diff --git a/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/stop b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/stop
new file mode 100644
index 0000000..46f8b01
--- /dev/null
+++ b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/stop
@@ -0,0 +1,24 @@
+#!/bin/sh
+################################################################################
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You under the Apache License, Version 2.0
+#    (the "License"); you may not use this file except in compliance with
+#    the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+################################################################################
+
+KARAF_HOME=${karaf.home}
+
+exec ${KARAF_HOME}/bin/admin stop ${karaf.name} "$@"
+
diff --git a/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/stop.bat b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/stop.bat
new file mode 100644
index 0000000..fd6279e
--- /dev/null
+++ b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/bin/stop.bat
@@ -0,0 +1,23 @@
+@ECHO OFF
+REM =========================================================================
+REM 
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements.  See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License.  You may obtain a copy of the License at
+REM 
+REM    http://www.apache.org/licenses/LICENSE-2.0
+REM 
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+REM 
+REM =========================================================================
+
+SET KARAF_HOME=${karaf.home}
+
+%KARAF_HOME%\bin\admin.bat stop ${karaf.name}
diff --git a/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/etc/org.apache.felix.karaf.shell.cfg b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/etc/org.apache.felix.karaf.shell.cfg
new file mode 100644
index 0000000..8b83a07
--- /dev/null
+++ b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/etc/org.apache.felix.karaf.shell.cfg
@@ -0,0 +1,23 @@
+################################################################################
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You under the Apache License, Version 2.0
+#    (the "License"); you may not use this file except in compliance with
+#    the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+################################################################################
+
+#
+startLocalConsole=${karaf.startLocalConsole}
+startRemoteShell=${karaf.startRemoteShell}
+sshPort=${karaf.sshPort}
diff --git a/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/etc/org.ops4j.pax.url.mvn.cfg b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/etc/org.ops4j.pax.url.mvn.cfg
new file mode 100644
index 0000000..9ae4b8a
--- /dev/null
+++ b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/etc/org.ops4j.pax.url.mvn.cfg
@@ -0,0 +1,80 @@
+################################################################################
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You under the Apache License, Version 2.0
+#    (the "License"); you may not use this file except in compliance with
+#    the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+################################################################################
+
+#
+# If set to true, the following property will not allow any certificate to be used
+# when accessing maven repositories through SSL
+#
+#org.ops4j.pax.url.mvn.certificateCheck=
+
+#
+# Path to the local maven settings file.
+# The repositories defined in this file will be automatically added to the list
+# of default repositories if the 'org.ops4j.pax.url.mvn.repositories' property
+# below is not set.
+# The following locations are checked for the existence of the settings.xml file
+#   * 1. looks for the specified url
+#   * 2. if not found looks for ${user.home}/.m2/settings.xml
+#   * 3. if not found looks for ${maven.home}/conf/settings.xml
+#   * 4. if not found looks for ${M2_HOME}/conf/settings.xml
+#
+#org.ops4j.pax.url.mvn.settings=
+
+#
+# Path to the local maven repository which is used to avoid downloading
+# artifacts when they already exist locally.
+# The value of this property will be extracted from the settings.xml file
+# above, or defaulted to:
+#     System.getProperty( "user.home" ) + "/.m2/repository"
+#
+#org.ops4j.pax.url.mvn.localRepository=
+
+#
+# Comma separated list of repositories scanned when resolving an artifact.
+# Those repositories will be checked before iterating through the
+     below list of repositories and even before the local repository
+# A repository url can be appended with zero or more of the following flags:
+#    @snapshots  : the repository contains snaphots
+#    @noreleases : the repository does not contain any released artifacts
+#
+# The following property value will add the system folder as a repo.
+#
+org.ops4j.pax.url.mvn.defaultRepositories=file:${karaf.home}/system@snapshots, \
+    file:${karaf.base}/system@snapshots
+
+#
+# Comma separated list of repositories scanned when resolving an artifact.
+# The default list includes the following repositories:
+#    http://repo1.maven.org/maven2
+#    http://repository.ops4j.org/maven2
+# To add repositories to the default ones, prepend '+' to the list of repositories
+# to add.
+# A repository url can be appended with zero or more of the following flags:
+#    @snapshots  : the repository contains snaphots
+#    @noreleases : the repository does not contain any released artifacts
+#
+# The following property value will add the system folder as a repo.
+#
+org.ops4j.pax.url.mvn.repositories= \
+    http://repo1.maven.org/maven2, \
+    http://people.apache.org/repo/m2-snapshot-repository@snapshots@noreleases, \
+    http://repository.ops4j.org/maven2, \
+    http://svn.apache.org/repos/asf/servicemix/m2-repo, \
+    http://repository.springsource.com/maven/bundles/release, \
+    http://repository.springsource.com/maven/bundles/external
diff --git a/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/etc/system.properties b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/etc/system.properties
new file mode 100644
index 0000000..66bbaef
--- /dev/null
+++ b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/admin/etc/system.properties
@@ -0,0 +1,23 @@
+################################################################################
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You under the Apache License, Version 2.0
+#    (the "License"); you may not use this file except in compliance with
+#    the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+################################################################################
+
+org.ops4j.pax.logging.DefaultServiceLog.level=ERROR
+karaf.name=${karaf.name}
+karaf.default.repository=system
+xml.catalog.files=
diff --git a/karaf/admin/core/src/main/resources/org/apache/felix/karaf/jpm/impl/unix/start.sh b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/jpm/impl/unix/start.sh
new file mode 100644
index 0000000..1d1d720
--- /dev/null
+++ b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/jpm/impl/unix/start.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+################################################################################
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You under the Apache License, Version 2.0
+#    (the "License"); you may not use this file except in compliance with
+#    the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+################################################################################
+
+#exec 1>${out.file}
+#exec 2>${err.file}
+exec 1>/dev/null
+exec 2>/dev/null
+if [ "x${dir}" != "x" ]; then
+    cd ${dir}
+fi
+nohup ${command} &
+echo $! > ${pid.file}
diff --git a/karaf/admin/core/src/main/resources/org/apache/felix/karaf/jpm/impl/windows/destroy.vbs b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/jpm/impl/windows/destroy.vbs
new file mode 100644
index 0000000..abd60eb
--- /dev/null
+++ b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/jpm/impl/windows/destroy.vbs
@@ -0,0 +1,27 @@
+'===============================================================================
+'
+'    Licensed to the Apache Software Foundation (ASF) under one or more
+'    contributor license agreements.  See the NOTICE file distributed with
+'    this work for additional information regarding copyright ownership.
+'    The ASF licenses this file to You under the Apache License, Version 2.0
+'    (the "License"); you may not use this file except in compliance with
+'    the License.  You may obtain a copy of the License at
+'
+'       http://www.apache.org/licenses/LICENSE-2.0
+'
+'    Unless required by applicable law or agreed to in writing, software
+'    distributed under the License is distributed on an "AS IS" BASIS,
+'    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+'    See the License for the specific language governing permissions and
+'    limitations under the License.
+'
+'===============================================================================
+
+Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
+Set colProcessList = objWMIService.ExecQuery("Select * from Win32_Process Where ProcessId = ${pid}")
+intRetVal = 1
+For Each objProcess in colProcessList
+    objProcess.Terminate()
+    intRetVal = 0
+Next
+WScript.Quit(intRetVal)
diff --git a/karaf/admin/core/src/main/resources/org/apache/felix/karaf/jpm/impl/windows/running.vbs b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/jpm/impl/windows/running.vbs
new file mode 100644
index 0000000..32c65c5
--- /dev/null
+++ b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/jpm/impl/windows/running.vbs
@@ -0,0 +1,26 @@
+'===============================================================================
+'
+'    Licensed to the Apache Software Foundation (ASF) under one or more
+'    contributor license agreements.  See the NOTICE file distributed with
+'    this work for additional information regarding copyright ownership.
+'    The ASF licenses this file to You under the Apache License, Version 2.0
+'    (the "License"); you may not use this file except in compliance with
+'    the License.  You may obtain a copy of the License at
+'
+'       http://www.apache.org/licenses/LICENSE-2.0
+'
+'    Unless required by applicable law or agreed to in writing, software
+'    distributed under the License is distributed on an "AS IS" BASIS,
+'    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+'    See the License for the specific language governing permissions and
+'    limitations under the License.
+'
+'===============================================================================
+
+Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
+Set colProcessList = objWMIService.ExecQuery("Select * from Win32_Process Where ProcessId = ${pid}")
+intRetVal = 1
+For Each objProcess in colProcessList
+    intRetVal = 0
+Next
+WScript.Quit(intRetVal)
diff --git a/karaf/admin/core/src/main/resources/org/apache/felix/karaf/jpm/impl/windows/start.vbs b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/jpm/impl/windows/start.vbs
new file mode 100644
index 0000000..6004c86
--- /dev/null
+++ b/karaf/admin/core/src/main/resources/org/apache/felix/karaf/jpm/impl/windows/start.vbs
@@ -0,0 +1,34 @@
+'===============================================================================
+'
+'    Licensed to the Apache Software Foundation (ASF) under one or more
+'    contributor license agreements.  See the NOTICE file distributed with
+'    this work for additional information regarding copyright ownership.
+'    The ASF licenses this file to You under the Apache License, Version 2.0
+'    (the "License"); you may not use this file except in compliance with
+'    the License.  You may obtain a copy of the License at
+'
+'       http://www.apache.org/licenses/LICENSE-2.0
+'
+'    Unless required by applicable law or agreed to in writing, software
+'    distributed under the License is distributed on an "AS IS" BASIS,
+'    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+'    See the License for the specific language governing permissions and
+'    limitations under the License.
+'
+'===============================================================================
+
+Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
+Set objConfig = objWMIService.Get("Win32_ProcessStartup").SpawnInstance_
+objConfig.ShowWindow = SW_HIDE
+objConfig.CreateFlags = 8
+If Len("${dir}") > 0 Then
+    intReturn = objWMIService.Get("Win32_Process").Create("${command}", "${dir}", objConfig, intProcessID)
+Else
+    intReturn = objWMIService.Get("Win32_Process").Create("${command}", Null, objConfig, intProcessID)
+End If
+If intReturn = 0 Then
+    Set objOutputFile = CreateObject("Scripting.fileSystemObject").CreateTextFile("${pid.file}", TRUE)
+    objOutputFile.WriteLine(intProcessID)
+    objOutputFile.Close
+End If
+WScript.Quit(intReturn)
diff --git a/karaf/admin/core/src/test/java/org/apache/felix/karaf/admin/InstanceSettingsTest.java b/karaf/admin/core/src/test/java/org/apache/felix/karaf/admin/InstanceSettingsTest.java
new file mode 100644
index 0000000..cce8c80
--- /dev/null
+++ b/karaf/admin/core/src/test/java/org/apache/felix/karaf/admin/InstanceSettingsTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.admin;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestCase;
+import org.apache.felix.karaf.admin.InstanceSettings;
+import org.junit.Assert;
+
+public class InstanceSettingsTest extends TestCase {
+    public void testInstanceSettings() {
+        InstanceSettings is =
+            new InstanceSettings(1, null, Collections.<String>emptyList(), Arrays.asList("hi"));
+        assertEquals(1, is.getPort());
+        Assert.assertNull(is.getLocation());
+        assertEquals(Arrays.asList("hi"), is.getFeatures());
+        assertEquals(0, is.getFeatureURLs().size());
+    }
+    
+    public void testEqualsHashCode() {
+        testEqualsHashCode(1, "top", Collections.<String>emptyList(), Arrays.asList("hi"));
+        testEqualsHashCode(0, null, null, null);
+    }
+
+    private void testEqualsHashCode(int port, String location, List<String> featureURLs, List<String> features) {
+        InstanceSettings is = new InstanceSettings(port, location, featureURLs, features);
+        InstanceSettings is2 = new InstanceSettings(port, location, featureURLs, features);
+        assertEquals(is, is2);
+        assertEquals(is.hashCode(), is2.hashCode());
+    }
+    
+    public void testEqualsHashCode2() {
+        InstanceSettings is = new InstanceSettings(1, "top", Collections.<String>emptyList(), Arrays.asList("hi"));
+        Assert.assertFalse(is.equals(null));
+        Assert.assertFalse(is.equals(new Object()));
+        assertEquals(is, is);
+    }
+}
diff --git a/karaf/admin/core/src/test/java/org/apache/felix/karaf/admin/internal/AdminServiceImplTest.java b/karaf/admin/core/src/test/java/org/apache/felix/karaf/admin/internal/AdminServiceImplTest.java
new file mode 100644
index 0000000..02b9961
--- /dev/null
+++ b/karaf/admin/core/src/test/java/org/apache/felix/karaf/admin/internal/AdminServiceImplTest.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.admin.internal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+import org.apache.felix.karaf.admin.InstanceSettings;
+
+public class AdminServiceImplTest extends TestCase {
+    public void testHandleFeatures() throws Exception {
+        AdminServiceImpl as = new AdminServiceImpl();
+        
+        File f = File.createTempFile(getName(), ".test");
+        try {
+            Properties p = new Properties();
+            p.put("featuresBoot", "abc,def ");
+            p.put("featuresRepositories", "somescheme://xyz");
+            OutputStream os = new FileOutputStream(f);
+            try {
+                p.store(os, "Test comment");
+            } finally {
+                os.close();
+            }
+            
+            InstanceSettings s = new InstanceSettings(8122, null, null, Arrays.asList("test"));
+            as.handleFeatures(f, s);
+            
+            Properties p2 = new Properties();
+            InputStream is = new FileInputStream(f);
+            try {
+                p2.load(is);
+            } finally {
+                is.close();
+            }
+            assertEquals(2, p2.size());
+            assertEquals("abc,def,test", p2.get("featuresBoot"));
+            assertEquals("somescheme://xyz", p2.get("featuresRepositories"));
+        } finally {
+            f.delete();
+        }
+    }
+}
diff --git a/karaf/admin/core/src/test/java/org/apache/felix/karaf/jpm/MainTest.java b/karaf/admin/core/src/test/java/org/apache/felix/karaf/jpm/MainTest.java
new file mode 100644
index 0000000..2d4c80a
--- /dev/null
+++ b/karaf/admin/core/src/test/java/org/apache/felix/karaf/jpm/MainTest.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.jpm;
+
+public class MainTest {
+
+    public static void main(String[] args) throws Exception {
+        Thread.sleep(Long.parseLong(args[0]));
+    }
+}
diff --git a/karaf/admin/core/src/test/java/org/apache/felix/karaf/jpm/ProcessTest.java b/karaf/admin/core/src/test/java/org/apache/felix/karaf/jpm/ProcessTest.java
new file mode 100644
index 0000000..8b6ebc4
--- /dev/null
+++ b/karaf/admin/core/src/test/java/org/apache/felix/karaf/jpm/ProcessTest.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.jpm;
+
+import java.io.File;
+
+import junit.framework.TestCase;
+import org.apache.felix.karaf.jpm.impl.ScriptUtils;
+import org.apache.felix.karaf.jpm.ProcessBuilder;
+
+public class ProcessTest extends TestCase {
+
+    public void testCreate() throws Exception {
+        String javaPath = new File(System.getProperty("java.home"), ScriptUtils.isWindows() ? "bin\\java.exe" : "bin/java").getCanonicalPath();
+        System.err.println(javaPath);
+        StringBuilder command = new StringBuilder();
+        command.append(javaPath);
+        command.append(" -Dprop=\"key\"");
+        command.append(" -classpath ");
+        String clRes = getClass().getName().replace('.', '/') + ".class";
+        String str = getClass().getClassLoader().getResource(clRes).toString();
+        str = str.substring("file:".length(), str.indexOf(clRes));
+        command.append(str);
+        command.append(" ");
+        command.append(MainTest.class.getName());
+        command.append(" ");
+        command.append(60000);
+        System.err.println("Executing: " + command.toString());
+
+        ProcessBuilder builder = ProcessBuilderFactory.newInstance().newBuilder();
+        org.apache.felix.karaf.jpm.Process p = builder.command(command.toString()).start();
+        assertNotNull(p);
+        System.err.println("Process: " + p.getPid());
+        assertNotNull(p.getPid());
+        Thread.currentThread().sleep(1000);
+        System.err.println("Running: " + p.isRunning());
+        assertTrue(p.isRunning());
+        System.err.println("Destroying");
+        p.destroy();
+        Thread.currentThread().sleep(1000);
+        System.err.println("Running: " + p.isRunning());
+        assertFalse(p.isRunning());
+    }
+
+    /*
+     * When the process creation fails, no error is reported by the script
+     * 
+    public void testFailure() throws Exception {
+        ProcessBuilder builder = ProcessBuilderFactory.newInstance().newBuilder();
+        Process p = builder.command("ec").start();
+        fail("An exception should have been thrown");
+    }
+    */
+}