You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by du...@apache.org on 2020/11/10 08:33:46 UTC

[brooklyn-server] 01/03: First draft taskfactory

This is an automated email from the ASF dual-hosted git repository.

duncangrant pushed a commit to branch winrmtaskfactory
in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git

commit 9fc84763665e7f343be6bf9d854ef9d7716217a9
Author: Duncan Grant <du...@cloudsoft.io>
AuthorDate: Thu Nov 5 10:15:50 2020 +0000

    First draft taskfactory
---
 .../brooklyn/api/location/MachineLocation.java     | 19 +++++
 .../core/effector/ssh/SshEffectorTasks.java        |  3 +-
 .../brooklyn/location/ssh/SshMachineLocation.java  | 17 +++-
 .../ssh/internal/AbstractSshExecTaskFactory.java   |  3 +-
 .../util/core/task/system/ProcessTaskFactory.java  |  3 +-
 .../util/core/task/system/ProcessTaskStub.java     |  5 +-
 .../internal/AbstractProcessTaskFactory.java       |  3 +-
 .../system/internal/SystemProcessTaskFactory.java  |  3 +-
 .../brooklyn/core/location/SimulatedLocation.java  | 48 ++++++++++-
 pom.xml                                            |  2 +-
 .../location/winrm/PlainWinRMExecTaskFactory.java  | 72 ++++++++++++++++
 .../apache/brooklyn/location/winrm/WinRMTasks.java | 52 ++++++++++++
 .../location/winrm/WinRmMachineLocation.java       | 99 +++++++++++++++++++---
 .../util/core/internal/winrm/WinRmTool.java        |  2 +-
 .../core/internal/winrm/winrm4j/Winrm4jTool.java   | 14 ++-
 15 files changed, 317 insertions(+), 28 deletions(-)

diff --git a/api/src/main/java/org/apache/brooklyn/api/location/MachineLocation.java b/api/src/main/java/org/apache/brooklyn/api/location/MachineLocation.java
index aa8ef9c..508c5d4 100644
--- a/api/src/main/java/org/apache/brooklyn/api/location/MachineLocation.java
+++ b/api/src/main/java/org/apache/brooklyn/api/location/MachineLocation.java
@@ -19,6 +19,8 @@
 package org.apache.brooklyn.api.location;
 
 import java.net.InetAddress;
+import java.util.List;
+import java.util.Map;
 
 import org.apache.brooklyn.util.net.HasNetworkAddresses;
 
@@ -44,4 +46,21 @@ public interface MachineLocation extends AddressableLocation, HasNetworkAddresse
      */
     MachineDetails getMachineDetails();
 
+    String getUser();
+
+    int execCommands(String summaryForLogging, List<String> commands);
+
+    int execCommands(Map<String, ?> props, String summaryForLogging, List<String> commands);
+
+    int execCommands(String summaryForLogging, List<String> commands, Map<String, ?> env);
+
+    int execCommands(Map<String, ?> props, String summaryForLogging, List<String> commands, Map<String, ?> env);
+
+    int execScript(String summaryForLogging, List<String> commands);
+
+    int execScript(Map<String, ?> props, String summaryForLogging, List<String> commands);
+
+    int execScript(String summaryForLogging, List<String> commands, Map<String, ?> env);
+
+    int execScript(Map<String, ?> props, String summaryForLogging, List<String> commands, Map<String, ?> env);
 }
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java
index e285817..706ea3d 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java
@@ -27,6 +27,7 @@ import javax.annotation.Nullable;
 import org.apache.brooklyn.api.effector.Effector;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.MachineLocation;
 import org.apache.brooklyn.api.mgmt.Task;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.config.StringConfigMap;
@@ -105,7 +106,7 @@ public class SshEffectorTasks {
         public SshEffectorTaskFactory(String ...commands) {
             super(commands);
         }
-        public SshEffectorTaskFactory(SshMachineLocation machine, String ...commands) {
+        public SshEffectorTaskFactory(MachineLocation machine, String ...commands) {
             super(machine, commands);
         }
         @Override
diff --git a/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java b/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
index ada35d0..14df87d 100644
--- a/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
+++ b/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
@@ -514,6 +514,7 @@ public class SshMachineLocation extends AbstractMachineLocation implements Machi
         return HostAndPort.fromParts(host, port);
     }
 
+    @Override
     public String getUser() {
         if (!groovyTruth(user)) {
             if (config().getLocalRaw(SshTool.PROP_USER).isPresent()) {
@@ -698,16 +699,20 @@ public class SshMachineLocation extends AbstractMachineLocation implements Machi
      * and/or {@code commandPrepend} and {@code commandAppend} similar to
      * (currently supported in SshjTool) {@code separator}.)
      */
+    @Override
     public int execCommands(String summaryForLogging, List<String> commands) {
         return execCommands(MutableMap.<String,Object>of(), summaryForLogging, commands, MutableMap.<String,Object>of());
     }
-    public int execCommands(Map<String,?> props, String summaryForLogging, List<String> commands) {
+    @Override
+    public int execCommands(Map<String, ?> props, String summaryForLogging, List<String> commands) {
         return execCommands(props, summaryForLogging, commands, MutableMap.<String,Object>of());
     }
-    public int execCommands(String summaryForLogging, List<String> commands, Map<String,?> env) {
+    @Override
+    public int execCommands(String summaryForLogging, List<String> commands, Map<String, ?> env) {
         return execCommands(MutableMap.<String,Object>of(), summaryForLogging, commands, env);
     }
-    public int execCommands(Map<String,?> props, String summaryForLogging, List<String> commands, Map<String,?> env) {
+    @Override
+    public int execCommands(Map<String, ?> props, String summaryForLogging, List<String> commands, Map<String, ?> env) {
         return newExecWithLoggingHelpers().execCommands(augmentPropertiesWithSshConfigGivenToProps(props), summaryForLogging, commands, env);
     }
 
@@ -718,15 +723,19 @@ public class SshMachineLocation extends AbstractMachineLocation implements Machi
      * flags 'noStdoutLogging' and 'noStderrLogging' are set. To set a logging prefix, use
      * the flag 'logPrefix'.
      */
+    @Override
     public int execScript(String summaryForLogging, List<String> commands) {
         return execScript(MutableMap.<String,Object>of(), summaryForLogging, commands, MutableMap.<String,Object>of());
     }
+    @Override
     public int execScript(Map<String,?> props, String summaryForLogging, List<String> commands) {
         return execScript(props, summaryForLogging, commands, MutableMap.<String,Object>of());
     }
+    @Override
     public int execScript(String summaryForLogging, List<String> commands, Map<String,?> env) {
         return execScript(MutableMap.<String,Object>of(), summaryForLogging, commands, env);
     }
+    @Override
     public int execScript(Map<String,?> props, String summaryForLogging, List<String> commands, Map<String,?> env) {
         return newExecWithLoggingHelpers().execScript(augmentPropertiesWithSshConfigGivenToProps(props), summaryForLogging, commands, env);
     }
@@ -869,7 +878,7 @@ public class SshMachineLocation extends AbstractMachineLocation implements Machi
             PipedInputStream insO = new PipedInputStream(); OutputStream outO = new PipedOutputStream(insO);
             PipedInputStream insE = new PipedInputStream(); OutputStream outE = new PipedOutputStream(insE);
             StreamGobbler sgsO = new StreamGobbler(insO, null, LOG); sgsO.setLogPrefix("[curl @ "+address+":stdout] ").start();
-            StreamGobbler sgsE = new StreamGobbler(insE, null, LOG); sgsE.setLogPrefix("[curl @ "+address+":stdout] ").start();
+            StreamGobbler sgsE = new StreamGobbler(insE, null, LOG); sgsE.setLogPrefix("[curl @ "+address+":stderr] ").start();
             Map<String, ?> sshProps = MutableMap.<String, Object>builder().putAll(props).put("out", outO).put("err", outE).build();
             int result = execScript(sshProps, "copying remote resource "+url+" to server",  ImmutableList.of(
                     BashCommands.INSTALL_CURL, // TODO should hold the 'installing' mutex
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/internal/AbstractSshExecTaskFactory.java b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/internal/AbstractSshExecTaskFactory.java
index 04272e8..354a2bb 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/internal/AbstractSshExecTaskFactory.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/internal/AbstractSshExecTaskFactory.java
@@ -20,6 +20,7 @@ package org.apache.brooklyn.util.core.task.ssh.internal;
 
 import com.google.common.base.Preconditions;
 
+import org.apache.brooklyn.api.location.MachineLocation;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory;
@@ -35,7 +36,7 @@ public abstract class AbstractSshExecTaskFactory<T extends AbstractProcessTaskFa
     }
 
     /** convenience constructor to supply machine immediately */
-    public AbstractSshExecTaskFactory(SshMachineLocation machine, String ...commands) {
+    public AbstractSshExecTaskFactory(MachineLocation machine, String ...commands) {
         this(commands);
         machine(machine);
     }
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskFactory.java b/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskFactory.java
index 8042cc2..b0878c3 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskFactory.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskFactory.java
@@ -20,6 +20,7 @@ package org.apache.brooklyn.util.core.task.system;
 
 import java.util.Map;
 
+import org.apache.brooklyn.api.location.MachineLocation;
 import org.apache.brooklyn.api.mgmt.TaskFactory;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
@@ -30,7 +31,7 @@ import com.google.common.annotations.Beta;
 import com.google.common.base.Function;
 
 public interface ProcessTaskFactory<T> extends TaskFactory<ProcessTaskWrapper<T>> {
-    public ProcessTaskFactory<T> machine(SshMachineLocation machine);
+    public ProcessTaskFactory<T> machine(MachineLocation machine);
     public ProcessTaskFactory<T> add(String ...commandsToAdd);
     public ProcessTaskFactory<T> add(Iterable<String> commandsToAdd);
     public ProcessTaskFactory<T> requiringExitCodeZero();
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskStub.java b/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskStub.java
index 5514ccf..549ba91 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskStub.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskStub.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.brooklyn.api.location.MachineLocation;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.config.ConfigBag;
@@ -35,7 +36,7 @@ public class ProcessTaskStub {
     
     protected final List<String> commands = new ArrayList<String>();
     /** null for localhost */
-    protected SshMachineLocation machine;
+    protected MachineLocation machine;
     
     // config data
     protected String summary;
@@ -75,7 +76,7 @@ public class ProcessTaskStub {
     }
     
     /** null for localhost */
-    public SshMachineLocation getMachine() {
+    public MachineLocation getMachine() {
         return machine;
     }
     
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/AbstractProcessTaskFactory.java b/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/AbstractProcessTaskFactory.java
index 6f9f0e0..e0a225a 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/AbstractProcessTaskFactory.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/AbstractProcessTaskFactory.java
@@ -21,6 +21,7 @@ package org.apache.brooklyn.util.core.task.system.internal;
 import java.util.Arrays;
 import java.util.Map;
 
+import org.apache.brooklyn.api.location.MachineLocation;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.apache.brooklyn.config.ConfigKey;
@@ -68,7 +69,7 @@ public abstract class AbstractProcessTaskFactory<T extends AbstractProcessTaskFa
     }
     
     @Override
-    public T machine(SshMachineLocation machine) {
+    public T machine(MachineLocation machine) {
         markDirty();
         this.machine = machine;
         return self();
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/SystemProcessTaskFactory.java b/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/SystemProcessTaskFactory.java
index 20e6180..afe5d33 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/SystemProcessTaskFactory.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/SystemProcessTaskFactory.java
@@ -20,6 +20,7 @@ package org.apache.brooklyn.util.core.task.system.internal;
 
 import java.io.File;
 
+import org.apache.brooklyn.api.location.MachineLocation;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.apache.brooklyn.core.config.Sanitizer;
@@ -57,7 +58,7 @@ public class SystemProcessTaskFactory<T extends SystemProcessTaskFactory<T,RET>,
     }
     
     @Override
-    public T machine(SshMachineLocation machine) {
+    public T machine(MachineLocation machine) {
         log.warn("Not permitted to set machines on "+this+" (ignoring - "+machine+")");
         if (log.isDebugEnabled())
             log.debug("Source of attempt to set machines on "+this+" ("+machine+")",
diff --git a/core/src/test/java/org/apache/brooklyn/core/location/SimulatedLocation.java b/core/src/test/java/org/apache/brooklyn/core/location/SimulatedLocation.java
index c5c2c85..41be3f5 100644
--- a/core/src/test/java/org/apache/brooklyn/core/location/SimulatedLocation.java
+++ b/core/src/test/java/org/apache/brooklyn/core/location/SimulatedLocation.java
@@ -20,6 +20,7 @@ package org.apache.brooklyn.core.location;
 
 import java.net.InetAddress;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -162,7 +163,52 @@ public class SimulatedLocation extends AbstractLocation implements MachineProvis
         OsDetails osDetails = BasicOsDetails.Factory.ANONYMOUS_LINUX;
         return new BasicMachineDetails(hardwareDetails, osDetails);
     }
-    
+
+    @Override
+    public String getUser() {
+        return null;
+    }
+
+    @Override
+    public int execCommands(String summaryForLogging, List<String> commands) {
+        return 0;
+    }
+
+    @Override
+    public int execCommands(Map<String, ?> props, String summaryForLogging, List<String> commands) {
+        return 0;
+    }
+
+    @Override
+    public int execCommands(String summaryForLogging, List<String> commands, Map<String, ?> env) {
+        return 0;
+    }
+
+    @Override
+    public int execCommands(Map<String, ?> props, String summaryForLogging, List<String> commands, Map<String, ?> env) {
+        return 0;
+    }
+
+    @Override
+    public int execScript(String summaryForLogging, List<String> commands) {
+        return 0;
+    }
+
+    @Override
+    public int execScript(Map<String, ?> props, String summaryForLogging, List<String> commands) {
+        return 0;
+    }
+
+    @Override
+    public int execScript(String summaryForLogging, List<String> commands, Map<String, ?> env) {
+        return 0;
+    }
+
+    @Override
+    public int execScript(Map<String, ?> props, String summaryForLogging, List<String> commands, Map<String, ?> env) {
+        return 0;
+    }
+
     private RuntimeException newException(String msg) {
         try {
             Exception result = getConfig(EXCEPTION_CLAZZ).getConstructor(String.class).newInstance(msg);
diff --git a/pom.xml b/pom.xml
index 0e96278..1c56a1a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -164,7 +164,7 @@
         <jax-rs-api.version>2.1.1</jax-rs-api.version> <!-- differs from jclouds 2.1.2, which depends on v2.0.1 -->
         <maxmind.version>2.8.0-rc1</maxmind.version>
         <maxmind-db.version>1.2.1</maxmind-db.version>
-        <winrm4j.version>0.9.0</winrm4j.version> <!--  FIXME NO CHECK IN -->
+        <winrm4j.version>0.9.0-SNAPSHOT</winrm4j.version> <!--  FIXME NO CHECK IN -->
         <felix-osgi-compendium.version>1.4.0</felix-osgi-compendium.version>
         <kubernetes-client.version>4.9.0</kubernetes-client.version>
 
diff --git a/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/PlainWinRMExecTaskFactory.java b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/PlainWinRMExecTaskFactory.java
new file mode 100644
index 0000000..0fa344e
--- /dev/null
+++ b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/PlainWinRMExecTaskFactory.java
@@ -0,0 +1,72 @@
+/*
+ * 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.location.winrm;
+
+import com.google.common.base.Function;
+import org.apache.brooklyn.util.core.task.ssh.internal.AbstractSshExecTaskFactory;
+import org.apache.brooklyn.util.core.task.ssh.internal.PlainSshExecTaskFactory;
+import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
+
+import java.util.List;
+
+public class PlainWinRMExecTaskFactory<RET> extends AbstractSshExecTaskFactory<PlainSshExecTaskFactory<RET>,RET> {
+
+    /** constructor where machine will be added later */
+    public PlainWinRMExecTaskFactory(String ...commands) {
+        super(commands);
+    }
+
+    /** convenience constructor to supply machine immediately */
+    public PlainWinRMExecTaskFactory(WinRmMachineLocation machine, String ...commands) {
+        this(commands);
+        machine(machine);
+    }
+
+    /** Constructor where machine will be added later */
+    public PlainWinRMExecTaskFactory(List<String> commands) {
+        this(commands.toArray(new String[commands.size()]));
+    }
+
+    /** Convenience constructor to supply machine immediately */
+    public PlainWinRMExecTaskFactory(WinRmMachineLocation machine, List<String> commands) {
+        this(machine, commands.toArray(new String[commands.size()]));
+    }
+
+    @Override
+    public <T2> PlainWinRMExecTaskFactory<T2> returning(ScriptReturnType type) {
+        return (PlainWinRMExecTaskFactory<T2>) super.<T2>returning(type);
+    }
+
+    @Override
+    public <RET2> PlainWinRMExecTaskFactory<RET2> returning(Function<ProcessTaskWrapper<?>, RET2> resultTransformation) {
+        return (PlainWinRMExecTaskFactory<RET2>) super.returning(resultTransformation);
+    }
+
+    @Override
+    public PlainWinRMExecTaskFactory<Boolean> returningIsExitCodeZero() {
+        return (PlainWinRMExecTaskFactory<Boolean>) super.returningIsExitCodeZero();
+    }
+
+    @Override
+    public PlainWinRMExecTaskFactory<String> requiringZeroAndReturningStdout() {
+        return (PlainWinRMExecTaskFactory<String>) super.requiringZeroAndReturningStdout();
+    }
+}
+
+
diff --git a/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRMTasks.java b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRMTasks.java
new file mode 100644
index 0000000..fb89ad5
--- /dev/null
+++ b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRMTasks.java
@@ -0,0 +1,52 @@
+/*
+ * 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.location.winrm;
+
+import org.apache.brooklyn.api.mgmt.TaskAdaptable;
+import org.apache.brooklyn.api.mgmt.TaskFactory;
+import org.apache.brooklyn.location.winrm.PlainWinRMExecTaskFactory;
+import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory;
+import org.apache.brooklyn.util.net.Urls;
+
+import java.util.Map;
+
+public class WinRMTasks {
+    public static ProcessTaskFactory<Integer> newWinrmExecTaskFactory(org.apache.brooklyn.location.winrm.WinRmMachineLocation winRmMachineLocation, String ...commands) {
+        return new PlainWinRMExecTaskFactory<>(winRmMachineLocation, commands);
+    }
+
+    public static TaskFactory<?> installFromUrl(final ResourceUtils utils, final Map<String, ?> props, final WinRmMachineLocation location, final String url, final String destPath) {
+        return new TaskFactory<TaskAdaptable<?>>() {
+            @Override
+            public TaskAdaptable<?> newTask() {
+                return Tasks.<Void>builder().displayName("installing "+ Urls.getBasename(url)).description("installing "+url+" to "+destPath).body(new Runnable() {
+                    @Override
+                    public void run() {
+                        int result = location.installTo(utils, props, url, destPath);
+                        if (result!=0)
+                            throw new IllegalStateException("Failed to install '"+url+"' to '"+destPath+"' at "+location+": exit code "+result);
+                    }
+                }).build();
+            }
+        };
+    }
+}
diff --git a/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java
index 0fd93b7..88d29a0 100644
--- a/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java
+++ b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java
@@ -21,10 +21,7 @@ package org.apache.brooklyn.location.winrm;
 import static org.apache.brooklyn.core.config.ConfigKeys.newConfigKeyWithPrefix;
 import static org.apache.brooklyn.core.config.ConfigKeys.newStringConfigKey;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
+import java.io.*;
 import java.net.InetAddress;
 import java.util.List;
 import java.util.Map;
@@ -32,6 +29,7 @@ import java.util.Set;
 
 import javax.annotation.Nullable;
 
+import com.google.common.base.*;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.location.MachineDetails;
 import org.apache.brooklyn.api.location.MachineLocation;
@@ -41,24 +39,25 @@ import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.config.ConfigUtils;
 import org.apache.brooklyn.core.config.Sanitizer;
-import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks;
 import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
 import org.apache.brooklyn.core.location.AbstractMachineLocation;
 import org.apache.brooklyn.core.location.access.PortForwardManager;
 import org.apache.brooklyn.core.location.access.PortForwardManagerLocationResolver;
 import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
 import org.apache.brooklyn.location.ssh.CanResolveOnBoxDir;
+import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.ClassLoaderUtils;
+import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.internal.ssh.ShellTool;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
 import org.apache.brooklyn.util.core.internal.winrm.WinRmTool;
 import org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse;
 import org.apache.brooklyn.util.core.internal.winrm.winrm4j.Winrm4jTool;
-import org.apache.brooklyn.util.core.task.DynamicTasks;
-import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
+import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.stream.StreamGobbler;
 import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.commons.codec.binary.Base64;
@@ -66,10 +65,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.Beta;
-import com.google.common.base.Charsets;
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -204,11 +199,54 @@ public class WinRmMachineLocation extends AbstractMachineLocation implements Mac
         return UNKNOWN_MACHINE_DETAILS;
     }
 
+    @Override
     public String getUser() {
         return config().get(USER);
     }
 
     @Override
+    public int execCommands(String summaryForLogging, List<String> commands) {
+        return executeCommand(commands).getStatusCode();
+    }
+
+    @Override
+    public int execCommands(Map<String, ?> props, String summaryForLogging, List<String> commands) {
+        return executeCommand(props, commands).getStatusCode();
+    }
+
+    @Override
+    public int execCommands(String summaryForLogging, List<String> commands, Map<String, ?> env) {
+        return executeCommand(ImmutableMap.of(Winrm4jTool.ENVIRONMENT, env),commands).getStatusCode();
+    }
+
+    @Override
+    public int execCommands(Map<String, ?> props, String summaryForLogging, List<String> commands, Map<String, ?> env) {
+        ImmutableMap<Object, Object> properties = ImmutableMap.builder().putAll(props).put(Winrm4jTool.ENVIRONMENT, env).build();
+        return executeCommand(properties, commands).getStatusCode();
+    }
+
+    @Override
+    public int execScript(String summaryForLogging, List<String> commands) {
+        return executePsScript(commands).getStatusCode();
+    }
+
+    @Override
+    public int execScript(Map<String, ?> props, String summaryForLogging, List<String> commands) {
+        return executePsScript(props, commands).getStatusCode();
+    }
+
+    @Override
+    public int execScript(String summaryForLogging, List<String> commands, Map<String, ?> env) {
+        return executePsScript(ImmutableMap.of(Winrm4jTool.ENVIRONMENT,env), commands).getStatusCode();
+    }
+
+    @Override
+    public int execScript(Map<String, ?> props, String summaryForLogging, List<String> commands, Map<String, ?> env) {
+        ImmutableMap<Object, Object> properties = ImmutableMap.builder().putAll(props).put(Winrm4jTool.ENVIRONMENT, env).build();
+        return executePsScript(properties, commands).getStatusCode();
+    }
+
+    @Override
     public InetAddress getAddress() {
         return getConfig(ADDRESS);
     }
@@ -507,4 +545,41 @@ public class WinRmMachineLocation extends AbstractMachineLocation implements Mac
         return unresolvedPath.replaceAll("/", "\\");
     }
 
+    public int installTo(ResourceUtils utils, Map<String, ?> props, String url, String destPath) {
+        LOG.debug("installing {} to {} on {}, attempting remote curl", new Object[] { url, destPath, this });
+
+        try(PipedInputStream insO = new PipedInputStream(); OutputStream outO = new PipedOutputStream(insO);
+            PipedInputStream insE = new PipedInputStream(); OutputStream outE = new PipedOutputStream(insE);
+            StreamGobbler sgsO = new StreamGobbler(insO, null, LOG);
+            StreamGobbler sgsE = new StreamGobbler(insE, null, LOG)
+            ){
+            sgsO.setLogPrefix("[curl @ "+getAddress()+":stdout] ").start();
+            sgsE.setLogPrefix("[curl @ "+getAddress()+":stderr] ").start();
+            Map<String, ?> winrmProps = MutableMap.<String, Object>builder().putAll(props).put("out", outO).put("err", outE).build();
+            int result = execScript(winrmProps,"",ImmutableList.of(
+                    "$WebClient = New-Object System.Net.WebClient",
+                    "$WebClient.DownloadFile(" + url + "," + destPath + ")"
+            ));
+
+            if (result != 0) {
+                LOG.debug("installing {} to {} on {}, curl failed, attempting local fetch and copy", new Object[] { url, destPath, this });
+                try {
+                    Tasks.setBlockingDetails("retrieving resource "+url+" for copying across");
+                    InputStream stream = utils.getResourceFromUrl(url);
+                    Tasks.setBlockingDetails("copying resource "+url+" to server");
+                    result = copyTo(props, stream, destPath);
+                } finally {
+                    Tasks.setBlockingDetails(null);
+                }
+            }
+            if (result == 0) {
+                LOG.debug("installing {} complete; {} on {}", new Object[] { url, destPath, this });
+            } else {
+                LOG.warn("installing {} failed; {} on {}: {}", new Object[] { url, destPath, this, result });
+            }
+            return result;
+        } catch (IOException e) {
+            throw Throwables.propagate(e);
+        }
+    }
 }
diff --git a/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/WinRmTool.java b/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/WinRmTool.java
index 1d9d2b9..b566224 100644
--- a/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/WinRmTool.java
+++ b/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/WinRmTool.java
@@ -105,6 +105,6 @@ public interface WinRmTool {
     WinRmToolResponse executeCommand(List<String> commands);
 
     WinRmToolResponse executePs(List<String> commands);
-    
+
     WinRmToolResponse copyToServer(InputStream source, String destination);
 }
diff --git a/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/winrm4j/Winrm4jTool.java b/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/winrm4j/Winrm4jTool.java
index b7ff665..332b008 100644
--- a/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/winrm4j/Winrm4jTool.java
+++ b/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/winrm4j/Winrm4jTool.java
@@ -20,7 +20,7 @@ package org.apache.brooklyn.util.core.internal.winrm.winrm4j;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import java.io.InputStream;
+import java.io.*;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
@@ -34,6 +34,7 @@ import org.apache.brooklyn.core.internal.BrooklynProperties;
 import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
 import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.internal.ssh.ShellTool;
 import org.apache.brooklyn.util.core.internal.winrm.WinRmException;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.javalang.Threads;
@@ -116,7 +117,16 @@ public class Winrm4jTool implements org.apache.brooklyn.util.core.internal.winrm
     public org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse executeCommand(final List<String> commands) {
         return exec(new Function<io.cloudsoft.winrm4j.winrm.WinRmTool, io.cloudsoft.winrm4j.winrm.WinRmToolResponse>() {
             @Override public WinRmToolResponse apply(io.cloudsoft.winrm4j.winrm.WinRmTool tool) {
-                return tool.executeCommand(commands);
+                OutputStream outputStream = bag.get(ShellTool.PROP_OUT_STREAM);
+                OutputStream errorStream = bag.get(ShellTool.PROP_ERR_STREAM);
+                try(Writer out = outputStream != null ? new OutputStreamWriter(outputStream): new StringWriter();
+                Writer err = errorStream != null ? new OutputStreamWriter(errorStream): new StringWriter()) {
+                    return tool.executeCommand(commands, out, err);
+                } catch (IOException e) {
+                    // TODO Duncan
+                    e.printStackTrace();
+                    return null;
+                }
             }
         });
     }