This closes #1160
diff --git a/brooklyn-docs/guide/misc/release-notes.md b/brooklyn-docs/guide/misc/release-notes.md
index 721655d..3661ab1 100644
--- a/brooklyn-docs/guide/misc/release-notes.md
+++ b/brooklyn-docs/guide/misc/release-notes.md
@@ -45,7 +45,9 @@
 and a caller references it, that location will now take priority over a location defined in a parent.
 Additionally, any locations specified in YAML extending the registered type will now *replace* locations on the referenced type;
 this means in many cases an explicit `locations: []` when extending a type will cause locations to be taken from the
-parent or application root in YAML.      
+parent or application root in YAML. Related to this, tags from referencing specs now preceed tags in the referenced types,
+and the referencing catalog item ID also takes priority; this has no effect in most cases, but if you have a chain of
+referenced types blueprint plan source code and the catalog item ID are now set correctly. 
 
 For changes in prior versions, please refer to the release notes for 
 [0.8.0](/v/0.8.0-incubating/misc/release-notes.html).
diff --git a/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java b/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java
index 560c499..789d282 100644
--- a/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java
+++ b/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java
@@ -83,7 +83,7 @@
 
     @Override
     public String toString() {
-        return Objects.toStringHelper(this).add("type", getType()).toString();
+        return Objects.toStringHelper(this).add("type", getType()).toString()+"@"+Integer.toHexString(System.identityHashCode(this));
     }
 
     protected abstract void checkValidType(Class<? extends T> type);
@@ -97,6 +97,19 @@
         catalogItemId = val;
         return self();
     }
+    // TODO in many places (callers to this method) we prefer a wrapper item ID;
+    // that is right, because the wrapper's defn will refer to the wrapped,
+    // but we might need also to collect the item ID's so that *all* can be searched.
+    // e.g. if R3 references R2 which references R1 any one of these might supply config keys 
+    // referencing resources or types in their local bundles. 
+    @Beta
+    public SpecT catalogItemIdIfNotNull(String val) {
+        if (val!=null) {
+            catalogItemId = val;
+        }
+        return self();
+    }
+
     
     public SpecT tag(Object tag) {
         tags.add(tag);
@@ -105,9 +118,19 @@
 
     /** adds the given tags */
     public SpecT tags(Iterable<Object> tagsToAdd) {
+        return tagsAdd(tagsToAdd);
+    }
+    /** adds the given tags */
+    public SpecT tagsAdd(Iterable<Object> tagsToAdd) {
         Iterables.addAll(this.tags, tagsToAdd);
         return self();
     }
+    /** replaces tags with the given */
+    public SpecT tagsReplace(Iterable<Object> tagsToReplace) {
+        this.tags.clear();
+        Iterables.addAll(this.tags, tagsToReplace);
+        return self();
+    }
     
     // TODO which semantics are correct? replace has been the behaviour;
     // add breaks tests and adds unwanted parameters,
diff --git a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java
index 7523343..5639945 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java
@@ -114,7 +114,7 @@
             throw new IllegalStateException("Creating spec from "+item+", got "+spec.getType()+" which is incompatible with expected "+expectedType);                
         }
 
-        ((AbstractBrooklynObjectSpec<?, ?>)spec).catalogItemId(item.getId());
+        ((AbstractBrooklynObjectSpec<?, ?>)spec).catalogItemIdIfNotNull(item.getId());
 
         if (Strings.isBlank( ((AbstractBrooklynObjectSpec<?, ?>)spec).getDisplayName() ))
             ((AbstractBrooklynObjectSpec<?, ?>)spec).displayName(item.getDisplayName());
diff --git a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EmptySoftwareProcessYamlTest.java b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EmptySoftwareProcessYamlTest.java
index bb3eeb9..0554917 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EmptySoftwareProcessYamlTest.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EmptySoftwareProcessYamlTest.java
@@ -23,14 +23,20 @@
 
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
 import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityAsserts;
 import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.collections.Jsonya;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 import org.testng.annotations.Test;
-import org.apache.brooklyn.location.ssh.SshMachineLocation;
-import org.apache.brooklyn.util.collections.Jsonya;
+
+import com.google.common.collect.Iterables;
 
 @Test
 public class EmptySoftwareProcessYamlTest extends AbstractYamlTest {
@@ -97,6 +103,22 @@
         Location actualMachine = entityLocationIterator.next();
         Assert.assertTrue(actualMachine instanceof SshMachineLocation, "wrong location: "+actualMachine);
         // TODO this, below, probably should be 'localhost on entity', see #1377
-        Assert.assertEquals(actualMachine.getParent().getDisplayName(), "loopback on app");
+        Assert.assertEquals(actualMachine.getParent().getDisplayName(), "localhost on entity");
+    }
+    
+    @Test(groups="Integration")
+    public void testNoSshing() throws Exception {
+        Entity app = createAndStartApplication(
+                "location: byon:(hosts=\"1.2.3.4\")",
+                "services:",
+                "- type: "+EmptySoftwareProcess.class.getName(),
+                "  brooklyn.config:",
+                "    sshMonitoring.enabled: false",
+                "    "+BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION.getName()+": true");
+        waitForApplicationTasks(app);
+
+        EmptySoftwareProcess entity = Iterables.getOnlyElement(Entities.descendants(app, EmptySoftwareProcess.class));
+        EntityAsserts.assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true);
+        EntityAsserts.assertAttributeEqualsContinually(entity, Attributes.SERVICE_UP, true);
     }
 }
diff --git a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EmptyWindowsProcessYamlTest.java b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EmptyWindowsProcessYamlTest.java
new file mode 100644
index 0000000..77043c7
--- /dev/null
+++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EmptyWindowsProcessYamlTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.brooklyn.camp.brooklyn;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityAsserts;
+import org.apache.brooklyn.entity.software.base.EmptyWindowsProcess;
+import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+
+@Test
+public class EmptyWindowsProcessYamlTest extends AbstractYamlTest {
+
+    @Test(groups="Integration")
+    public void testNoWinrm() throws Exception {
+        Entity app = createAndStartApplication(
+                "location: byon:(hosts=\"1.2.3.4\",osFamily=windows)",
+                "services:",
+                "- type: "+EmptyWindowsProcess.class.getName(),
+                "  brooklyn.config:",
+                "    winrmMonitoring.enabled: false");
+        waitForApplicationTasks(app);
+
+        EmptyWindowsProcess entity = Iterables.getOnlyElement(Entities.descendants(app, EmptyWindowsProcess.class));
+        EntityAsserts.assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true);
+        EntityAsserts.assertAttributeEqualsContinually(entity, Attributes.SERVICE_UP, true);
+        
+        Iterables.find(entity.getLocations(), Predicates.instanceOf(WinRmMachineLocation.class));
+    }
+}
diff --git a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java
index a759fed..d83711c 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java
@@ -40,6 +40,7 @@
 import org.apache.brooklyn.entity.stock.BasicApplication;
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.test.support.TestResourceUnavailableException;
+import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.osgi.OsgiTestResources;
 import org.testng.Assert;
 import org.testng.TestListenerAdapter;
@@ -210,6 +211,45 @@
         Assert.assertEquals(child.getCatalogItemId(), "t1:"+TEST_VERSION);
     }
     
+    @Test
+    public void testMetadataOnSpecCreatedFromItemReferencingAnApp() throws Exception {
+        // this nested ref to an app caused nested plan contents also to be recorded,
+        // due to how tags are merged. the *first* one is the most important, however,
+        // and ordering of tags should guarantee that.
+        // similarly ensure we get the right outermost non-null catalog item id.
+        addCatalogItems(
+              "brooklyn.catalog:",
+              "  version: '1'",
+              "  items:",
+              "  - id: app1",
+              "    name: myApp1",
+              "    item:",
+              "      type: org.apache.brooklyn.entity.stock.BasicApplication",
+              "      brooklyn.config: { foo: bar }",
+              "  - id: app1r",
+              "    item_type: template",
+              "    item:",
+              "      services:",
+              "      - type: app1",
+              "        brooklyn.config:",
+              "          foo: boo"
+            );
+        
+        EntitySpec<? extends Application> spec = EntityManagementUtils.createEntitySpecForApplication(mgmt(),
+            "services: [ { type: app1r } ]\n" +
+            "location: localhost");
+        
+        List<NamedStringTag> yamls = BrooklynTags.findAll(BrooklynTags.YAML_SPEC_KIND, spec.getTags());
+        Assert.assertTrue(yamls.size() >= 1, "Expected at least 1 yaml tag; instead had: "+yamls);
+        String yaml = yamls.iterator().next().getContents();
+        Asserts.assertStringContains(yaml, "services:", "type: app1r", "localhost");
+        
+        Assert.assertEquals(spec.getChildren().size(), 0);
+        Assert.assertEquals(spec.getType(), BasicApplication.class);
+        Assert.assertEquals(ConfigBag.newInstance(spec.getConfig()).getStringKey("foo"), "boo");
+        Assert.assertEquals(spec.getCatalogItemId(), "app1r:1");
+    }
+    
     private RegisteredType makeItem() {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
         
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
index e75e2de..2f37e7c 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
@@ -253,10 +253,9 @@
         
         if (!wrapperParent.getParameters().isEmpty())
             wrappedChild.parametersReplace(wrapperParent.getParameters());
-        
-        if (wrappedChild.getCatalogItemId()==null) {
-            wrappedChild.catalogItemId(wrapperParent.getCatalogItemId());
-        }
+
+        // prefer the wrapper ID (change in 2016-01); see notes on the catalogItemIdIfNotNull method
+        wrappedChild.catalogItemIdIfNotNull(wrapperParent.getCatalogItemId());
 
         // NB: this clobber's child config wherever they conflict; might prefer to deeply merge maps etc
         // (or maybe even prevent the merge in these cases; 
@@ -266,11 +265,12 @@
         wrappedChild.configure(wrapperParent.getFlags());
         
         // copying tags to all entities may be something the caller wants to control,
-        // e.g. if we're creating a list of entities which will be added,
-        // ignoring the parent Application holder; 
-        // in that case each child's BrooklynTags.YAML_SPEC tag will show all entities;
-        // but in the normal case where we're unwrapping one, it's probably right.
-        wrappedChild.tags(wrapperParent.getTags());
+        // e.g. if we're adding multiple, the caller might not want to copy the parent
+        // (the BrooklynTags.YAML_SPEC tag will include the parents source including siblings),
+        // but OTOH they might because otherwise the parent's tags might get lost.
+        // also if we are unwrapping multiple registry references we will get the YAML_SPEC for each;
+        // putting the parent's tags first however causes the preferred (outer) one to be retrieved first.
+        wrappedChild.tagsReplace(MutableList.copyOf(wrapperParent.getTags()).appendAll(wrappedChild.getTags()));
     }
 
     public static EntitySpec<? extends Application> newWrapperApp() {
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/typereg/AbstractTypePlanTransformer.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/typereg/AbstractTypePlanTransformer.java
index 8f671f2..5ba36b3 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/typereg/AbstractTypePlanTransformer.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/typereg/AbstractTypePlanTransformer.java
@@ -103,7 +103,8 @@
                 @Override protected Object visitSpec() {
                     try { 
                         AbstractBrooklynObjectSpec<?, ?> result = createSpec(type, context);
-                        result.catalogItemId(type.getId());
+                        // see notes on catalogItemIdIfNotNull
+                        result.catalogItemIdIfNotNull(type.getId());
                         return result;
                     } catch (Exception e) { throw Exceptions.propagate(e); }
                 }
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/typereg/BasicRegisteredType.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/typereg/BasicRegisteredType.java
index 05f0773..e0e2305 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/typereg/BasicRegisteredType.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/typereg/BasicRegisteredType.java
@@ -64,6 +64,7 @@
 
     @Override
     public String getId() {
+        if (symbolicName==null) return null;
         return symbolicName + (version!=null ? ":"+version : "");
     }
     
diff --git a/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptySoftwareProcess.java b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptySoftwareProcess.java
index 98a97c0..3fd694c 100644
--- a/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptySoftwareProcess.java
+++ b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptySoftwareProcess.java
@@ -19,10 +19,14 @@
 package org.apache.brooklyn.entity.software.base;
 
 import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
 
 @ImplementedBy(EmptySoftwareProcessImpl.class)
 public interface EmptySoftwareProcess extends SoftwareProcess {
 
+    ConfigKey<Boolean> USE_SSH_MONITORING = ConfigKeys.newConfigKey("sshMonitoring.enabled", "SSH monitoring enabled", Boolean.TRUE);
+
     public SoftwareProcessDriver getDriver();
     
 }
diff --git a/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptySoftwareProcessImpl.java b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptySoftwareProcessImpl.java
index 154a08a..7330461 100644
--- a/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptySoftwareProcessImpl.java
+++ b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptySoftwareProcessImpl.java
@@ -18,6 +18,8 @@
  */
 package org.apache.brooklyn.entity.software.base;
 
+import org.apache.brooklyn.core.entity.Attributes;
+
 public class EmptySoftwareProcessImpl extends SoftwareProcessImpl implements EmptySoftwareProcess {
 
     @Override
@@ -28,7 +30,11 @@
     @Override
     protected void connectSensors() {
         super.connectSensors();
-        connectServiceUpIsRunning();
+        if (isSshMonitoringEnabled()) {
+            connectServiceUpIsRunning();
+        } else {
+            sensors().set(Attributes.SERVICE_UP, true);
+        }
     }
 
     @Override
@@ -36,4 +42,8 @@
         disconnectServiceUpIsRunning();
         super.disconnectSensors();
     }
+    
+    protected boolean isSshMonitoringEnabled() {
+        return Boolean.TRUE.equals(getConfig(USE_SSH_MONITORING));
+    }
 }
diff --git a/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptyWindowsProcess.java b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptyWindowsProcess.java
new file mode 100644
index 0000000..770ba0d
--- /dev/null
+++ b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptyWindowsProcess.java
@@ -0,0 +1,38 @@
+/*
+ * 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.brooklyn.entity.software.base;
+
+import java.util.Collection;
+
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+
+import com.google.common.collect.ImmutableSet;
+
+@ImplementedBy(EmptyWindowsProcessImpl.class)
+public interface EmptyWindowsProcess extends SoftwareProcess {
+
+    // 3389 is RDP; 5985 is WinRM (3389 isn't used by Brooklyn, but useful for the end-user subsequently)
+    ConfigKey<Collection<Integer>> REQUIRED_OPEN_LOGIN_PORTS = ConfigKeys.newConfigKeyWithDefault(
+            SoftwareProcess.REQUIRED_OPEN_LOGIN_PORTS,
+            ImmutableSet.of(5985, 3389));
+    
+    ConfigKey<Boolean> USE_WINRM_MONITORING = ConfigKeys.newConfigKey("winrmMonitoring.enabled", "WinRM monitoring enabled", Boolean.TRUE);
+}
diff --git a/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptyWindowsProcessDriver.java b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptyWindowsProcessDriver.java
new file mode 100644
index 0000000..d6e1c99
--- /dev/null
+++ b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptyWindowsProcessDriver.java
@@ -0,0 +1,22 @@
+/*
+ * 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.brooklyn.entity.software.base;
+
+public interface EmptyWindowsProcessDriver extends SoftwareProcessDriver {
+}
diff --git a/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptyWindowsProcessImpl.java b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptyWindowsProcessImpl.java
new file mode 100644
index 0000000..ae87774
--- /dev/null
+++ b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptyWindowsProcessImpl.java
@@ -0,0 +1,49 @@
+/*
+ * 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.brooklyn.entity.software.base;
+
+import org.apache.brooklyn.core.entity.Attributes;
+
+public class EmptyWindowsProcessImpl extends SoftwareProcessImpl implements EmptyWindowsProcess {
+
+    @Override
+    public Class<?> getDriverInterface() {
+        return EmptyWindowsProcessDriver.class;
+    }
+
+    @Override
+    protected void connectSensors() {
+        super.connectSensors();
+        if (isWinrmMonitoringEnabled()) {
+            connectServiceUpIsRunning();
+        } else {
+            sensors().set(Attributes.SERVICE_UP, true);
+        }
+    }
+
+    @Override
+    protected void disconnectSensors() {
+        disconnectServiceUpIsRunning();
+        super.disconnectSensors();
+    }
+    
+    protected boolean isWinrmMonitoringEnabled() {
+        return Boolean.TRUE.equals(getConfig(USE_WINRM_MONITORING));
+    }
+}
diff --git a/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptyWindowsProcessWinRmDriver.java b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptyWindowsProcessWinRmDriver.java
new file mode 100644
index 0000000..4a9a054
--- /dev/null
+++ b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/EmptyWindowsProcessWinRmDriver.java
@@ -0,0 +1,97 @@
+/*
+ * 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.brooklyn.entity.software.base;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
+import org.apache.brooklyn.util.net.UserAndHostAndPort;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class EmptyWindowsProcessWinRmDriver extends AbstractSoftwareProcessWinRmDriver implements VanillaWindowsProcessDriver {
+    @SuppressWarnings("unused")
+    private static final Logger LOG = LoggerFactory.getLogger(EmptyWindowsProcessWinRmDriver.class);
+
+    private final AtomicBoolean running = new AtomicBoolean();
+
+    public EmptyWindowsProcessWinRmDriver(EmptyWindowsProcessImpl entity, WinRmMachineLocation machine) {
+        super(entity, machine);
+    }
+
+    @Override
+    public void start() {
+        WinRmMachineLocation machine = (WinRmMachineLocation) location;
+        UserAndHostAndPort winrmAddress = UserAndHostAndPort.fromParts(machine.getUser(), machine.getAddress().getHostName(), machine.config().get(WinRmMachineLocation.WINRM_PORT));
+        getEntity().sensors().set(Attributes.WINRM_ADDRESS, winrmAddress);
+
+        super.start();
+    }
+
+    @Override
+    public boolean isRunning() {
+        return running.get();
+    }
+
+    @Override
+    public void install() { }
+
+    @Override
+    public void customize() { }
+
+    @Override
+    public void copyInstallResources() { 
+        Map<String, String> installFiles = entity.getConfig(SoftwareProcess.INSTALL_FILES);
+        Map<String, String> installTemplates = entity.getConfig(SoftwareProcess.INSTALL_TEMPLATES);
+        if ((installFiles!=null && !installFiles.isEmpty()) || (installTemplates!=null && !installTemplates.isEmpty())) {
+            // only do this if there are files, to prevent unnecessary `mkdir`
+            super.copyInstallResources();
+        }
+    }
+
+    @Override
+    public void copyRuntimeResources() { 
+        Map<String, String> runtimeFiles = entity.getConfig(SoftwareProcess.RUNTIME_FILES);
+        Map<String, String> runtimeTemplates = entity.getConfig(SoftwareProcess.RUNTIME_TEMPLATES);
+        if ((runtimeFiles!=null && !runtimeFiles.isEmpty()) || (runtimeTemplates!=null && !runtimeTemplates.isEmpty())) {
+            // only do this if there are files, to prevent unnecessary `mkdir`
+            super.copyRuntimeResources();
+        }        
+    }
+
+    @Override
+    public void launch() {
+        running.set(true);
+    }
+
+    @Override
+    public void rebind() {
+        super.rebind();
+        /* TODO not necessarily, but there is not yet an easy way to persist state without 
+         * using config/sensors which we might not want do. */
+        running.set(true);
+    }
+
+    @Override
+    public void stop() {
+        running.set(false);
+    }
+}