You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2020/11/13 15:06:07 UTC

[brooklyn-server] 01/05: installTo for winrm supports urls

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

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

commit 3b56db700b251150e02345bb3e8f24f9fc447b1e
Author: Duncan Grant <du...@cloudsoft.io>
AuthorDate: Tue Nov 10 08:19:49 2020 +0000

    installTo for winrm supports urls
---
 .../brooklyn/api/location/MachineLocation.java     |  12 --
 .../brooklyn/location/ssh/SshMachineLocation.java  |   6 -
 .../brooklyn/core/location/SimulatedLocation.java  |  30 ----
 pom.xml                                            |   2 +-
 ...Factory.java => PlainWinRmExecTaskFactory.java} |  26 +--
 .../location/winrm/WinRmMachineLocation.java       |  43 +----
 .../location/winrm/WinRmPutTaskFactory.java        | 120 +++++++++++++
 .../brooklyn/location/winrm/WinRmPutTaskStub.java  |  68 ++++++++
 .../location/winrm/WinRmPutTaskWrapper.java        | 185 +++++++++++++++++++++
 .../winrm/{WinRMTasks.java => WinRmTasks.java}     |  13 +-
 .../core/internal/winrm/winrm4j/Winrm4jTool.java   |  45 ++---
 11 files changed, 421 insertions(+), 129 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 508c5d4..d207886 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
@@ -48,19 +48,7 @@ public interface MachineLocation extends AddressableLocation, HasNetworkAddresse
 
     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/location/ssh/SshMachineLocation.java b/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
index 14df87d..d5ed481 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
@@ -699,15 +699,12 @@ 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());
     }
-    @Override
     public int execCommands(Map<String, ?> props, String summaryForLogging, List<String> commands) {
         return execCommands(props, summaryForLogging, commands, MutableMap.<String,Object>of());
     }
-    @Override
     public int execCommands(String summaryForLogging, List<String> commands, Map<String, ?> env) {
         return execCommands(MutableMap.<String,Object>of(), summaryForLogging, commands, env);
     }
@@ -723,15 +720,12 @@ 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);
     }
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 41be3f5..83faa97 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
@@ -170,41 +170,11 @@ public class SimulatedLocation extends AbstractLocation implements MachineProvis
     }
 
     @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;
     }
diff --git a/pom.xml b/pom.xml
index 1c56a1a..572103e 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-SNAPSHOT</winrm4j.version> <!--  FIXME NO CHECK IN -->
+        <winrm4j.version>0.10.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
similarity index 70%
rename from software/winrm/src/main/java/org/apache/brooklyn/location/winrm/PlainWinRMExecTaskFactory.java
rename to software/winrm/src/main/java/org/apache/brooklyn/location/winrm/PlainWinRmExecTaskFactory.java
index 0fa344e..0fa3baf 100644
--- 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
@@ -25,47 +25,47 @@ import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 
 import java.util.List;
 
-public class PlainWinRMExecTaskFactory<RET> extends AbstractSshExecTaskFactory<PlainSshExecTaskFactory<RET>,RET> {
+public class PlainWinRmExecTaskFactory<RET> extends AbstractSshExecTaskFactory<PlainSshExecTaskFactory<RET>,RET> {
 
     /** constructor where machine will be added later */
-    public PlainWinRMExecTaskFactory(String ...commands) {
+    public PlainWinRmExecTaskFactory(String ...commands) {
         super(commands);
     }
 
     /** convenience constructor to supply machine immediately */
-    public PlainWinRMExecTaskFactory(WinRmMachineLocation machine, String ...commands) {
+    public PlainWinRmExecTaskFactory(WinRmMachineLocation machine, String ...commands) {
         this(commands);
         machine(machine);
     }
 
     /** Constructor where machine will be added later */
-    public PlainWinRMExecTaskFactory(List<String> commands) {
+    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) {
+    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);
+    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);
+    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();
+    public PlainWinRmExecTaskFactory<Boolean> returningIsExitCodeZero() {
+        return (PlainWinRmExecTaskFactory<Boolean>) super.returningIsExitCodeZero();
     }
 
     @Override
-    public PlainWinRMExecTaskFactory<String> requiringZeroAndReturningStdout() {
-        return (PlainWinRMExecTaskFactory<String>) super.requiringZeroAndReturningStdout();
+    public PlainWinRmExecTaskFactory<String> requiringZeroAndReturningStdout() {
+        return (PlainWinRmExecTaskFactory<String>) super.requiringZeroAndReturningStdout();
     }
 }
 
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 1198697..efc0cb6 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
@@ -205,42 +205,12 @@ public class WinRmMachineLocation extends AbstractMachineLocation implements Mac
     }
 
     @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();
@@ -556,12 +526,13 @@ public class WinRmMachineLocation extends AbstractMachineLocation implements Mac
             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 + ")"
-//            ));
-
-            int result = 1;
+            ImmutableList<String> commands = ImmutableList.of(
+                    "$tmp = New-TemporaryFile",
+                    "echo $WebClient = New-Object System.Net.WebClient > $tmp.FullName",
+                    "echo $WebClient.DownloadFile(" + url + "," + destPath + ") >> $tmp.FullName",
+                    "powershell -c $tmp.FullName"
+            );
+            int result = execCommands(winrmProps,"", commands, ImmutableMap.of());
 
             if (result != 0) {
                 LOG.debug("installing {} to {} on {}, curl failed, attempting local fetch and copy", new Object[] { url, destPath, this });
diff --git a/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmPutTaskFactory.java b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmPutTaskFactory.java
new file mode 100644
index 0000000..23081bc
--- /dev/null
+++ b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmPutTaskFactory.java
@@ -0,0 +1,120 @@
+/*
+ * 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.Suppliers;
+import org.apache.brooklyn.api.mgmt.TaskFactory;
+import org.apache.brooklyn.util.stream.KnownSizeInputStream;
+import org.apache.brooklyn.util.stream.ReaderInputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.io.Reader;
+
+public class WinRmPutTaskFactory extends WinRmPutTaskStub implements TaskFactory<WinRmPutTaskWrapper> {
+
+    private static final Logger log = LoggerFactory.getLogger(WinRmPutTaskFactory.class);
+
+    private boolean dirty = false;
+
+    /** constructor where machine will be added later */
+    public WinRmPutTaskFactory(String remoteFile) {
+        remoteFile(remoteFile);
+    }
+
+    /** convenience constructor to supply machine immediately */
+    public WinRmPutTaskFactory(WinRmMachineLocation machine, String remoteFile) {
+        machine(machine);
+        remoteFile(remoteFile);
+    }
+
+    protected WinRmPutTaskFactory self() { return this; }
+
+    protected void markDirty() {
+        dirty = true;
+    }
+
+    public WinRmPutTaskFactory machine(WinRmMachineLocation machine) {
+        markDirty();
+        this.machine = machine;
+        return self();
+    }
+
+    public WinRmPutTaskFactory remoteFile(String remoteFile) {
+        this.remoteFile = remoteFile;
+        return self();
+    }
+
+    public WinRmPutTaskFactory summary(String summary) {
+        markDirty();
+        this.summary = summary;
+        return self();
+    }
+
+    public WinRmPutTaskFactory contents(String contents) {
+        markDirty();
+        this.contents = Suppliers.ofInstance(KnownSizeInputStream.of(contents));
+        return self();
+    }
+
+    public WinRmPutTaskFactory contents(byte[] contents) {
+        markDirty();
+        this.contents = Suppliers.ofInstance(KnownSizeInputStream.of(contents));
+        return self();
+    }
+
+    public WinRmPutTaskFactory contents(InputStream stream) {
+        markDirty();
+        this.contents = Suppliers.ofInstance(stream);
+        return self();
+    }
+
+    public WinRmPutTaskFactory contents(Reader reader) {
+        markDirty();
+        this.contents = Suppliers.ofInstance(new ReaderInputStream(reader));
+        return self();
+    }
+
+    public WinRmPutTaskFactory allowFailure() {
+        markDirty();
+        allowFailure = true;
+        return self();
+    }
+
+    public WinRmPutTaskFactory createDirectory() {
+        markDirty();
+        createDirectory = true;
+        return self();
+    }
+
+    @Override
+    public WinRmPutTaskWrapper newTask() {
+        dirty = false;
+        return new WinRmPutTaskWrapper(this);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        // help let people know of API usage error
+        if (dirty)
+            log.warn("Task "+this+" was modified but modification was never used");
+        super.finalize();
+    }
+}
diff --git a/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmPutTaskStub.java b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmPutTaskStub.java
new file mode 100644
index 0000000..28a27bd
--- /dev/null
+++ b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmPutTaskStub.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.winrm;
+
+import com.google.common.base.Supplier;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.task.ssh.SshPutTaskStub;
+
+import java.io.InputStream;
+
+public class WinRmPutTaskStub {
+    protected String remoteFile;
+    protected WinRmMachineLocation machine;
+    protected Supplier<? extends InputStream> contents;
+    protected String summary;
+    protected String permissions;
+    protected boolean allowFailure = false;
+    protected boolean createDirectory = false;
+    protected final ConfigBag config = ConfigBag.newInstance();
+
+    protected WinRmPutTaskStub() {
+    }
+
+    protected WinRmPutTaskStub(WinRmPutTaskStub constructor) {
+        this.remoteFile = constructor.remoteFile;
+        this.machine = constructor.machine;
+        this.contents = constructor.contents;
+        this.summary = constructor.summary;
+        this.allowFailure = constructor.allowFailure;
+        this.createDirectory = constructor.createDirectory;
+        this.permissions = constructor.permissions;
+        this.config.copy(constructor.config);
+    }
+
+    public String getRemoteFile() {
+        return remoteFile;
+    }
+
+    public String getSummary() {
+        if (summary!=null) return summary;
+        return "scp put: "+remoteFile;
+    }
+
+    public WinRmMachineLocation getMachine() {
+        return machine;
+    }
+
+    protected ConfigBag getConfig() {
+        return config;
+    }
+}
diff --git a/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmPutTaskWrapper.java b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmPutTaskWrapper.java
new file mode 100644
index 0000000..1315010
--- /dev/null
+++ b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmPutTaskWrapper.java
@@ -0,0 +1,185 @@
+/*
+ * 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.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.api.mgmt.TaskWrapper;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.internal.ssh.SshTool;
+import org.apache.brooklyn.util.core.task.TaskBuilder;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+
+public class WinRmPutTaskWrapper extends WinRmPutTaskStub implements TaskWrapper<Void> {
+    private static final Logger log = LoggerFactory.getLogger(WinRmPutTaskWrapper.class);
+
+    private final Task<Void> task;
+
+    protected Integer exitCodeOfCopy = null;
+    protected Exception exception = null;
+    protected boolean successful = false;
+
+    // package private as only AbstractWinRmTaskFactory should invoke
+    WinRmPutTaskWrapper(WinRmPutTaskFactory constructor) {
+        super(constructor);
+        TaskBuilder<Void> tb = TaskBuilder.<Void>builder().dynamic(false).displayName(getSummary());
+        task = tb.body(new WinRmPutJob()).build();
+    }
+
+    @Override
+    public Task<Void> asTask() {
+        return getTask();
+    }
+
+    @Override
+    public Task<Void> getTask() {
+        return task;
+    }
+
+    // TODO:
+    //   verify
+    //   copyAsRoot
+    //   owner
+    //   lastModificationDate - see {@link #PROP_LAST_MODIFICATION_DATE}; not supported by all SshTool implementations
+    //   lastAccessDate - see {@link #PROP_LAST_ACCESS_DATE}; not supported by all SshTool implementations
+
+    private class WinRmPutJob implements Callable<Void> {
+        @Override
+        public Void call() throws Exception {
+            try {
+                Preconditions.checkNotNull(getMachine(), "machine");
+
+                String remoteFile = getRemoteFile();
+
+                if (createDirectory) {
+                    String remoteDir = remoteFile;
+                    int exitCodeOfCreate = -1;
+                    try {
+                        int li = remoteDir.lastIndexOf("/");
+                        if (li>=0) {
+                            remoteDir = remoteDir.substring(0, li+1);
+                            exitCodeOfCreate = getMachine().execCommands(ImmutableMap.of(), "creating directory for "+getSummary(),
+                                    Arrays.asList("md "+remoteDir), ImmutableMap.of());
+                        } else {
+                            // nothing to create
+                            exitCodeOfCreate = 0;
+                        }
+                    } catch (Exception e) {
+                        if (log.isDebugEnabled())
+                            log.debug("WinRM put "+getRemoteFile()+" (create dir, in task "+getSummary()+") to "+getMachine()+" threw exception: "+e);
+                        exception = e;
+                    }
+                    if (exception!=null || !((Integer)0).equals(exitCodeOfCreate)) {
+                        if (!allowFailure) {
+                            if (exception != null) {
+                                throw new IllegalStateException(getSummary()+" (creating dir "+remoteDir+" for WinRM put task) ended with exception, in "+ Tasks.current()+": "+exception, exception);
+                            }
+                            if (exitCodeOfCreate!=0) {
+                                exception = new IllegalStateException(getSummary()+" (creating dir "+remoteDir+" WinRM put task) ended with exit code "+exitCodeOfCreate+", in "+Tasks.current());
+                                throw exception;
+                            }
+                        }
+                        // not successful, but allowed
+                        return null;
+                    }
+                }
+
+                ConfigBag config = ConfigBag.newInstanceCopying(getConfig());
+                if (permissions!=null) config.put(SshTool.PROP_PERMISSIONS, permissions);
+
+                exitCodeOfCopy = getMachine().copyTo(config.getAllConfig(), contents.get(), remoteFile);
+
+                if (log.isDebugEnabled())
+                    log.debug("WinRM put "+getRemoteFile()+" (task "+getSummary()+") to "+getMachine()+" completed with exit code "+exitCodeOfCopy);
+            } catch (Exception e) {
+                if (log.isDebugEnabled())
+                    log.debug("WinRM put "+getRemoteFile()+" (task "+getSummary()+") to "+getMachine()+" threw exception: "+e);
+                exception = e;
+            }
+
+            if (exception!=null || !((Integer)0).equals(exitCodeOfCopy)) {
+                if (!allowFailure) {
+                    if (exception != null) {
+                        throw new IllegalStateException(getSummary()+" (WinRM put task) ended with exception, in "+Tasks.current()+": "+exception, exception);
+                    }
+                    if (exitCodeOfCopy!=0) {
+                        exception = new IllegalStateException(getSummary()+" (WinRM put task) ended with exit code "+exitCodeOfCopy+", in "+Tasks.current());
+                        throw exception;
+                    }
+                }
+                // not successful, but allowed
+                return null;
+            }
+
+            // TODO verify
+
+            successful = (exception==null && ((Integer)0).equals(exitCodeOfCopy));
+            return null;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return super.toString()+"["+task+"]";
+    }
+
+    /** blocks, throwing if there was an exception */
+    public Void get() {
+        return getTask().getUnchecked();
+    }
+
+    /** returns the exit code from the copy, 0 on success;
+     * null if it has not completed or threw exception
+     * (not sure if this is ever a non-zero integer or if it is meaningful)
+     * <p>
+     * most callers will want the simpler {@link #isSuccessful()} */
+    public Integer getExitCode() {
+        return exitCodeOfCopy;
+    }
+
+    /** returns any exception encountered in the operation */
+    public Exception getException() {
+        return exception;
+    }
+
+    /** blocks until the task completes; does not throw
+     * @return*/
+    public WinRmPutTaskWrapper block() {
+        getTask().blockUntilEnded();
+        return this;
+    }
+
+    /** true iff the ssh job has completed (with or without failure) */
+    public boolean isDone() {
+        return getTask().isDone();
+    }
+
+    /** true iff the scp has completed successfully; guaranteed to be set before {@link #isDone()} or {@link #block()} are satisfied */
+    public boolean isSuccessful() {
+        return successful;
+    }
+
+
+}
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
similarity index 81%
rename from software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRMTasks.java
rename to software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmTasks.java
index fb89ad5..7ae2663 100644
--- 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
@@ -20,8 +20,6 @@ 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;
@@ -29,9 +27,9 @@ import org.apache.brooklyn.util.net.Urls;
 
 import java.util.Map;
 
-public class WinRMTasks {
+public class WinRmTasks {
     public static ProcessTaskFactory<Integer> newWinrmExecTaskFactory(org.apache.brooklyn.location.winrm.WinRmMachineLocation winRmMachineLocation, String ...commands) {
-        return new PlainWinRMExecTaskFactory<>(winRmMachineLocation, 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) {
@@ -49,4 +47,11 @@ public class WinRMTasks {
             }
         };
     }
+
+    public static WinRmPutTaskFactory newWinrmPutTaskFactory(WinRmMachineLocation winRmMachineLocation, String remoteFile) {
+        return newWinRmPutTaskFactory(winRmMachineLocation, true, remoteFile);
+    }
+    public static WinRmPutTaskFactory newWinRmPutTaskFactory(WinRmMachineLocation machine, final boolean useMachineConfig, String remoteFile) {
+        return new WinRmPutTaskFactory(machine, remoteFile);
+    }
 }
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 784e7d1..4295168 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
@@ -115,19 +115,12 @@ public class Winrm4jTool implements org.apache.brooklyn.util.core.internal.winrm
 
     @Override
     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) {
-                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;
-                }
-            }
+        return exec(tool -> {
+            OutputStream outputStream = bag.get(ShellTool.PROP_OUT_STREAM);
+            OutputStream errorStream = bag.get(ShellTool.PROP_ERR_STREAM);
+            Writer out = outputStream != null ? new OutputStreamWriter(outputStream): new StringWriter();
+            Writer err = errorStream != null ? new OutputStreamWriter(errorStream): new StringWriter();
+            return tool.executeCommand(commands, out, err);
         });
     }
 
@@ -136,17 +129,15 @@ public class Winrm4jTool implements org.apache.brooklyn.util.core.internal.winrm
     public org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse executeScript(final List<String> commands) {
         return executeCommand(commands);
     }
-    
+
     @Override
     public org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse executePs(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) {
-                OutputStream outputStream = bag.get(ShellTool.PROP_OUT_STREAM);
-                OutputStream errorStream = bag.get(ShellTool.PROP_ERR_STREAM);
-                Writer out = outputStream != null ? new OutputStreamWriter(outputStream): new StringWriter();
-                Writer err = errorStream != null ? new OutputStreamWriter(errorStream): new StringWriter();
-                return tool.executePs(commands, out, err);
-            }
+        return exec(tool -> {
+            OutputStream outputStream = bag.get(ShellTool.PROP_OUT_STREAM);
+            OutputStream errorStream = bag.get(ShellTool.PROP_ERR_STREAM);
+            Writer out = outputStream != null ? new OutputStreamWriter(outputStream): new StringWriter();
+            Writer err = errorStream != null ? new OutputStreamWriter(errorStream): new StringWriter();
+            return tool.executePs(commands, out, err);
         });
     }
 
@@ -161,9 +152,9 @@ public class Winrm4jTool implements org.apache.brooklyn.util.core.internal.winrm
             int i=0;
             while ((bytesRead = source.read(inputData)) > 0) {
                 i++;
-                
+
                 LOG.debug("Copying chunk "+i+" to "+destination+" on "+host);
-                
+
                 byte[] chunk;
                 if (bytesRead == chunkSize) {
                     chunk = inputData;
@@ -176,7 +167,7 @@ public class Winrm4jTool implements org.apache.brooklyn.util.core.internal.winrm
                 expectedFileSize += bytesRead;
             }
             LOG.debug("Finished copying to "+destination+" on "+host);
-            
+
             return new org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse("", "", 0);
         } catch (java.io.IOException e) {
             throw propagate(e, "Failed copying to server at "+destination);
@@ -186,7 +177,7 @@ public class Winrm4jTool implements org.apache.brooklyn.util.core.internal.winrm
     private org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse exec(Function<io.cloudsoft.winrm4j.winrm.WinRmTool, io.cloudsoft.winrm4j.winrm.WinRmToolResponse> task) {
         Collection<Throwable> exceptions = Lists.newArrayList();
         Stopwatch totalStopwatch = Stopwatch.createStarted();
-        
+
         for (int i = 0; i < execTries; i++) {
             Stopwatch stopwatch = Stopwatch.createStarted();
             Duration connectTimestamp = null;
@@ -212,7 +203,7 @@ public class Winrm4jTool implements org.apache.brooklyn.util.core.internal.winrm
                 String timeMsg = "total time "+Duration.of(totalStopwatch).toStringRounded()
                         + ", this attempt failed after "+Duration.of(failTimestamp).toStringRounded()
                         + (connectTimestamp != null ? ", connected in "+Duration.of(connectTimestamp).toStringRounded() : "");
-                
+
                 if ((i + 1) == execTries) {
                     LOG.info("Propagating exception - WinRM failed on "+user+"@"+host+":"+port+" "
                             + (logCredentials ? "password=" + password : "")