You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2015/09/23 12:23:30 UTC

[3/8] incubator-brooklyn git commit: Testing winrm exit codes

Testing winrm exit codes


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/10071a92
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/10071a92
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/10071a92

Branch: refs/heads/master
Commit: 10071a928c57aa3178442d9f72f22d47798ff7ca
Parents: 2995471
Author: Aled Sage <al...@gmail.com>
Authored: Mon Sep 21 21:36:11 2015 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Wed Sep 23 01:32:48 2015 +0100

----------------------------------------------------------------------
 ...laWindowsProcessWinrmExitStatusLiveTest.java | 291 ++++++++++
 ...nillaWindowsProcessWinrmStreamsLiveTest.java |  80 ++-
 .../location/WinRmMachineLocationLiveTest.java  | 563 ++++++++++++++++++-
 .../base/test/location/WindowsTestFixture.java  |  71 +++
 usage/camp/pom.xml                              |   6 +
 .../camp/brooklyn/WindowsYamlLiveTest.java      | 410 ++++++++++++++
 .../apache/brooklyn/camp/brooklyn/echoArg.bat   |  19 +
 .../camp/brooklyn/echoFreemarkerMyarg.bat       |  18 +
 .../camp/brooklyn/echoFreemarkerMyarg.ps1       |  18 +
 .../apache/brooklyn/camp/brooklyn/echoMyArg.ps1 |  22 +
 .../org/apache/brooklyn/camp/brooklyn/exit0.bat |  18 +
 .../org/apache/brooklyn/camp/brooklyn/exit0.ps1 |  18 +
 .../org/apache/brooklyn/camp/brooklyn/exit1.bat |  18 +
 .../org/apache/brooklyn/camp/brooklyn/exit1.ps1 |  19 +
 14 files changed, 1521 insertions(+), 50 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/10071a92/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessWinrmExitStatusLiveTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessWinrmExitStatusLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessWinrmExitStatusLiveTest.java
new file mode 100644
index 0000000..998d987
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessWinrmExitStatusLiveTest.java
@@ -0,0 +1,291 @@
+/*
+ * 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 static org.testng.Assert.fail;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+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.factory.ApplicationBuilder;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.entity.software.base.test.location.WindowsTestFixture;
+import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class VanillaWindowsProcessWinrmExitStatusLiveTest {
+    private static final Logger LOG = LoggerFactory.getLogger(VanillaWindowsProcessWinrmExitStatusLiveTest.class);
+
+    private static final String INVALID_CMD = "thisCommandDoesNotExistAEFafiee3d";
+    
+    protected ManagementContextInternal mgmt;
+    protected TestApplication app;
+    protected MachineProvisioningLocation<WinRmMachineLocation> location;
+    protected WinRmMachineLocation machine;
+
+    @BeforeClass(alwaysRun=true)
+    public void setUpClass() throws Exception {
+        mgmt = new LocalManagementContextForTests(BrooklynProperties.Factory.newDefault());
+
+        location = WindowsTestFixture.setUpWindowsLocation(mgmt);
+        machine = location.obtain(ImmutableMap.of());
+    }
+
+    @AfterClass(alwaysRun=true)
+    public void tearDownClass() throws Exception {
+        try {
+            try {
+                if (location != null) location.release(machine);
+            } finally {
+                if (mgmt != null) Entities.destroyAll(mgmt);
+            }
+        } catch (Throwable t) {
+            LOG.error("Caught exception in tearDownClass method", t);
+        } finally {
+            mgmt = null;
+        }
+    }
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class)
+                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true);
+        app = ApplicationBuilder.newManagedApp(appSpec, mgmt);
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        try {
+            try {
+                if (app != null) Entities.destroy(app);
+            } catch (Throwable t) {
+                LOG.error("Caught exception in tearDown method", t);
+            }
+        } finally {
+            app = null;
+        }
+    }
+
+    @Test(groups = "Live")
+    public void testExecWithZeroExitCodes() {
+        VanillaWindowsProcess entity = app.createAndManageChild(EntitySpec.create(VanillaWindowsProcess.class)
+                .configure(VanillaWindowsProcess.PRE_INSTALL_COMMAND, "echo preinstall")
+                .configure(VanillaWindowsProcess.INSTALL_COMMAND, "echo install")
+                .configure(VanillaWindowsProcess.POST_INSTALL_COMMAND, "echo postinstall")
+                .configure(VanillaWindowsProcess.CUSTOMIZE_COMMAND, "echo customize")
+                .configure(VanillaWindowsProcess.PRE_LAUNCH_COMMAND, "echo prelaunch")
+                .configure(VanillaWindowsProcess.LAUNCH_COMMAND, "echo launch")
+                .configure(VanillaWindowsProcess.POST_LAUNCH_COMMAND, "echo postlaunch")
+                .configure(VanillaWindowsProcess.CHECK_RUNNING_COMMAND, "echo checkrunning")
+                .configure(VanillaWindowsProcess.STOP_COMMAND, "echo stop"));
+        
+        app.start(ImmutableList.of(machine));
+        LOG.info("app started; asserting up");
+        EntityTestUtils.assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        
+        entity.stop();
+        LOG.info("stopping entity");
+        EntityTestUtils.assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, false);
+    }
+
+    @Test(groups = "Live")
+    public void testExecPsWithZeroExitCodes() {
+        VanillaWindowsProcess entity = app.createAndManageChild(EntitySpec.create(VanillaWindowsProcess.class)
+                .configure(VanillaWindowsProcess.PRE_INSTALL_POWERSHELL_COMMAND, "Write-Host preinstall")
+                .configure(VanillaWindowsProcess.INSTALL_POWERSHELL_COMMAND, "Write-Host install")
+                .configure(VanillaWindowsProcess.POST_INSTALL_POWERSHELL_COMMAND, "Write-Host postinstall")
+                .configure(VanillaWindowsProcess.CUSTOMIZE_POWERSHELL_COMMAND, "Write-Host customize")
+                .configure(VanillaWindowsProcess.PRE_LAUNCH_POWERSHELL_COMMAND, "Write-Host prelaunch")
+                .configure(VanillaWindowsProcess.LAUNCH_POWERSHELL_COMMAND, "Write-Host launch")
+                .configure(VanillaWindowsProcess.POST_LAUNCH_POWERSHELL_COMMAND, "Write-Host postlaunch")
+                .configure(VanillaWindowsProcess.CHECK_RUNNING_POWERSHELL_COMMAND, "Write-Host checkrunning")
+                .configure(VanillaWindowsProcess.STOP_POWERSHELL_COMMAND, "Write-Host stop"));
+        
+        app.start(ImmutableList.of(machine));
+        LOG.info("app started; asserting up");
+        EntityTestUtils.assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        
+        entity.stop();
+        LOG.info("stopping entity");
+        EntityTestUtils.assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, false);
+    }
+
+    @Test(groups = "Live")
+    public void testPreInstallNonZeroExitCode() {
+        runExecNonZeroExitCode("pre-install-command");
+    }
+
+    @Test(groups = "Live")
+    public void testInstallNonZeroExitCode() {
+        runExecNonZeroExitCode("install-command");
+    }
+
+    @Test(groups = "Live")
+    public void testPostInstallNonZeroExitCode() {
+        runExecNonZeroExitCode("post-install-command");
+    }
+
+    @Test(groups = "Live")
+    public void testCustomizeNonZeroExitCode() {
+        runExecNonZeroExitCode("customize-command");
+    }
+
+    @Test(groups = "Live")
+    public void testPreLaunchNonZeroExitCode() {
+        runExecNonZeroExitCode("pre-launch-command");
+    }
+
+    @Test(groups = "Live")
+    public void testLaunchNonZeroExitCode() {
+        runExecNonZeroExitCode("launch-command");
+    }
+
+    @Test(groups = "Live")
+    public void testCheckRunningNonZeroExitCode() {
+        runExecNonZeroExitCode("is-running-command");
+    }
+
+    @Test(groups = "Live")
+    public void testStopNonZeroExitCode() {
+        runExecNonZeroExitCode("stop-command");
+    }
+    
+    @Test(groups = "Live")
+    public void testPsPreInstallNonZeroExitCode() {
+        runExecPsNonZeroExitCode("pre-install-command");
+    }
+
+    @Test(groups = "Live")
+    public void testPsInstallNonZeroExitCode() {
+        runExecPsNonZeroExitCode("install-command");
+    }
+
+    @Test(groups = "Live")
+    public void testPsPostInstallNonZeroExitCode() {
+        runExecPsNonZeroExitCode("post-install-command");
+    }
+
+    @Test(groups = "Live")
+    public void testPsCustomizeNonZeroExitCode() {
+        runExecPsNonZeroExitCode("customize-command");
+    }
+
+    @Test(groups = "Live")
+    public void testPsPreLaunchNonZeroExitCode() {
+        runExecPsNonZeroExitCode("pre-launch-command");
+    }
+
+    @Test(groups = "Live")
+    public void testPsLaunchNonZeroExitCode() {
+        runExecPsNonZeroExitCode("launch-command");
+    }
+
+    @Test(groups = "Live")
+    public void testPsCheckRunningNonZeroExitCode() {
+        runExecPsNonZeroExitCode("is-running-command");
+    }
+
+    @Test(groups = "Live")
+    public void testPsStopNonZeroExitCode() {
+        runExecPsNonZeroExitCode("stop-command");
+    }
+
+    protected void runExecNonZeroExitCode(String phase) {
+        VanillaWindowsProcess entity = app.createAndManageChild(EntitySpec.create(VanillaWindowsProcess.class)
+                .configure(VanillaWindowsProcess.PRE_INSTALL_COMMAND, phase.equals("pre-install-command") ? INVALID_CMD : "echo install")
+                .configure(VanillaWindowsProcess.INSTALL_COMMAND, phase.equals("install-command") ? INVALID_CMD : "echo install")
+                .configure(VanillaWindowsProcess.POST_INSTALL_COMMAND, phase.equals("post-install-command") ? INVALID_CMD : "echo postinstall")
+                .configure(VanillaWindowsProcess.CUSTOMIZE_COMMAND, phase.equals("customize-command") ? INVALID_CMD : "echo customize")
+                .configure(VanillaWindowsProcess.PRE_LAUNCH_COMMAND, phase.equals("pre-launch-command") ? INVALID_CMD : "echo prelaunch")
+                .configure(VanillaWindowsProcess.LAUNCH_COMMAND, phase.equals("launch-command") ? INVALID_CMD : "echo launch")
+                .configure(VanillaWindowsProcess.POST_LAUNCH_COMMAND, phase.equals("post-launch-command") ? INVALID_CMD : "echo postlaunch")
+                .configure(VanillaWindowsProcess.CHECK_RUNNING_COMMAND, phase.equals("is-running-command") ? INVALID_CMD : "echo checkrunning")
+                .configure(VanillaWindowsProcess.STOP_COMMAND, phase.equals("stop-command") ? INVALID_CMD : "echo stop")
+                .configure(BrooklynConfigKeys.START_TIMEOUT, Duration.ONE_MINUTE));
+
+        if (phase.equals("stop-command")) {
+            app.start(ImmutableList.of(machine));
+            try {
+                entity.stop();
+                fail();
+            } catch (Exception e) {
+                if (!(e.toString().contains("invalid result") && e.toString().contains("for "+phase))) throw e;
+            }
+        } else {
+            try {
+                app.start(ImmutableList.of(machine));
+                fail();
+            } catch (Exception e) {
+                if (!(e.toString().contains("invalid result") && e.toString().contains("for "+phase))) throw e;
+            }
+        }
+    }
+    
+    protected void runExecPsNonZeroExitCode(String phase) {
+        VanillaWindowsProcess entity = app.createAndManageChild(EntitySpec.create(VanillaWindowsProcess.class)
+                .configure(VanillaWindowsProcess.PRE_INSTALL_POWERSHELL_COMMAND, phase.equals("pre-install-command") ? INVALID_CMD : "Write-Host install")
+                .configure(VanillaWindowsProcess.INSTALL_POWERSHELL_COMMAND, phase.equals("install-command") ? INVALID_CMD : "Write-Host install")
+                .configure(VanillaWindowsProcess.POST_INSTALL_POWERSHELL_COMMAND, phase.equals("post-install-command") ? INVALID_CMD : "Write-Host postinstall")
+                .configure(VanillaWindowsProcess.CUSTOMIZE_POWERSHELL_COMMAND, phase.equals("customize-command") ? INVALID_CMD : "Write-Host customize")
+                .configure(VanillaWindowsProcess.PRE_LAUNCH_POWERSHELL_COMMAND, phase.equals("pre-launch-command") ? INVALID_CMD : "Write-Host prelaunch")
+                .configure(VanillaWindowsProcess.LAUNCH_POWERSHELL_COMMAND, phase.equals("launch-command") ? INVALID_CMD : "Write-Host launch")
+                .configure(VanillaWindowsProcess.POST_LAUNCH_POWERSHELL_COMMAND, phase.equals("post-launch-command") ? INVALID_CMD : "Write-Host postlaunch")
+                .configure(VanillaWindowsProcess.CHECK_RUNNING_POWERSHELL_COMMAND, phase.equals("is-running-command") ? INVALID_CMD : "Write-Host checkrunning")
+                .configure(VanillaWindowsProcess.STOP_POWERSHELL_COMMAND, phase.equals("stop-command") ? INVALID_CMD : "Write-Host stop")
+                .configure(BrooklynConfigKeys.START_TIMEOUT, Duration.ONE_MINUTE));
+
+        if (phase.equals("stop-command")) {
+            app.start(ImmutableList.of(machine));
+            try {
+                entity.stop();
+                fail();
+            } catch (Exception e) {
+                if (!(e.toString().contains("invalid result") && e.toString().contains("for "+phase))) throw e;
+            }
+        } else {
+            try {
+                app.start(ImmutableList.of(machine));
+                fail();
+            } catch (Exception e) {
+                if (!(e.toString().contains("invalid result") && e.toString().contains("for "+phase))) throw e;
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/10071a92/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessWinrmStreamsLiveTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessWinrmStreamsLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessWinrmStreamsLiveTest.java
index 2ee310b..fdc4c59 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessWinrmStreamsLiveTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessWinrmStreamsLiveTest.java
@@ -18,41 +18,78 @@
  */
 package org.apache.brooklyn.entity.software.base;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+
 import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.location.jclouds.JcloudsLocation;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.entity.software.base.test.location.WindowsTestFixture;
+import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import java.util.Map;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 
 public class VanillaWindowsProcessWinrmStreamsLiveTest extends AbstractSoftwareProcessStreamsTest {
-    private Location location;
+    
+    private static final Logger LOG = LoggerFactory.getLogger(VanillaWindowsProcessWinrmStreamsLiveTest.class);
+
+    protected MachineProvisioningLocation<WinRmMachineLocation> location;
+    protected WinRmMachineLocation machine;
+    
+    // Using BeforeClass so that uses just a single VM for all tests
+    @BeforeClass(alwaysRun = true)
+    public void setUpClass() throws Exception {
+        super.setUp();
+        if (app != null) Entities.destroy(app);
+        
+        location = WindowsTestFixture.setUpWindowsLocation(mgmt);
+        machine = location.obtain(ImmutableMap.of());
+    }
+
+    @AfterClass(alwaysRun = true)
+    public void tearDownClass() throws Exception {
+        try {
+            if (location != null && machine != null) location.release(machine);
+        } catch (Throwable t) {
+            LOG.error("Caught exception in tearDownClass method", t);
+        } finally {
+            super.tearDown();
+        }
+    }
 
-    @BeforeMethod(alwaysRun=true)
+    @BeforeMethod(alwaysRun = true)
     @Override
     public void setUp() throws Exception {
-        super.setUp();
+        app = ApplicationBuilder.newManagedApp(TestApplication.class, mgmt);
+    }
 
-        Map<String, Object> config = ImmutableMap.<String, Object>builder()
-                .put("inboundPorts", ImmutableList.of(5985, 3389))
-                .put("osFamily", "windows")
-                .put("displayName", "AWS Oregon (Windows)")
-                .put("imageOwner", "801119661308")
-                .put("imageNameRegex", "Windows_Server-2012-R2_RTM-English-64Bit-Base-.*")
-                .put("hardwareId", "m3.medium")
-                .put("checkRunning.command", "echo true")
-                .put("useJcloudsSshInit", false)
-                .build();
-        location = ((JcloudsLocation)mgmt.getLocationRegistry().resolve("jclouds:aws-ec2:us-west-1", config)).obtain();
+    @AfterMethod(alwaysRun = true)
+    @Override
+    public void tearDown() throws Exception {
+        try {
+            if (app != null) Entities.destroy(app);
+        } catch (Throwable t) {
+            LOG.error("Caught exception in tearDown method", t);
+        } finally {
+            app = null;
+        }
     }
 
     @Test(groups = "Live")
     @Override
     public void testGetsStreams() {
         VanillaWindowsProcess entity = app.createAndManageChild(EntitySpec.create(VanillaWindowsProcess.class)
+                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true)
                 .configure(VanillaSoftwareProcess.PRE_INSTALL_COMMAND, "echo " + getCommands().get("winrm: pre-install-command.*"))
                 .configure(VanillaSoftwareProcess.INSTALL_COMMAND, "echo " + getCommands().get("winrm: install.*"))
                 .configure(VanillaSoftwareProcess.POST_INSTALL_COMMAND, "echo " + getCommands().get("winrm: post-install-command.*"))
@@ -61,13 +98,14 @@ public class VanillaWindowsProcessWinrmStreamsLiveTest extends AbstractSoftwareP
                 .configure(VanillaSoftwareProcess.LAUNCH_COMMAND, "echo " + getCommands().get("winrm: launch.*"))
                 .configure(VanillaSoftwareProcess.POST_LAUNCH_COMMAND, "echo " + getCommands().get("winrm: post-launch-command.*"))
                 .configure(VanillaSoftwareProcess.CHECK_RUNNING_COMMAND, "echo true"));
-        app.start(ImmutableList.of(location));
+        app.start(ImmutableList.of(machine));
         assertStreams(entity);
     }
 
     @Test(groups = "Live")
     public void testGetsStreamsPowerShell() {
         VanillaWindowsProcess entity = app.createAndManageChild(EntitySpec.create(VanillaWindowsProcess.class)
+                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true)
                 .configure(VanillaWindowsProcess.PRE_INSTALL_POWERSHELL_COMMAND, "echo " + getCommands().get("winrm: pre-install-command.*"))
                 .configure(VanillaWindowsProcess.INSTALL_POWERSHELL_COMMAND, "echo " + getCommands().get("winrm: install.*"))
                 .configure(VanillaWindowsProcess.POST_INSTALL_POWERSHELL_COMMAND, "echo " + getCommands().get("winrm: post-install-command.*"))
@@ -76,7 +114,7 @@ public class VanillaWindowsProcessWinrmStreamsLiveTest extends AbstractSoftwareP
                 .configure(VanillaWindowsProcess.LAUNCH_POWERSHELL_COMMAND, "echo " + getCommands().get("winrm: launch.*"))
                 .configure(VanillaWindowsProcess.POST_LAUNCH_POWERSHELL_COMMAND, "echo " + getCommands().get("winrm: post-launch-command.*"))
                 .configure(VanillaWindowsProcess.CHECK_RUNNING_POWERSHELL_COMMAND, "echo true"));
-        app.start(ImmutableList.of(location));
+        app.start(ImmutableList.of(machine));
         assertStreams(entity);
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/10071a92/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java
index dd4dee7..615eda7 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java
@@ -19,76 +19,581 @@
 package org.apache.brooklyn.entity.software.base.test.location;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotEquals;
 
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
 import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.internal.BrooklynProperties;
 import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
 import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
 import org.apache.brooklyn.core.test.entity.TestApplication;
-import org.apache.brooklyn.location.jclouds.JcloudsLocation;
-
-import io.cloudsoft.winrm4j.winrm.WinRmToolResponse;
-
+import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.apache.brooklyn.util.time.Time;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
-import org.apache.brooklyn.location.jclouds.JcloudsWinRmMachineLocation;
 
+import com.google.api.client.util.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.base.Stopwatch;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import io.cloudsoft.winrm4j.winrm.WinRmToolResponse;
 
+/**
+ * Tests execution of commands (batch and powershell) on Windows over WinRM, and of
+ * file upload.
+ * 
+ * There are limitations with what is supported by PyWinRM. These are highlighted in
+ * tests marked as "WIP" (see individual tests).
+ * 
+ * These limitations are documented in docs/guide/yaml/winrm/index.md.
+ * Please update the docs if you encountered new situations, or change the behaviuor 
+ * of existing use-cases.
+ */
 public class WinRmMachineLocationLiveTest {
-    
-    // FIXME failing locally with:
-    //   Caused by: Traceback (most recent call last):
-    //     File "__pyclasspath__/winrm/__init__.py", line 40, in run_cmd
-    //     File "__pyclasspath__/winrm/protocol.py", line 118, in open_shell
-    //     File "__pyclasspath__/winrm/protocol.py", line 190, in send_message
-    //     File "__pyclasspath__/winrm/transport.py", line 112, in send_message
-    //     winrm.exceptions.WinRMTransportError: 500 WinRMTransport. [Errno 20001] getaddrinfo failed
-    //     at org.python.core.PyException.doRaise(PyException.java:226)
 
+    private static final int MAX_EXECUTOR_THREADS = 100;
+
+    /*
+     * TODO: Deferred implementing copyFrom or environment variables.
+     */
+    
     private static final Logger LOG = LoggerFactory.getLogger(BrooklynAppLiveTestSupport.class);
 
-    protected JcloudsLocation loc;
+    private static final String INVALID_CMD = "thisCommandDoesNotExistAEFafiee3d";
+    private static final String PS_ERR_ACTION_PREF_EQ_STOP = "$ErrorActionPreference = \"Stop\"";
+
+    protected MachineProvisioningLocation<WinRmMachineLocation> loc;
     protected TestApplication app;
     protected ManagementContextInternal mgmt;
 
-    private JcloudsWinRmMachineLocation machine;
+    private WinRmMachineLocation machine;
+    
+    private ListeningExecutorService executor;
     
     @BeforeClass(alwaysRun=true)
     public void setUpClass() throws Exception {
+        executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(MAX_EXECUTOR_THREADS));
+        
         mgmt = new LocalManagementContextForTests(BrooklynProperties.Factory.newDefault());
-        JcloudsLocation loc = (JcloudsLocation) mgmt.getLocationRegistry().resolve("jclouds:aws-ec2:us-west-2", ImmutableMap.<String, Object>builder()
-                .put("inboundPorts", ImmutableList.of(5985, 3389))
-                .put("displayName", "AWS Oregon (Windows)")
-                .put("imageOwner", "801119661308")
-                .put("imageNameRegex", "Windows_Server-2012-R2_RTM-English-64Bit-Base-.*")
-                .put("hardwareId", "m3.medium")
-                .put("useJcloudsSshInit", false)
-                .build());
-        machine = (JcloudsWinRmMachineLocation) loc.obtain();
+        
+        loc = WindowsTestFixture.setUpWindowsLocation(mgmt);
+        machine = loc.obtain(ImmutableMap.of());
     }
-
+    
     @AfterClass(alwaysRun=true)
     public void tearDownClass() throws Exception {
         try {
+            if (executor != null) executor.shutdownNow();
             if (machine != null) loc.release(machine);
             if (mgmt != null) Entities.destroyAll(mgmt);
         } catch (Throwable t) {
             LOG.error("Caught exception in tearDown method", t);
         } finally {
+            executor = null;
             mgmt = null;
         }
     }
 
     @Test(groups="Live")
+    public void testCopyTo() throws Exception {
+        String contents = "abcdef";
+        runCopyTo(contents);
+        runCopyFileTo(contents);
+    }
+    
+    // Takes several minutes to upload/download!
+    @Test(groups="Live")
+    public void testLargeFileCopyTo() throws Exception {
+        String contents = Identifiers.makeRandomId(65537);
+        runCopyTo(contents);
+        runCopyFileTo(contents);
+    }
+    
+    protected void runCopyTo(String contents) throws Exception {
+        String remotePath = "C:\\myfile-"+Identifiers.makeRandomId(4)+".txt";
+        machine.copyTo(new ByteArrayInputStream(contents.getBytes()), remotePath);
+        
+        WinRmToolResponse response = machine.executeScript("type "+remotePath);
+        String msg = "statusCode="+response.getStatusCode()+"; out="+response.getStdOut()+"; err="+response.getStdErr();
+        assertEquals(response.getStatusCode(), 0, msg);
+        assertEquals(response.getStdOut().trim(), contents, msg);
+    }
+    
+    protected void runCopyFileTo(String contents) throws Exception {
+        File localFile = File.createTempFile("winrmtest"+Identifiers.makeRandomId(4), ".txt");
+        try {
+            Files.write(contents, localFile, Charsets.UTF_8);
+            String remotePath = "C:\\myfile-"+Identifiers.makeRandomId(4)+".txt";
+            machine.copyTo(localFile, remotePath);
+            
+            WinRmToolResponse response = machine.executeScript("type "+remotePath);
+            String msg = "statusCode="+response.getStatusCode()+"; out="+response.getStdOut()+"; err="+response.getStdErr();
+            assertEquals(response.getStatusCode(), 0, msg);
+            assertEquals(response.getStdOut().trim(), contents, msg);
+        } finally {
+            localFile.delete();
+        }
+    }
+    
+    @Test(groups="Live")
     public void testExecScript() throws Exception {
-        WinRmToolResponse response = machine.executeScript("echo true");
-        assertEquals(response.getStatusCode(), 0);
-        assertEquals(response.getStdErr(), "");
+        assertExecSucceeds("echo myline", "myline", "");
+    }
+    
+    /*
+     * TODO Not supported in PyWinRM.
+     * 
+     * Executing (in python):
+     *     import winrm
+     *     s = winrm.Session('52.12.211.247', auth=('Administrator', 'pa55w0rd'))
+     *     r = s.run_cmd("echo first \r\n echo second")
+     * gives just "first".
+     */
+    @Test(groups={"Live", "WIP"})
+    public void testExecMultiLineScript() throws Exception {
+        assertExecSucceeds("echo first" + "\r\n" + "echo second", "first"+"\r\n"+"second", "");
+    }
+    
+    /*
+     * TODO Not supported in PyWinRM. Under the covers, we just concatenate the commands.
+     * See {@link #testExecMultiLineScript()}.
+     */
+    @Test(groups={"Live", "WIP"})
+    public void testExecMultiPartScript() throws Exception {
+        assertExecSucceeds(ImmutableList.of("echo first", "echo second"), "first"+"\r\n"+"second", "");
+    }
+    
+    @Test(groups="Live")
+    public void testExecFailingScript() throws Exception {
+        final String INVALID_CMD = "thisCommandDoesNotExistAEFafiee3d";
+        
+        // Single commands
+        assertExecFails(INVALID_CMD);
+        assertExecFails(ImmutableList.of(INVALID_CMD));
+    }
+
+    @Test(groups="Live")
+    public void testExecScriptExit0() throws Exception {
+        assertExecSucceeds("exit /B 0", "", "");
+        assertExecSucceeds(ImmutableList.of("exit /B 0"), "", "");
+    }
+
+    /*
+     * TODO Not supported in PyWinRM.
+     * 
+     * Executing (in python):
+     *     import winrm
+     *     s = winrm.Session('52.12.211.247', auth=('Administrator', 'pa55w0rd'))
+     *     r = s.run_cmd("exit /B 1")
+     * gives exit code 0.
+     */
+    @Test(groups={"Live", "WIP"})
+    public void testExecScriptExit1() throws Exception {
+        // Single commands
+        assertExecFails("exit /B 1");
+        assertExecFails(ImmutableList.of("exit /B 1"));
+    }
+
+    @Test(groups="Live")
+    public void testExecBatchFileSingleLine() throws Exception {
+        String script = "EXIT /B 0";
+        String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".bat";
+        machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+        assertExecSucceeds(scriptPath, null, "");
+    }
+
+    @Test(groups="Live")
+    public void testExecBatchFileMultiLine() throws Exception {
+        String script = Joiner.on("\n").join(
+                "@ECHO OFF",
+                "echo first", 
+                "echo second", 
+                "EXIT /B 0");
+        String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".bat";
+        machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+        assertExecSucceeds(scriptPath, "first"+"\r\n"+"second", "");
+    }
+
+    @Test(groups="Live")
+    public void testExecBatchFileWithArgs() throws Exception {
+        String script = Joiner.on("\n").join(
+                "@ECHO OFF",
+                "echo got %1", 
+                "echo got %2", 
+                "EXIT /B 0");
+        String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".bat";
+        machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+        assertExecSucceeds(scriptPath+" first second", "got first"+"\r\n"+"got second", "");
+    }
+
+    @Test(groups="Live")
+    public void testExecBatchFileWithExit1() throws Exception {
+        String script = "EXIT /B 1";
+        String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".bat";
+        machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+        assertExecFails(scriptPath);
+    }
+
+    @Test(groups="Live")
+    public void testExecCorruptExe() throws Exception {
+        String exe = "garbage";
+        String exePath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".exe";
+        machine.copyTo(new ByteArrayInputStream(exe.getBytes()), exePath);
+
+        assertExecFails(exePath);
+    }
+
+    @Test(groups="Live")
+    public void testExecFilePs() throws Exception {
+        String script = Joiner.on("\r\n").join(
+                "Write-Host myline", 
+                "exit 0");
+        String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".ps1";
+        machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+        assertExecPsSucceeds(
+                "PowerShell -NonInteractive -NoProfile -Command "+scriptPath,
+                "myline",
+                "");
+    }
+
+    @Test(groups="Live")
+    public void testExecFilePsWithExit1() throws Exception {
+        String script = Joiner.on("\r\n").join(
+                "Write-Host myline", 
+                "exit 1");
+        String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".ps1";
+        machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+        assertExecFails("PowerShell -NonInteractive -NoProfile -Command "+scriptPath);
+    }
+
+    /*
+     * TODO Not supported in PyWinRM - single line .ps1 file with "exit 1" gives an
+     * exit code 0 over PyWinRM, but an exit code 1 when executed locally!
+     * 
+     * Executing (in python):
+     *     import winrm
+     *     s = winrm.Session('52.12.211.247', auth=('Administrator', 'pa55w0rd'))
+     *     r = s.run_cmd("PowerShell -NonInteractive -NoProfile -Command C:\singleLineExit1.ps1")
+     * gives exit code 0.
+     */
+    @Test(groups={"Live", "WIP"})
+    public void testExecFilePsWithSingleLineExit1() throws Exception {
+        String script = "exit 1";
+        String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".ps1";
+        machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+        assertExecFails("PowerShell -NonInteractive -NoProfile -Command "+scriptPath);
+    }
+
+    @Test(groups="Live")
+    public void testExecPsScript() throws Exception {
+        assertExecPsSucceeds("Write-Host myline", "myline", "");
+    }
+    
+    @Test(groups="Live")
+    public void testExecPsMultiLineScript() throws Exception {
+        // Note stdout is "\n" rather than "\r\n" (the latter is returned for run_cmd, versus run_ps)
+        assertExecPsSucceeds("Write-Host first" + "\r\n" + "Write-Host second", "first"+"\n"+"second", "");
+    }
+    
+    @Test(groups="Live")
+    public void testExecPsMultiLineScriptWithoutSlashR() throws Exception {
+        assertExecPsSucceeds("Write-Host first" + "\n" + "Write-Host second", "first"+"\n"+"second", "");
+    }
+    
+    @Test(groups="Live")
+    public void testExecPsMultiPartScript() throws Exception {
+        assertExecPsSucceeds(ImmutableList.of("Write-Host first", "Write-Host second"), "first"+"\n"+"second", "");
+    }
+
+    @Test(groups="Live")
+    public void testExecPsBatchFile() throws Exception {
+        String script = "EXIT /B 0";
+        String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".bat";
+        machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+        assertExecPsSucceeds("& '"+scriptPath+"'", null, "");
+    }
+    
+    @Test(groups="Live")
+    public void testExecPsBatchFileExit1() throws Exception {
+        String script = "EXIT /B 1";
+        String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".bat";
+        machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+        assertExecPsFails("& '"+scriptPath+"'");
+    }
+
+    /*
+     * TODO Not supported in PyWinRM - gives exit status 1, rather than the 3 from the batch file.
+     */
+    @Test(groups={"Live", "WIP"})
+    public void testExecPsBatchFileExit3() throws Exception {
+        String script = "EXIT /B 3";
+        String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".bat";
+        machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+        WinRmToolResponse response = machine.executePsScript("& '"+scriptPath+"'");
+        String msg = "statusCode="+response.getStatusCode()+"; out="+response.getStdOut()+"; err="+response.getStdErr();
+        assertEquals(response.getStatusCode(), 3, msg);
+    }
+
+    @Test(groups="Live")
+    public void testExecPsCorruptExe() throws Exception {
+        String exe = "garbage";
+        String exePath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".exe";
+        machine.copyTo(new ByteArrayInputStream(exe.getBytes()), exePath);
+
+        assertExecPsFails("& '"+exePath+"'");
+    }
+
+    @Test(groups="Live")
+    public void testExecPsFileWithArg() throws Exception {
+        String script = Joiner.on("\r\n").join(
+                "Param(",
+                "  [string]$myarg",
+                ")",
+                "Write-Host got $myarg", 
+                "exit 0");
+        String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".ps1";
+        machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+        assertExecPsSucceeds("& "+scriptPath+" -myarg myval", "got myval", "");
+    }
+
+    @Test(groups="Live")
+    public void testExecPsFilePs() throws Exception {
+        String script = Joiner.on("\r\n").join(
+                "Write-Host myline", 
+                "exit 0");
+        String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".ps1";
+        machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+        assertExecPsSucceeds("& "+scriptPath, "myline", "");
+    }
+
+    @Test(groups="Live")
+    public void testExecPsFilePsWithExit1() throws Exception {
+        String script = Joiner.on("\r\n").join(
+                "Write-Host myline", 
+                "exit 1");
+        String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".ps1";
+        machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+        System.out.println(scriptPath);
+
+        assertExecPsFails("& "+scriptPath);
+    }
+
+    /*
+     * TODO Not supported in PyWinRM - single line .ps1 file with "exit 1" gives an
+     * exit code 0 over PyWinRM, but an exit code 1 when executed locally!
+     * 
+     * Executing (in python):
+     *     import winrm
+     *     s = winrm.Session('52.12.211.247', auth=('Administrator', 'pa55w0rd'))
+     *     r = s.run_cmd("PowerShell -NonInteractive -NoProfile -Command C:\singleLineExit1.ps1")
+     * gives exit code 0.
+     */
+    @Test(groups={"Live", "WIP"})
+    public void testExecPsFilePsSingleLineWithExit1() throws Exception {
+        String script = "exit 1";
+        String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".ps1";
+        machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+        assertExecPsFails("& "+scriptPath);
+    }
+
+    /*
+     * TODO Not supported in PyWinRM - single line .ps1 file with "exit 1" gives an
+     * exit code 0 over PyWinRM, but an exit code 1 when executed locally!
+     * 
+     * Executing (in python):
+     *     import winrm
+     *     s = winrm.Session('52.12.211.247', auth=('Administrator', 'pa55w0rd'))
+     *     r = s.run_cmd("PowerShell -NonInteractive -NoProfile -Command C:\singleLineGarbage.ps1")
+     * gives exit code 0.
+     */
+    @Test(groups={"Live", "WIP"})
+    public void testExecPsFilePsSingleLineWithInvalidCommand() throws Exception {
+        String script = INVALID_CMD;
+        String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".ps1";
+        machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+        assertExecPsFails("& "+scriptPath);
+    }
+
+    @Test(groups="Live")
+    public void testConfirmUseOfErrorActionPreferenceDoesNotCauseErr() throws Exception {
+        // Confirm that ErrorActionPreference=Stop does not itself cause a failure, and still get output on success.
+        assertExecPsSucceeds(ImmutableList.of(PS_ERR_ACTION_PREF_EQ_STOP, "Write-Host myline"), "myline", "");
+    }
+
+    /*
+     * TODO Not supported in PyWinRM
+     * 
+     * Executing (in python):
+     *     import winrm
+     *     s = winrm.Session('52.12.211.247', auth=('Administrator', 'pa55w0rd'))
+     *     r = s.run_ps("exit 1")
+     * gives exit code 0.
+     */
+    @Test(groups={"Live", "WIP"})
+    public void testExecPsExit1() throws Exception {
+        // Single commands
+        assertExecPsFails("exit 1");
+        assertExecPsFails(ImmutableList.of("exit 1"));
+        
+        // Multi-part
+        assertExecPsFails(ImmutableList.of(PS_ERR_ACTION_PREF_EQ_STOP, "Write-Host myline", "exit 1"));
+        
+        // Multi-line
+        assertExecPsFails(PS_ERR_ACTION_PREF_EQ_STOP + "\n" + "Write-Host myline" + "\n" + "exit 1");
+    }
+
+    @Test(groups="Live")
+    public void testExecFailingPsScript() throws Exception {
+        // Single commands
+        assertExecPsFails(INVALID_CMD);
+        assertExecPsFails(ImmutableList.of(INVALID_CMD));
+        
+        // Multi-part commands
+        assertExecPsFails(ImmutableList.of(PS_ERR_ACTION_PREF_EQ_STOP, "Write-Host myline", INVALID_CMD));
+        assertExecPsFails(ImmutableList.of(PS_ERR_ACTION_PREF_EQ_STOP, INVALID_CMD, "Write-Host myline"));
+    }
+
+    @Test(groups={"Live", "Acceptance"})
+    public void testExecConcurrently() throws Exception {
+        final int NUM_RUNS = 10;
+        final int TIMEOUT_MINS = 30;
+        final AtomicInteger counter = new AtomicInteger();
+        
+        // Find the test methods that are enabled, and that are not WIP 
+        List<Method> methodsToRun = Lists.newArrayList();
+        Method[] allmethods = WinRmMachineLocationLiveTest.class.getMethods();
+        for (Method method : allmethods) {
+            Test annotatn = method.getAnnotation(Test.class);
+            if (method.getParameterTypes().length != 0) {
+                continue;
+            }
+            if (method.getName().equals("testExecConcurrently")) {
+                continue;
+            }
+            if (annotatn == null || !annotatn.enabled()) {
+                continue;
+            }
+            String[] groups = annotatn.groups();
+            if (groups != null && Arrays.asList(groups).contains("WIP")) {
+                continue;
+            }
+            methodsToRun.add(method);
+        }
+
+        // Execute all the methods many times
+        LOG.info("Executing "+methodsToRun.size()+" methods "+NUM_RUNS+" times each, with "+MAX_EXECUTOR_THREADS+" threads for concurrent execution; max permitted time "+TIMEOUT_MINS+"mins; methods="+methodsToRun);
+        
+        List<ListenableFuture<?>> results = Lists.newArrayList();
+        for (int i = 0; i < NUM_RUNS; i++) {
+            for (final Method method : methodsToRun) {
+                results.add(executor.submit(new Callable<Void>() {
+                    public Void call() throws Exception {
+                        LOG.info("Executing "+method.getName()+" in thread "+Thread.currentThread());
+                        Stopwatch stopwatch = Stopwatch.createStarted();
+                        try {
+                            method.invoke(WinRmMachineLocationLiveTest.this);
+                            LOG.info("Executed "+method.getName()+" in "+Time.makeTimeStringRounded(stopwatch)+", in thread "+Thread.currentThread()+"; total "+counter.incrementAndGet()+" methods done");
+                            return null;
+                        } catch (Exception e) {
+                            LOG.error("Execute failed for "+method.getName()+" after "+Time.makeTimeStringRounded(stopwatch)+", in thread "+Thread.currentThread()+"; total "+counter.incrementAndGet()+" methods done");
+                            throw e;
+                        }
+                    }}));
+            }
+        }
+        
+        Futures.allAsList(results).get(TIMEOUT_MINS, TimeUnit.MINUTES);
+    }
+
+    private void assertExecFails(String cmd) {
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        assertFailed(cmd, machine.executeScript(cmd), stopwatch);
+    }
+
+    private void assertExecFails(List<String> cmds) {
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        assertFailed(cmds, machine.executeScript(cmds), stopwatch);
+    }
+    
+    private void assertExecPsFails(String cmd) {
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        assertFailed(cmd, machine.executePsScript(cmd), stopwatch);
+    }
+    
+    private void assertExecPsFails(List<String> cmds) {
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        assertFailed(cmds, machine.executePsScript(cmds), stopwatch);
+    }
+
+    private void assertExecSucceeds(String cmd, String stdout, String stderr) {
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        assertSucceeded(cmd, machine.executeScript(cmd), stdout, stderr, stopwatch);
+    }
+
+    private void assertExecSucceeds(List<String> cmds, String stdout, String stderr) {
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        assertSucceeded(cmds, machine.executeScript(cmds), stdout, stderr, stopwatch);
+    }
+
+    private void assertExecPsSucceeds(String cmd, String stdout, String stderr) {
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        assertSucceeded(cmd, machine.executePsScript(cmd), stdout, stderr, stopwatch);
+    }
+
+    private void assertExecPsSucceeds(List<String> cmds, String stdout, String stderr) {
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        assertSucceeded(cmds, machine.executePsScript(cmds), stdout, stderr, stopwatch);
+    }
+
+    private void assertFailed(Object cmd, WinRmToolResponse response, Stopwatch stopwatch) {
+        String msg = "statusCode="+response.getStatusCode()+"; out="+response.getStdOut()+"; err="+response.getStdErr();
+        LOG.info("Executed in "+Time.makeTimeStringRounded(stopwatch)+" (asserting failed): "+msg+"; cmd="+cmd);
+        assertNotEquals(response.getStatusCode(), 0, msg);
+    }
+    
+    private WinRmToolResponse assertSucceeded(Object cmd, WinRmToolResponse response, String stdout, String stderr, Stopwatch stopwatch) {
+        String msg = "statusCode="+response.getStatusCode()+"; out="+response.getStdOut()+"; err="+response.getStdErr();
+        LOG.info("Executed in "+Time.makeTimeStringRounded(stopwatch)+" (asserting success): "+msg+"; cmd="+cmd);
+        assertEquals(response.getStatusCode(), 0, msg);
+        if (stdout != null) assertEquals(response.getStdOut().trim(), stdout, msg);
+        if (stderr != null) assertEquals(response.getStdErr().trim(), stderr, msg);
+        return response;
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/10071a92/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WindowsTestFixture.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WindowsTestFixture.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WindowsTestFixture.java
new file mode 100644
index 0000000..10aa4b7
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WindowsTestFixture.java
@@ -0,0 +1,71 @@
+/*
+ * 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.test.location;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.location.jclouds.JcloudsLocation;
+import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class WindowsTestFixture {
+
+    @SuppressWarnings("unchecked")
+    public static MachineProvisioningLocation<WinRmMachineLocation> setUpWindowsLocation(ManagementContext mgmt) throws Exception {
+        // Commented out / unused code included here to make it easy to supply a 
+        // pre-existing Windows VM for use in a bunch of different tests.
+//        return (MachineProvisioningLocation<WinRmMachineLocation>) newByonLocation((ManagementContextInternal) mgmt);
+        return (MachineProvisioningLocation<WinRmMachineLocation>) newJcloudsLocation((ManagementContextInternal) mgmt);
+    }
+    
+    private static MachineProvisioningLocation<?> newJcloudsLocation(ManagementContextInternal mgmt) {
+        // Requires no userMetadata to be set, so that we use WinRmMachineLocation.getDefaultUserMetadataString()
+        mgmt.getBrooklynProperties().remove("brooklyn.location.jclouds.aws-ec2.userMetadata");
+        mgmt.getBrooklynProperties().remove("brooklyn.location.jclouds.userMetadata");
+        mgmt.getBrooklynProperties().remove("brooklyn.location.userMetadata");
+        
+        return (JcloudsLocation) mgmt.getLocationRegistry().resolve("jclouds:aws-ec2:us-west-2", ImmutableMap.<String, Object>builder()
+                .put("inboundPorts", ImmutableList.of(5985, 3389))
+                .put("displayName", "AWS Oregon (Windows)")
+                .put("imageOwner", "801119661308")
+                .put("imageNameRegex", "Windows_Server-2012-R2_RTM-English-64Bit-Base-.*")
+                .put("hardwareId", "m3.medium")
+                .put("useJcloudsSshInit", false)
+                .build());
+    }
+    
+    @SuppressWarnings("unused")
+    private static MachineProvisioningLocation<?> newByonLocation(ManagementContextInternal mgmt) {
+        Map<String, String> config = new LinkedHashMap<>();
+        config.put("hosts", "52.12.211.123:5985");
+        config.put("osFamily", "windows");
+        config.put("winrm", "52.12.211.123:5985");
+        config.put("user", "Administrator");
+        config.put("password", "pa55w0rd");
+        config.put("useJcloudsSshInit", "false");
+        config.put("byonIdentity", "123");
+        return (MachineProvisioningLocation<?>) mgmt.getLocationRegistry().resolve("byon", config);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/10071a92/usage/camp/pom.xml
----------------------------------------------------------------------
diff --git a/usage/camp/pom.xml b/usage/camp/pom.xml
index 911ff6a..fb5d7ee 100644
--- a/usage/camp/pom.xml
+++ b/usage/camp/pom.xml
@@ -157,6 +157,12 @@
         </dependency>
         <dependency>
             <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-locations-jclouds</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
             <artifactId>brooklyn-test-support</artifactId>
             <version>${project.version}</version>
             <scope>test</scope>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/10071a92/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/WindowsYamlLiveTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/WindowsYamlLiveTest.java b/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/WindowsYamlLiveTest.java
new file mode 100644
index 0000000..15626db
--- /dev/null
+++ b/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/WindowsYamlLiveTest.java
@@ -0,0 +1,410 @@
+/*
+ * 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 static com.google.common.base.Preconditions.checkNotNull;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.io.StringReader;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.api.mgmt.HasTaskChildren;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+import org.apache.brooklyn.entity.software.base.VanillaWindowsProcess;
+import org.apache.brooklyn.entity.software.base.test.location.WindowsTestFixture;
+import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.core.task.TaskPredicates;
+import org.apache.brooklyn.util.text.StringPredicates;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+
+/**
+ * Tests Windows YAML blueprint features.
+ */
+@Test
+public class WindowsYamlLiveTest extends AbstractYamlTest {
+    
+    // TODO Remove duplication of assertStreams and VanillaWindowsProcessWinrmStreamsLiveTest.assertStreams
+    
+    private static final Logger log = LoggerFactory.getLogger(WindowsYamlLiveTest.class);
+
+    /**
+     * Maps from the task names that are used to the names used in log/exception messages.
+     */
+    private static final Map<String, String> TASK_REGEX_TO_COMMAND = ImmutableMap.<String, String>builder()
+            .put("winrm: pre-install-command.*", "pre-install-command")
+            .put("winrm: install.*", "install-command")
+            .put("winrm: post-install-command.*", "post-install-command")
+            .put("winrm: customize.*", "customize-command")
+            .put("winrm: pre-launch-command.*", "pre-launch-command")
+            .put("winrm: launch.*", "launch-command")
+            .put("winrm: post-launch-command.*", "post-launch-command")
+            .put("winrm: stop-command.*", "stop-command")
+            .put("winrm: is-running-command.*", "is-running-command")
+            .build();
+
+    protected List<String> yamlLocation;
+    protected MachineProvisioningLocation<WinRmMachineLocation> location;
+    protected WinRmMachineLocation machine;
+    protected Entity app;
+    
+    @BeforeClass(alwaysRun = true)
+    public void setUpClass() throws Exception {
+        super.setUp();
+        
+        location = WindowsTestFixture.setUpWindowsLocation(mgmt());
+        machine = (WinRmMachineLocation) location.obtain(ImmutableMap.of());
+        String ip = machine.getAddress().getHostAddress();
+        String password = machine.config().get(WinRmMachineLocation.PASSWORD);
+
+        yamlLocation = ImmutableList.of(
+                "location:",
+                "  byon:",
+                "    hosts:",
+                "    - winrm: "+ip+":5985",
+                "      password: "+password,
+                "      user: Administrator",
+                "      osFamily: windows");
+    }
+
+    @AfterClass(alwaysRun = true)
+    public void tearDownClass() {
+        try {
+            if (location != null) location.release(machine);
+        } catch (Throwable t) {
+            log.error("Caught exception in tearDownClass method", t);
+        } finally {
+            super.tearDown();
+        }
+    }
+
+    @BeforeMethod(alwaysRun = true)
+    @Override
+    public void setUp() {
+        // no-op; everything done @BeforeClass
+    }
+
+    @AfterMethod(alwaysRun = true)
+    @Override
+    public void tearDown() {
+        try {
+            if (app != null) Entities.destroy(app);
+        } catch (Throwable t) {
+            log.error("Caught exception in tearDown method", t);
+        } finally {
+            app = null;
+        }
+    }
+    
+    @Override
+    protected ManagementContextInternal mgmt() {
+        return (ManagementContextInternal) super.mgmt();
+    }
+    
+    @Test(groups="Live")
+    public void testPowershellMinimalist() throws Exception {
+        Map<String, String> cmds = ImmutableMap.<String, String>builder()
+                .put("myarg", "myval")
+                .put("launch.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .put("checkRunning.powershell.command", "\"& c:\\\\exit0.bat\"")
+                .build();
+        
+        Map<String, List<String>> stdouts = ImmutableMap.of();
+        
+        runWindowsApp(cmds, stdouts, null);
+    }
+    
+    @Test(groups="Live")
+    public void testPowershell() throws Exception {
+        Map<String, String> cmds = ImmutableMap.<String, String>builder()
+                .put("myarg", "myval")
+                .put("pre.install.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .put("install.powershell.command", "\"& c:\\\\echoMyArg.ps1 -myarg myInstall\"")
+                .put("post.install.powershell.command", "\"& c:\\\\echoArg.bat myPostInstall\"")
+                .put("customize.powershell.command", "\"& c:\\\\echoFreemarkerMyarg.bat\"")
+                .put("pre.launch.powershell.command", "\"& c:\\\\echoFreemarkerMyarg.ps1\"")
+                .put("launch.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .put("post.launch.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .put("checkRunning.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .put("stop.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .build();
+        
+        Map<String, List<String>> stdouts = ImmutableMap.<String, List<String>>builder()
+                .put("winrm: install.*", ImmutableList.of("myInstall"))
+                .put("winrm: post-install-command.*", ImmutableList.of("myPostInstall"))
+                .put("winrm: customize.*", ImmutableList.of("myval"))
+                .put("winrm: pre-launch-command.*", ImmutableList.of("myval"))
+                .build();
+        
+        runWindowsApp(cmds, stdouts, null);
+    }
+    
+    @Test(groups="Live")
+    public void testBatch() throws Exception {
+        Map<String, String> cmds = ImmutableMap.<String, String>builder()
+                .put("myarg", "myval")
+                .put("pre.install.command", "\"PowerShell -NonInteractive -NoProfile -Command c:\\\\exit0.ps1\"")
+                .put("install.command", "\"PowerShell -NonInteractive -NoProfile -Command c:\\\\echoMyArg.ps1 -myarg myInstall\"")
+                .put("post.install.command", "\"c:\\\\echoArg.bat myPostInstall\"")
+                .put("customize.command", "\"c:\\\\echoFreemarkerMyarg.bat\"")
+                .put("pre.launch.command", "\"PowerShell -NonInteractive -NoProfile -Command c:\\\\echoFreemarkerMyarg.ps1\"")
+                .put("launch.command", "\"PowerShell -NonInteractive -NoProfile -Command c:\\\\exit0.ps1\"")
+                .put("post.launch.command", "\"PowerShell -NonInteractive -NoProfile -Command c:\\\\exit0.ps1\"")
+                .put("checkRunning.command", "\"PowerShell -NonInteractive -NoProfile -Command c:\\\\exit0.ps1\"")
+                .put("stop.command", "\"PowerShell -NonInteractive -NoProfile -Command c:\\\\exit0.ps1\"")
+                .build();
+
+        Map<String, List<String>> stdouts = ImmutableMap.<String, List<String>>builder()
+                .put("winrm: install.*", ImmutableList.of("myInstall"))
+                .put("winrm: post-install-command.*", ImmutableList.of("myPostInstall"))
+                .put("winrm: customize.*", ImmutableList.of("myval"))
+                .put("winrm: pre-launch-command.*", ImmutableList.of("myval"))
+                .build();
+        
+        runWindowsApp(cmds, stdouts, null);
+    }
+    
+    @Test(groups="Live")
+    public void testPowershellExit1() throws Exception {
+        Map<String, String> cmds = ImmutableMap.<String, String>builder()
+                .put("myarg", "myval")
+                .put("pre.install.powershell.command", "\"& c:\\\\exit1.ps1\"")
+                .put("install.powershell.command", "\"& c:\\\\echoMyArg.ps1 -myarg myInstall\"")
+                .put("post.install.powershell.command", "\"& c:\\\\echoArg.bat myPostInstall\"")
+                .put("customize.powershell.command", "\"& c:\\\\echoFreemarkerMyarg.bat\"")
+                .put("pre.launch.powershell.command", "\"& c:\\\\echoFreemarkerMyarg.ps1\"")
+                .put("launch.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .put("post.launch.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .put("checkRunning.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .put("stop.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .build();
+        
+        Map<String, List<String>> stdouts = ImmutableMap.of();
+        
+        runWindowsApp(cmds, stdouts, "winrm: pre-install-command.*");
+    }
+    
+    // FIXME Failing to match the expected exception, but looks fine! Needs more investigation.
+    @Test(groups="Live")
+    public void testPowershellCheckRunningExit1() throws Exception {
+        Map<String, String> cmds = ImmutableMap.<String, String>builder()
+                .put("myarg", "myval")
+                .put("pre.install.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .put("install.powershell.command", "\"& c:\\\\echoMyArg.ps1 -myarg myInstall\"")
+                .put("post.install.powershell.command", "\"& c:\\\\echoArg.bat myPostInstall\"")
+                .put("customize.powershell.command", "\"& c:\\\\echoFreemarkerMyarg.bat\"")
+                .put("pre.launch.powershell.command", "\"& c:\\\\echoFreemarkerMyarg.ps1\"")
+                .put("launch.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .put("post.launch.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .put("checkRunning.powershell.command", "\"& c:\\\\exit1.ps1\"")
+                .put("stop.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .build();
+        
+        Map<String, List<String>> stdouts = ImmutableMap.of();
+        
+        runWindowsApp(cmds, stdouts, "winrm: is-running-command.*");
+    }
+    
+    // FIXME Needs more work to get the stop's task that failed, so can assert got the right error message
+    @Test(groups="Live")
+    public void testPowershellStopExit1() throws Exception {
+        Map<String, String> cmds = ImmutableMap.<String, String>builder()
+                .put("myarg", "myval")
+                .put("pre.install.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .put("install.powershell.command", "\"& c:\\\\echoMyArg.ps1 -myarg myInstall\"")
+                .put("post.install.powershell.command", "\"& c:\\\\echoArg.bat myPostInstall\"")
+                .put("customize.powershell.command", "\"& c:\\\\echoFreemarkerMyarg.bat\"")
+                .put("pre.launch.powershell.command", "\"& c:\\\\echoFreemarkerMyarg.ps1\"")
+                .put("launch.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .put("post.launch.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .put("checkRunning.powershell.command", "\"& c:\\\\exit0.ps1\"")
+                .put("stop.powershell.command", "\"& c:\\\\exit1.ps1\"")
+                .build();
+        
+        Map<String, List<String>> stdouts = ImmutableMap.of();
+        
+        runWindowsApp(cmds, stdouts, "winrm: stop-command.*");
+    }
+    
+    protected void runWindowsApp(Map<String, String> commands, Map<String, List<String>> stdouts, String taskRegexFailed) throws Exception {
+        String cmdFailed = (taskRegexFailed == null) ? null : TASK_REGEX_TO_COMMAND.get(taskRegexFailed);
+        
+        List<String> yaml = Lists.newArrayList();
+        yaml.addAll(yamlLocation);
+        yaml.addAll(ImmutableList.of(
+                "services:",
+                "- type: org.apache.brooklyn.entity.software.base.VanillaWindowsProcess",
+                "  brooklyn.config:",
+                "    onbox.base.dir.skipResolution: true",
+                "    templates.preinstall:",
+                "      classpath://org/apache/brooklyn/camp/brooklyn/echoFreemarkerMyarg.bat: c:\\echoFreemarkerMyarg.bat",
+                "      classpath://org/apache/brooklyn/camp/brooklyn/echoFreemarkerMyarg.ps1: c:\\echoFreemarkerMyarg.ps1",
+                "    files.preinstall:",
+                "      classpath://org/apache/brooklyn/camp/brooklyn/echoArg.bat: c:\\echoArg.bat",
+                "      classpath://org/apache/brooklyn/camp/brooklyn/echoMyArg.ps1: c:\\echoMyArg.ps1",
+                "      classpath://org/apache/brooklyn/camp/brooklyn/exit0.bat: c:\\exit0.bat",
+                "      classpath://org/apache/brooklyn/camp/brooklyn/exit1.bat: c:\\exit1.bat",
+                "      classpath://org/apache/brooklyn/camp/brooklyn/exit0.ps1: c:\\exit0.ps1",
+                "      classpath://org/apache/brooklyn/camp/brooklyn/exit1.ps1: c:\\exit1.ps1",
+                ""));
+        
+        for (Map.Entry<String, String> entry : commands.entrySet()) {
+            yaml.add("    "+entry.getKey()+": "+entry.getValue());
+        }
+
+        if (Strings.isBlank(cmdFailed)) {
+            app = createAndStartApplication(new StringReader(Joiner.on("\n").join(yaml)));
+            waitForApplicationTasks(app);
+            log.info("App started:");
+            Entities.dumpInfo(app);
+            
+            VanillaWindowsProcess entity = (VanillaWindowsProcess) app.getChildren().iterator().next();
+            
+            EntityTestUtils.assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true);
+            assertStreams(entity, stdouts);
+            
+        } else if (cmdFailed.equals("stop-command")) {
+            app = createAndStartApplication(new StringReader(Joiner.on("\n").join(yaml)));
+            waitForApplicationTasks(app);
+            log.info("App started:");
+            Entities.dumpInfo(app);
+            VanillaWindowsProcess entity = (VanillaWindowsProcess) app.getChildren().iterator().next();
+            EntityTestUtils.assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true);
+            
+            entity.stop();
+            assertSubTaskFailures(entity, ImmutableMap.of(taskRegexFailed, StringPredicates.containsLiteral("for "+cmdFailed)));
+            
+        } else {
+            try {
+                app = createAndStartApplication(new StringReader(Joiner.on("\n").join(yaml)));
+                fail("start should have failed for app="+app);
+            } catch (Exception e) {
+                if (e.toString().contains("invalid result") && e.toString().contains("for "+cmdFailed)) throw e;
+            }
+        }
+    }
+
+    protected void assertStreams(SoftwareProcess entity, Map<String, List<String>> stdouts) {
+        Set<Task<?>> tasks = BrooklynTaskTags.getTasksInEntityContext(mgmt().getExecutionManager(), entity);
+
+        for (Map.Entry<String, List<String>> entry : stdouts.entrySet()) {
+            String taskNameRegex = entry.getKey();
+            List<String> expectedOuts = entry.getValue();
+
+            Task<?> subTask = findTaskOrSubTask(tasks, TaskPredicates.displayNameMatches(StringPredicates.matchesRegex(taskNameRegex))).get();
+
+            String stdin = getStreamOrFail(subTask, BrooklynTaskTags.STREAM_STDIN);
+            String stdout = getStreamOrFail(subTask, BrooklynTaskTags.STREAM_STDOUT);
+            String stderr = getStreamOrFail(subTask, BrooklynTaskTags.STREAM_STDERR);
+            String env = getStream(subTask, BrooklynTaskTags.STREAM_ENV);
+            String msg = "stdin="+stdin+"; stdout="+stdout+"; stderr="+stderr+"; env="+env;
+
+            for (String expectedOut : expectedOuts) {
+                assertTrue(stdout.contains(expectedOut), msg);
+            }
+        }
+    }
+
+    protected void assertSubTaskFailures(SoftwareProcess entity, Map<String, Predicate<CharSequence>> taskErrs) throws Exception {
+        Set<Task<?>> tasks = BrooklynTaskTags.getTasksInEntityContext(mgmt().getExecutionManager(), entity);
+
+        for (Map.Entry<String, Predicate<CharSequence>> entry : taskErrs.entrySet()) {
+            String taskNameRegex = entry.getKey();
+            Predicate<? super String> errChecker = entry.getValue();
+            Task<?> subTask = findTaskOrSubTask(tasks, TaskPredicates.displayNameMatches(StringPredicates.matchesRegex(taskNameRegex))).get();
+            String msg = "regex="+taskNameRegex+"; task="+subTask;
+            assertNotNull(subTask, msg);
+            assertTrue(subTask.isDone(), msg);
+            assertTrue(subTask.isError(), msg);
+            try {
+                subTask.get();
+                fail();
+            } catch (Exception e) {
+                if (!errChecker.apply(e.toString())) {
+                    throw e;
+                }
+            }
+        }
+    }
+
+    public static String getStreamOrFail(Task<?> task, String streamType) {
+        String msg = "task="+task+"; stream="+streamType;
+        BrooklynTaskTags.WrappedStream stream = checkNotNull(BrooklynTaskTags.stream(task, streamType), "Stream null: " + msg);
+        return checkNotNull(stream.streamContents.get(), "Contents null: "+msg);
+    }
+
+    public static String getStream(Task<?> task, String streamType) {
+        BrooklynTaskTags.WrappedStream stream = BrooklynTaskTags.stream(task, streamType);
+        return (stream != null) ? stream.streamContents.get() : null;
+    }
+
+    protected Optional<Task<?>> findTaskOrSubTask(Iterable<? extends Task<?>> tasks, Predicate<? super Task<?>> matcher) {
+        List<String> taskNames = Lists.newArrayList();
+        Optional<Task<?>> result = findTaskOrSubTaskImpl(tasks, matcher, taskNames);
+        if (!result.isPresent() && log.isDebugEnabled()) {
+            log.debug("Task not found matching "+matcher+"; contender names were "+taskNames);
+        }
+        return result;
+    }
+
+    protected Optional<Task<?>> findTaskOrSubTaskImpl(Iterable<? extends Task<?>> tasks, Predicate<? super Task<?>> matcher, List<String> taskNames) {
+        for (Task<?> task : tasks) {
+            if (matcher.apply(task)) return Optional.<Task<?>>of(task);
+
+            if (!(task instanceof HasTaskChildren)) {
+                return Optional.absent();
+            } else {
+                Optional<Task<?>> subResult = findTaskOrSubTask(((HasTaskChildren) task).getChildren(), matcher);
+                if (subResult.isPresent()) return subResult;
+            }
+        }
+
+        return Optional.<Task<?>>absent();
+    }
+
+    @Override
+    protected Logger getLogger() {
+        return log;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/10071a92/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/echoArg.bat
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/echoArg.bat b/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/echoArg.bat
new file mode 100644
index 0000000..dd77ba6
--- /dev/null
+++ b/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/echoArg.bat
@@ -0,0 +1,19 @@
+REM Licensed to the Apache Software Foundation (ASF) under one
+REM or more contributor license agreements.  See the NOTICE file
+REM distributed with this work for additional information
+REM regarding copyright ownership.  The ASF licenses this file
+REM to you under the Apache License, Version 2.0 (the
+REM "License"); you may not use this file except in compliance
+REM with 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,
+REM software distributed under the License is distributed on an
+REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+REM KIND, either express or implied.  See the License for the
+REM specific language governing permissions and limitations
+REM under the License.
+
+@ECHO OFF
+echo %1
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/10071a92/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/echoFreemarkerMyarg.bat
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/echoFreemarkerMyarg.bat b/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/echoFreemarkerMyarg.bat
new file mode 100644
index 0000000..33f7b7e
--- /dev/null
+++ b/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/echoFreemarkerMyarg.bat
@@ -0,0 +1,18 @@
+REM Licensed to the Apache Software Foundation (ASF) under one
+REM or more contributor license agreements.  See the NOTICE file
+REM distributed with this work for additional information
+REM regarding copyright ownership.  The ASF licenses this file
+REM to you under the Apache License, Version 2.0 (the
+REM "License"); you may not use this file except in compliance
+REM with 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,
+REM software distributed under the License is distributed on an
+REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+REM KIND, either express or implied.  See the License for the
+REM specific language governing permissions and limitations
+REM under the License.
+
+echo ${config['myarg']}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/10071a92/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/echoFreemarkerMyarg.ps1
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/echoFreemarkerMyarg.ps1 b/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/echoFreemarkerMyarg.ps1
new file mode 100644
index 0000000..c2535ea
--- /dev/null
+++ b/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/echoFreemarkerMyarg.ps1
@@ -0,0 +1,18 @@
+# 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.
+
+Write-Host ${config['myarg']}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/10071a92/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/echoMyArg.ps1
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/echoMyArg.ps1 b/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/echoMyArg.ps1
new file mode 100644
index 0000000..3a2d33d
--- /dev/null
+++ b/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/echoMyArg.ps1
@@ -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.
+
+param(
+  [string]$myarg
+)
+
+Write-Host $myarg 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/10071a92/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/exit0.bat
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/exit0.bat b/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/exit0.bat
new file mode 100644
index 0000000..2a926b6
--- /dev/null
+++ b/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/exit0.bat
@@ -0,0 +1,18 @@
+REM Licensed to the Apache Software Foundation (ASF) under one
+REM or more contributor license agreements.  See the NOTICE file
+REM distributed with this work for additional information
+REM regarding copyright ownership.  The ASF licenses this file
+REM to you under the Apache License, Version 2.0 (the
+REM "License"); you may not use this file except in compliance
+REM with 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,
+REM software distributed under the License is distributed on an
+REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+REM KIND, either express or implied.  See the License for the
+REM specific language governing permissions and limitations
+REM under the License.
+
+EXIT /B 0
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/10071a92/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/exit0.ps1
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/exit0.ps1 b/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/exit0.ps1
new file mode 100644
index 0000000..fe0e1f1
--- /dev/null
+++ b/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/exit0.ps1
@@ -0,0 +1,18 @@
+# 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.
+
+exit 0
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/10071a92/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/exit1.bat
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/exit1.bat b/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/exit1.bat
new file mode 100644
index 0000000..f8b79b9
--- /dev/null
+++ b/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/exit1.bat
@@ -0,0 +1,18 @@
+REM Licensed to the Apache Software Foundation (ASF) under one
+REM or more contributor license agreements.  See the NOTICE file
+REM distributed with this work for additional information
+REM regarding copyright ownership.  The ASF licenses this file
+REM to you under the Apache License, Version 2.0 (the
+REM "License"); you may not use this file except in compliance
+REM with 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,
+REM software distributed under the License is distributed on an
+REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+REM KIND, either express or implied.  See the License for the
+REM specific language governing permissions and limitations
+REM under the License.
+
+EXIT /B 1
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/10071a92/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/exit1.ps1
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/exit1.ps1 b/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/exit1.ps1
new file mode 100644
index 0000000..a7fc049
--- /dev/null
+++ b/usage/camp/src/test/resources/org/apache/brooklyn/camp/brooklyn/exit1.ps1
@@ -0,0 +1,19 @@
+# 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.
+
+Write-Host single line file gives exit code 0 from PyWinRM
+exit 1
\ No newline at end of file