diff --git a/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/AbstractBaseSshClient.java b/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/AbstractBaseSshClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..793eb82e922be76e5f1b56f20bec76322da6fbc7
--- /dev/null
+++ b/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/AbstractBaseSshClient.java
@@ -0,0 +1,173 @@
+
+package cz.it4i.fiji.scpclient;
+
+import com.jcraft.jsch.Identity;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+import com.jcraft.jsch.UserInfo;
+
+import java.io.Closeable;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Field;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import cz.it4i.fiji.commons.DoActionEventualy;
+
+public class AbstractBaseSshClient implements Closeable {
+
+	protected static final int MAX_NUMBER_OF_CONNECTION_ATTEMPTS = 5;
+
+	protected static final long TIMEOUT_BETWEEN_CONNECTION_ATTEMPTS = 500;
+
+	private final static Logger log = LoggerFactory.getLogger(
+		AbstractBaseSshClient.class);
+
+	private String hostName;
+	private String username;
+	private final JSch jsch = new JSch();
+	private Session session;
+
+	private int port = 22;
+
+	public AbstractBaseSshClient(String hostName, String username,
+		byte[] privateKeyFile) throws JSchException
+	{
+		init(hostName, username, new ByteIdentity(jsch, privateKeyFile));
+	}
+
+	public AbstractBaseSshClient(String hostName, String username,
+		Identity privateKeyFile) throws JSchException
+	{
+		init(hostName, username, privateKeyFile);
+	}
+
+	public AbstractBaseSshClient(String hostName, String userName, String keyFile,
+		String pass) throws JSchException
+	{
+		Identity id = IdentityFile.newInstance(keyFile, null, jsch);
+		try {
+			if (pass != null) {
+				id.setPassphrase(pass.getBytes("UTF-8"));
+			}
+		}
+		catch (UnsupportedEncodingException e) {
+			throw new RuntimeException(e);
+		}
+		init(hostName, userName, id);
+	}
+
+	public void setPort(int port) {
+		this.port = port;
+	}
+
+	@Override
+	public void close() {
+		if (session != null && session.isConnected()) {
+			// log.info("disconnect");
+			try (DoActionEventualy actionEventualy = new DoActionEventualy(
+				TIMEOUT_BETWEEN_CONNECTION_ATTEMPTS, this::interruptSessionThread))
+			{
+				session.disconnect();
+			}
+		}
+		session = null;
+	}
+
+	protected Session getConnectedSession() throws JSchException {
+		if (session == null) {
+			session = jsch.getSession(username, hostName, port);
+
+			UserInfo ui = new P_UserInfo();
+
+			session.setUserInfo(ui);
+		}
+		int connectRetry = 0;
+		long timoutBetweenRetry = TIMEOUT_BETWEEN_CONNECTION_ATTEMPTS;
+		while (!session.isConnected()) {
+			try {
+				session.connect();
+			}
+			catch (JSchException e) {
+				if (e.getMessage().contains("Auth fail") || e.getMessage().contains(
+					"Packet corrupt"))
+				{
+					if (connectRetry < MAX_NUMBER_OF_CONNECTION_ATTEMPTS) {
+						connectRetry++;
+						try {
+							Thread.sleep(timoutBetweenRetry);
+							timoutBetweenRetry *= 2;
+						}
+						catch (InterruptedException exc) {
+							log.info("Interruption detected");
+							throw new JSchException(exc.getMessage(), exc);
+						}
+						continue;
+					}
+					e = new AuthFailException(e.getMessage(), e);
+				}
+				throw e;
+			}
+		}
+		return session;
+	}
+
+	private void interruptSessionThread() {
+		try {
+			Field f = session.getClass().getDeclaredField("connectThread");
+			if (!f.isAccessible()) {
+				f.setAccessible(true);
+				Thread thread = (Thread) f.get(session);
+				thread.interrupt();
+			}
+		}
+		catch (NoSuchFieldException | SecurityException | IllegalArgumentException
+				| IllegalAccessException exc)
+		{
+			log.error(exc.getMessage(), exc);
+		}
+	}
+
+	private void init(String initHostName, String initUsername,
+		Identity privateKeyFile) throws JSchException
+	{
+		this.hostName = initHostName;
+		this.username = initUsername;
+		jsch.addIdentity(privateKeyFile, null);
+	}
+
+	private class P_UserInfo implements UserInfo {
+
+		@Override
+		public String getPassphrase() {
+			return null;
+		}
+
+		@Override
+		public String getPassword() {
+			return null;
+		}
+
+		@Override
+		public boolean promptPassword(String message) {
+			return false;
+		}
+
+		@Override
+		public boolean promptPassphrase(String message) {
+			return false;
+		}
+
+		@Override
+		public boolean promptYesNo(String message) {
+			return true;
+		}
+
+		@Override
+		public void showMessage(String message) {}
+
+	}
+
+}
diff --git a/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/ScpClient.java b/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/ScpClient.java
index b1736e5752892cdcc6b58cef38e897e9173fc91d..20cd2e9a1fc57357ce03b308278320832751426f 100644
--- a/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/ScpClient.java
+++ b/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/ScpClient.java
@@ -6,19 +6,13 @@ import com.jcraft.jsch.ChannelExec;
 import com.jcraft.jsch.ChannelSftp;
 import com.jcraft.jsch.ChannelSftp.LsEntry;
 import com.jcraft.jsch.Identity;
-import com.jcraft.jsch.JSch;
 import com.jcraft.jsch.JSchException;
-import com.jcraft.jsch.Session;
 import com.jcraft.jsch.SftpException;
-import com.jcraft.jsch.UserInfo;
 
-import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InterruptedIOException;
 import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Field;
 import java.nio.channels.ClosedByInterruptException;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
@@ -30,31 +24,20 @@ import java.util.stream.Collectors;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import cz.it4i.fiji.commons.DoActionEventualy;
+public class ScpClient extends AbstractBaseSshClient {
 
-public class ScpClient implements Closeable {
+	public static final Logger log = LoggerFactory.getLogger(ScpClient.class);
 
 	private static final String NO_SUCH_FILE_OR_DIRECTORY_ERROR_TEXT =
 		"No such file or directory";
 
-	public static final Logger log = LoggerFactory.getLogger(
-		cz.it4i.fiji.scpclient.ScpClient.class);
-	
 	private static String constructExceptionText(AckowledgementChecker ack) {
 		return "Check acknowledgement failed with status: " + ack.getLastStatus() +
 			" and message: " + ack.getLastMessage();
 	}
-	
-	private static final int MAX_NUMBER_OF_CONNECTION_ATTEMPTS = 5;
 
-	private static final long TIMEOUT_BETWEEN_CONNECTION_ATTEMPTS = 500;
+	private static final int BUFFER_SIZE = 4 * 1024 * 1024; // 4 MB
 
-	private static final int BUFFER_SIZE = 4 * 1024 * 1024 ; //4 MB
-
-	private String hostName;
-	private String username;
-	private final JSch jsch = new JSch();
-	private Session session;
 	private final TransferFileProgress dummyProgress =
 		new TransferFileProgress()
 		{
@@ -62,48 +45,25 @@ public class ScpClient implements Closeable {
 			@Override
 			public void dataTransfered(long bytesTransfered) {
 
-		}
+			}
 		};
 
-	private int port = 22;
-
 	public ScpClient(String hostName, String username, byte[] privateKeyFile)
 		throws JSchException
 	{
-		init(hostName, username, new ByteIdentity(jsch, privateKeyFile));
+		super(hostName, username, privateKeyFile);
 	}
 
 	public ScpClient(String hostName, String username, Identity privateKeyFile)
 		throws JSchException
 	{
-		init(hostName, username, privateKeyFile);
+		super(hostName, username, privateKeyFile);
 	}
 
 	public ScpClient(String hostName, String userName, String keyFile,
 		String pass) throws JSchException
 	{
-		Identity id = IdentityFile.newInstance(keyFile, null, jsch);
-		try {
-			if (pass != null) {
-				id.setPassphrase(pass.getBytes("UTF-8"));
-			}
-		}
-		catch (UnsupportedEncodingException e) {
-			throw new RuntimeException(e);
-		}
-		init(hostName, userName, id);
-	}
-	
-	public void setPort(int port) {
-		this.port  = port;
-	}
-
-	private void init(String initHostName, String initUsername,
-		Identity privateKeyFile) throws JSchException
-	{
-		this.hostName = initHostName;
-		this.username = initUsername;
-		jsch.addIdentity(privateKeyFile, null);
+		super(hostName, userName, keyFile, pass);
 	}
 
 	public void download(String lfile, Path rFile) throws JSchException,
@@ -112,8 +72,8 @@ public class ScpClient implements Closeable {
 		download(lfile, rFile, dummyProgress);
 	}
 
-	public void download(String lfile, Path rfile,
-		TransferFileProgress progress) throws JSchException, IOException
+	public void download(String lfile, Path rfile, TransferFileProgress progress)
+		throws JSchException, IOException
 	{
 		if (!Files.exists(rfile.getParent())) {
 			Files.createDirectories(rfile.getParent());
@@ -129,8 +89,8 @@ public class ScpClient implements Closeable {
 		AckowledgementChecker ack = new AckowledgementChecker();
 		// exec 'scp -f rfile' remotely
 
-		lfile=lfile.replace("'", "'\"'\"'");
-		lfile="'"+lfile+"'";
+		lfile = lfile.replace("'", "'\"'\"'");
+		lfile = "'" + lfile + "'";
 
 		String command = "scp -f " + lfile;
 		Channel channel = getConnectedSession().openChannel("exec");
@@ -184,7 +144,7 @@ public class ScpClient implements Closeable {
 					// System.out.println("filesize="+filesize+", file="+file);
 
 					// send '\0'
-					
+
 					buf[0] = 0;
 					out.write(buf, 0, 1);
 					out.flush();
@@ -236,8 +196,8 @@ public class ScpClient implements Closeable {
 		throws JSchException, IOException
 	{
 		try (InputStream is = Files.newInputStream(file)) {
-			upload(is, rfile, file.toFile().length(), file.toFile()
-				.lastModified(), progress);
+			upload(is, rfile, file.toFile().length(), file.toFile().lastModified(),
+				progress);
 		}
 	}
 
@@ -250,7 +210,8 @@ public class ScpClient implements Closeable {
 			try {
 				scp2Server(is, fileName, length, lastModified, progress);
 				break;
-			} catch (NoSuchFileException e) {
+			}
+			catch (NoSuchFileException e) {
 				if (noSuchFileExceptionThrown > MAX_NUMBER_OF_CONNECTION_ATTEMPTS) {
 					throw new JSchException(e.getReason());
 				}
@@ -258,22 +219,22 @@ public class ScpClient implements Closeable {
 					try {
 						Thread.sleep(TIMEOUT_BETWEEN_CONNECTION_ATTEMPTS);
 					}
-					catch (InterruptedException exc) {
-					}
+					catch (InterruptedException exc) {}
 				}
 				mkdir(e.getFile());
 				noSuchFileExceptionThrown++;
 				continue;
 			}
-		} while(true);
+		}
+		while (true);
 	}
 
 	public long size(String lfile) throws JSchException, IOException {
 		AckowledgementChecker ack = new AckowledgementChecker();
 		// exec 'scp -f rfile' remotely
 
-		lfile=lfile.replace("'", "'\"'\"'");
-		lfile="'"+lfile+"'";
+		lfile = lfile.replace("'", "'\"'\"'");
+		lfile = "'" + lfile + "'";
 
 		String command = "scp -f " + lfile;
 		Channel channel = getConnectedSession().openChannel("exec");
@@ -346,73 +307,10 @@ public class ScpClient implements Closeable {
 		return null;
 	}
 
-	@Override
-	public void close() {
-		if (session != null && session.isConnected()) {
-			// log.info("disconnect");
-			try(DoActionEventualy actionEventualy = new DoActionEventualy(TIMEOUT_BETWEEN_CONNECTION_ATTEMPTS, this::interruptSessionThread)){
-				session.disconnect();
-			}
-		}
-		session = null;
-	}
-
-	private void interruptSessionThread() {
-		try {
-			Field f=  session.getClass().getDeclaredField("connectThread");
-			if(!f.isAccessible()) {
-				f.setAccessible(true);
-				Thread thread = (Thread) f.get(session);
-				thread.interrupt();
-			}
-		}
-		catch (NoSuchFieldException | SecurityException | IllegalArgumentException
-				| IllegalAccessException exc)
-		{
-			log.error(exc.getMessage(), exc);
-		}
-	}
-
 	private int getBufferSize() {
 		return BUFFER_SIZE;
 	}
 
-	private Session getConnectedSession() throws JSchException {
-		if (session == null) {
-			session = jsch.getSession(username, hostName, port);
-
-			UserInfo ui = new P_UserInfo();
-
-			session.setUserInfo(ui);
-		}
-		int connectRetry = 0;
-		long timoutBetweenRetry = TIMEOUT_BETWEEN_CONNECTION_ATTEMPTS;
-		while (!session.isConnected()) {
-			try {
-				session.connect();
-			}
-			catch(JSchException e) {
-				if(e.getMessage().contains("Auth fail") || e.getMessage().contains("Packet corrupt")) {
-					if(connectRetry < MAX_NUMBER_OF_CONNECTION_ATTEMPTS) {
-						connectRetry++;
-						try {
-							Thread.sleep(timoutBetweenRetry);
-							timoutBetweenRetry *= 2;
-						}
-						catch (InterruptedException exc) {
-							log.info("Interruption detected");
-							throw new JSchException(exc.getMessage(), exc);
-						}
-						continue;
-					} 
-					e = new AuthFailException(e.getMessage(), e); 
-				}
-				throw e;
-			}
-		}
-		return session;
-	}
-
 	private void scp2Server(InputStream is, String fileName, long length,
 		long lastModified, TransferFileProgress progress) throws JSchException,
 		IOException, InterruptedIOException
@@ -421,9 +319,10 @@ public class ScpClient implements Closeable {
 		boolean ptimestamp = false;
 		// exec 'scp -t rfile' remotely
 
-		fileName=fileName.replace("'", "'\"'\"'");
+		fileName = fileName.replace("'", "'\"'\"'");
 
-		String command = "scp " + (ptimestamp ? "-p" : "") + " -t '" + fileName + "'";
+		String command = "scp " + (ptimestamp ? "-p" : "") + " -t '" + fileName +
+			"'";
 		Channel channel = getConnectedSession().openChannel("exec");
 		((ChannelExec) channel).setCommand(command);
 		// get I/O streams for remote scp
@@ -434,7 +333,7 @@ public class ScpClient implements Closeable {
 			if (!ack.checkAck(in)) {
 				throw new JSchException(constructExceptionText(ack));
 			}
-	
+
 			if (ptimestamp) {
 				command = "T " + (lastModified / 1000) + " 0";
 				// The access time should be sent here,
@@ -446,7 +345,7 @@ public class ScpClient implements Closeable {
 					throw new JSchException(constructExceptionText(ack));
 				}
 			}
-	
+
 			// send "C0644 filesize filename", where filename should not include '/'
 			long filesize = length;
 			command = "C0644 " + filesize + " ";
@@ -479,7 +378,7 @@ public class ScpClient implements Closeable {
 				throw new JSchException(constructExceptionText(ack));
 			}
 			out.close();
-	
+
 		}
 		catch (ClosedByInterruptException e) {
 			Thread.interrupted();
@@ -491,12 +390,14 @@ public class ScpClient implements Closeable {
 	}
 
 	private int mkdir(String file) throws JSchException {
-		ChannelExec channel = (ChannelExec) getConnectedSession().openChannel("exec");
+		ChannelExec channel = (ChannelExec) getConnectedSession().openChannel(
+			"exec");
 		channel.setCommand("mkdir -p '" + file + "'");
 		try {
 			channel.connect();
 			return channel.getExitStatus();
-		} finally {
+		}
+		finally {
 			channel.disconnect();
 		}
 	}
@@ -509,38 +410,4 @@ public class ScpClient implements Closeable {
 		return fileName.substring(0, index);
 	}
 
-	private class P_UserInfo implements UserInfo {
-
-		@Override
-		public String getPassphrase() {
-			return null;
-		}
-
-		@Override
-		public String getPassword() {
-			return null;
-		}
-
-		@Override
-		public boolean promptPassword(String message) {
-			return false;
-		}
-
-		@Override
-		public boolean promptPassphrase(String message) {
-			return false;
-		}
-
-		@Override
-		public boolean promptYesNo(String message) {
-			return true;
-		}
-
-		@Override
-		public void showMessage(String message) {}
-
-	}
-
-	
-	
 }
diff --git a/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/SshCommandClient.java b/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/SshCommandClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b3ab613800f32631ae07f84885a9d83e874baa2
--- /dev/null
+++ b/java-scpclient/src/main/java/cz/it4i/fiji/scpclient/SshCommandClient.java
@@ -0,0 +1,75 @@
+
+package cz.it4i.fiji.scpclient;
+
+import com.jcraft.jsch.ChannelExec;
+import com.jcraft.jsch.Identity;
+import com.jcraft.jsch.JSchException;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SshCommandClient extends AbstractBaseSshClient {
+
+	private final static Logger log = LoggerFactory.getLogger(
+		SshCommandClient.class);
+
+	public SshCommandClient(String hostName, String username,
+		byte[] privateKeyFile) throws JSchException
+	{
+		super(hostName, username, privateKeyFile);
+	}
+
+	public SshCommandClient(String hostName, String username,
+		Identity privateKeyFile) throws JSchException
+	{
+		super(hostName, username, privateKeyFile);
+	}
+
+	public SshCommandClient(String hostName, String userName, String keyFile,
+		String pass) throws JSchException
+	{
+		super(hostName, userName, keyFile, pass);
+	}
+
+	public String executeCommand(String command) {
+		StringBuilder sb = new StringBuilder();
+		try {
+			ChannelExec channelExec = (ChannelExec) getConnectedSession().openChannel(
+				"exec");
+
+			InputStream in = channelExec.getInputStream();
+
+			channelExec.setCommand(command);
+			channelExec.connect();
+
+			BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+			String line;
+
+			while ((line = reader.readLine()) != null) {
+				sb.append(line).append('\n');
+			}
+
+			int exitStatus = channelExec.getExitStatus();
+			channelExec.disconnect();
+
+			if (exitStatus < 0) {
+				log.debug("Done, but exit status not set!");
+			}
+			else if (exitStatus > 0) {
+				log.debug("Done, but with error!");
+			}
+			else {
+				log.debug("Done!");
+			}
+		}
+		catch (Exception e) {
+			log.error("Error: ", e);
+			throw new RuntimeException(e);
+		}
+		return sb.toString();
+	}
+}