Skip to content

Commit

Permalink
#24 Support for Linux based OS
Browse files Browse the repository at this point in the history
  • Loading branch information
frimtec committed Dec 18, 2020
1 parent f9ae3ee commit bcac4c2
Show file tree
Hide file tree
Showing 12 changed files with 446 additions and 238 deletions.
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ API to easily execute PowerShell commands and scripts from Java.

## Supported platforms
* Windows
* Linux (with installed PowerShell)

## Example
Call PowerShell commands or scripts like this:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.github.frimtec.libraries.jpse;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.nio.charset.StandardCharsets.UTF_16LE;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;

abstract class AbstractPowerShellExecutor implements PowerShellExecutor {

private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder();

private static final int INITIAL_STREAM_BUFFER_SIZE = 1000;
private static final String JPSE_GLOBBER_THREAD_NAME = "JPSE-Gobbler";

private final String executionCommand;
private final Path tempPath;

AbstractPowerShellExecutor(Path tempPath, String executionCommand) {
this.tempPath = tempPath;
this.executionCommand = executionCommand;
if (tempPath != null && !Files.exists(tempPath)) {
try {
Files.createDirectories(tempPath);
} catch (IOException e) {
throw new UncheckedIOException("Cannot create given temp folder: " + tempPath.toAbsolutePath(), e);
}
}
}

@Override
public ExecutionResult execute(String command) {
String encodedCommand = BASE64_ENCODER.encodeToString(command.getBytes(UTF_16LE));
return execute(createProcessBuilder(Arrays.asList(this.executionCommand, "-EncodedCommand", encodedCommand)));
}

@Override
public ExecutionResult execute(Path script, Map<String, String> arguments) {
List<String> commandLine = new ArrayList<>(Arrays.asList(this.executionCommand, "-File", script.toString()));
commandLine.addAll(arguments.entrySet()
.stream()
.flatMap(entry -> Stream.of("-" + entry.getKey(), "\"" + entry.getValue() + "\""))
.collect(Collectors.toList()));
return execute(createProcessBuilder(commandLine));
}

@Override
public ExecutionResult execute(InputStream script, Map<String, String> arguments) {
try {
String prefix = "java-power-shell-";
String suffix = ".ps1";
Path tempFile = this.tempPath != null ? Files.createTempFile(this.tempPath, prefix, suffix) : Files.createTempFile(prefix, suffix);
try {
Files.write(tempFile, readStream(script).getBytes(UTF_8), TRUNCATE_EXISTING);
return execute(tempFile, arguments);
} finally {
Files.deleteIfExists(tempFile);
}
} catch (IOException e) {
throw new UncheckedIOException("Cannot handle script", e);
}
}

@Override
public Optional<Version> version() {
try {
String[] versionParts = execute("'' + $PSVersionTable.PSVersion.Major + '.' + $PSVersionTable.PSVersion.Minor").getStandardOutput().split("\\.");
return Optional.of(new Version(
Integer.parseInt(versionParts[0]),
Integer.parseInt(versionParts[1])
));
} catch (Exception e) {
return Optional.empty();
}
}

protected ProcessBuilder createProcessBuilder(List<String> commandLine) {
return new ProcessBuilder(commandLine);
}

private ExecutionResult execute(ProcessBuilder processBuilder) {
StringWriter outputStringWriter = new StringWriter(INITIAL_STREAM_BUFFER_SIZE);
StringWriter errorStringWriter = new StringWriter(INITIAL_STREAM_BUFFER_SIZE);
PrintWriter outputBuffer = new PrintWriter(outputStringWriter);
PrintWriter errorBuffer = new PrintWriter(errorStringWriter);
try {
Process process = processBuilder.start();
StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), outputBuffer::println);
StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), errorBuffer::println);
List<Thread> threads = Arrays.asList(
new Thread(outputGobbler, JPSE_GLOBBER_THREAD_NAME),
new Thread(errorGobbler, JPSE_GLOBBER_THREAD_NAME)
);
threads.forEach(thread -> {
thread.setDaemon(true);
thread.start();
});
for (StreamGobbler gobbler : Arrays.asList(outputGobbler, errorGobbler)) {
gobbler.waitTillFinished();
}
return new ExecutionResultImpl(process.waitFor(), getOutputFromWriter(outputStringWriter), getOutputFromWriter(errorStringWriter));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Wait for process termination interrupted", e);
} catch (IOException e) {
throw new UncheckedIOException("Powershell cannot be executed", e);
}
}

private String getOutputFromWriter(StringWriter outputStringWriter) {
return outputStringWriter.getBuffer().toString().trim();
}

private String readStream(InputStream inputStream) {
return new BufferedReader(new InputStreamReader(inputStream, UTF_8))
.lines()
.collect(Collectors.joining("\n"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.github.frimtec.libraries.jpse;

import java.nio.file.Path;

class LinuxPowerShellExecutor extends AbstractPowerShellExecutor {

private static final String POWER_SHELL_CMD = "pwsh";

LinuxPowerShellExecutor(Path tempPath) {
super(tempPath, POWER_SHELL_CMD);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;

/**
* Executor for PowerShell commands and scripts.
Expand Down Expand Up @@ -37,8 +38,17 @@ public interface PowerShellExecutor {
*/
ExecutionResult execute(InputStream script, Map<String, String> arguments);

/**
* Returns the version of the available power shell environment.
*
* @return version of the available power shell environment or empty if powershell is not available
* @since 1.3.0
*/
Optional<Version> version();

/**
* Creates a power shell executor using the default temp path to create and execute temporary scripts.
*
* @return power shell executor
*/
static PowerShellExecutor instance() {
Expand All @@ -52,7 +62,13 @@ static PowerShellExecutor instance() {
* @return power shell executor
*/
static PowerShellExecutor instance(Path tempPath) {
String osName = System.getProperty("os.name");
return osName.toLowerCase().startsWith("windows") ? new WindowsPowerShellExecutor(tempPath) : new UnsupportedOsPowerShellExecutor(osName);
String osName = System.getProperty("os.name").toLowerCase();
if (osName.startsWith("win")) {
return new WindowsPowerShellExecutor(tempPath);
} else if (osName.contains("nix") || osName.contains("nux")) {
return new LinuxPowerShellExecutor(tempPath);
} else {
return new UnsupportedOsPowerShellExecutor(osName);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
package com.github.frimtec.libraries.jpse;

import java.io.InputStream;
import java.nio.file.Path;
import java.util.Map;

final class UnsupportedOsPowerShellExecutor implements PowerShellExecutor {
private final String osName;

UnsupportedOsPowerShellExecutor(String osName) {
this.osName = osName;
}

@Override
public ExecutionResult execute(String command) {
throw new UnsupportedOperationException("Not supported on OS " + this.osName);
}

@Override
public ExecutionResult execute(Path script, Map<String, String> arguments) {
throw new UnsupportedOperationException("Not supported on OS " + this.osName);
}

@Override
public ExecutionResult execute(InputStream script, Map<String, String> arguments) {
throw new UnsupportedOperationException("Not supported on OS " + this.osName);
}
}
package com.github.frimtec.libraries.jpse;

import java.io.InputStream;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;

final class UnsupportedOsPowerShellExecutor implements PowerShellExecutor {
private final String osName;

UnsupportedOsPowerShellExecutor(String osName) {
this.osName = osName;
}

@Override
public ExecutionResult execute(String command) {
throw new UnsupportedOperationException("Not supported on OS " + this.osName);
}

@Override
public ExecutionResult execute(Path script, Map<String, String> arguments) {
throw new UnsupportedOperationException("Not supported on OS " + this.osName);
}

@Override
public ExecutionResult execute(InputStream script, Map<String, String> arguments) {
throw new UnsupportedOperationException("Not supported on OS " + this.osName);
}

@Override
public Optional<Version> version() {
return Optional.empty();
}
}
45 changes: 45 additions & 0 deletions src/main/java/com/github/frimtec/libraries/jpse/Version.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.github.frimtec.libraries.jpse;

/**
* Power shell version.
*
* @see PowerShellExecutor#version()
* @since 1.3.0
*/
public final class Version {
private final int major;
private final int minor;

Version(int major, int minor) {
this.major = major;
this.minor = minor;
}

/**
* Major version.
*
* @return major
*/
public int getMajor() {
return this.major;
}

/**
* Minor version.
*
* @return major
*/
public int getMinor() {
return this.minor;
}

/**
* Version string in the format "{@code <major>.<minor>}".
*
* @return version string
*/
@Override
public String toString() {
return String.format("%d.%d", this.major, this.minor);
}
}
Loading

0 comments on commit bcac4c2

Please sign in to comment.