package org.cloudifysource.esc.installer.remoteExec;
/*******************************************************************************
* Copyright (c) 2011 GigaSpaces Technologies Ltd. All rights reserved
*
* Licensed 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.
*******************************************************************************/
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
/*********
* Wrapper for command line calls to Windows powershell.
*
* @author barakme
* @since 2.5.0
*
*/
public class PowershellClient {
private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(PowershellClient.class
.getName());
/*******
* Interface for clients that want to get the powershell output line-by-line
* rather then wait for the process to terminate and only then get the
* entire output.
*
* @author barakme
*
*/
public interface PowerShellOutputListener {
/******
* Called when a new line is read from the powershell output.
*
* @param line
* the new line.
*/
void onPowerShellOutput(final String line);
}
private final List<PowerShellOutputListener> outputListeners =
new java.util.LinkedList<PowershellClient.PowerShellOutputListener>();
private static volatile Boolean powerShellInstalled;
private static final String[] POWERSHELL_INSTALLED_COMMAND = new String[] { "powershell.exe", "-inputformat",
"none", "-?" };
private static final String POWERSHELL_CLIENT_SCRIPT = "bootstrap-client.ps1";
/********
* Adds an output listener.
*
* @param listener
* the listener.
*/
public void addOutputListener(final PowerShellOutputListener listener) {
this.outputListeners.add(listener);
}
private void notifyListeners(final String line) {
for (final PowerShellOutputListener listener : this.outputListeners) {
listener.onPowerShellOutput(line);
}
}
// TODO: add timeout
private String readProcessOutput(final Process p, final boolean notifyOnOutput) throws PowershellClientException {
final StringBuilder sb = new StringBuilder();
final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
try {
final String newline = System.getProperty("line.separator");
while (true) {
final String line = reader.readLine();
if (line == null) {
break;
}
// this.publishEvent("powershell_output_line", line);
if (notifyOnOutput) {
notifyListeners(line);
}
logger.fine(line);
sb.append(line).append(newline);
}
} catch (final IOException e) {
throw new PowershellClientException("Failed to read output of powershell process", e);
} finally {
try {
reader.close();
} catch (final IOException e) {
logger.log(Level.SEVERE, "Error while closing process input stream: " + e.getMessage(), e);
}
}
return sb.toString();
}
private List<String> getPowershellCommandLine(final String target, final String username, final String password,
final String command, final String localDir) throws PowershellClientException {
final File clientScriptFile = new File(localDir, POWERSHELL_CLIENT_SCRIPT);
if (!clientScriptFile.exists()) {
throw new PowershellClientException(
"Could not find expected powershell client script in local directory. Was expecting file: "
+ clientScriptFile.getAbsolutePath());
}
final String[] commandLineParts = { "powershell.exe", "-inputformat", "none", "-File",
clientScriptFile.getAbsolutePath(), "-target", target, "-password", quoteString(password), "-username",
quoteString(username), "-command", quoteString(command) };
return Arrays.asList(commandLineParts);
}
private String quoteString(final String input) {
return "\"" + input + "\"";
}
// TODO - add timeout. Use commons exec package to add watchdog
/********
* Executes a remote powershell command. using the client script.
*
* @param targetHost
* ip or host name of target machine.
* @param command
* the command to execute.
* @param username
* the username for the remote machine.
* @param password
* the password for the remote machine.
* @param localDir
* the local directory where the client script can be found.
* @return the output of the powershell process.
* @throws PowershellClientException
* if there was a problem executing the script.
* @throws InterruptedException .
*/
public String invokeRemotePowershellCommand(final String targetHost, final String command, final String username,
final String password, final String localDir) throws PowershellClientException, InterruptedException {
checkPowershellInstalled();
final List<String> fullCommand = getPowershellCommandLine(targetHost, username, password, command, localDir);
final ProcessBuilder pb = new ProcessBuilder(fullCommand);
pb.redirectErrorStream(true);
try {
final Process p = pb.start();
final String output = readProcessOutput(p, true);
final int exitCode = p.waitFor();
if (exitCode != 0) {
throw new PowershellClientException("Remote installation failed with exit code: " + exitCode
+ ". Execution output: " + output);
}
return output;
} catch (final IOException e) {
throw new PowershellClientException("Failed to invoke remote installation: " + e.getMessage(), e);
}
}
private void checkPowershellInstalled() throws PowershellClientException, InterruptedException {
if (powerShellInstalled != null) {
if (powerShellInstalled.booleanValue()) {
return;
}
throw new PowershellClientException(
"powershell.exe is not on installed, or is not available on the system path. "
+ "Powershell is required on both client and server for Cloudify to work on Windows. ");
}
logger.fine("Checking if powershell is installed using: " + Arrays.toString(POWERSHELL_INSTALLED_COMMAND));
final ProcessBuilder pb = new ProcessBuilder(Arrays.asList(POWERSHELL_INSTALLED_COMMAND));
pb.redirectErrorStream(true);
Process p;
try {
p = pb.start();
} catch (final IOException e) {
throw new PowershellClientException("Failed to start powershell - it may not be installed", e);
}
final String output = readProcessOutput(p, false);
logger.fine("Finished reading output");
final int retval = p.waitFor();
logger.fine("Powershell installed command exit value: " + retval);
if (retval != 0) {
throw new PowershellClientException(
"powershell.exe is not on installed, or is not available on the system path. "
+ "Powershell is required on both client and server for Cloudify to work on Windows. "
+ "Execution result: " + output);
}
powerShellInstalled = Boolean.TRUE;
}
}