From 1417606120ba0e8a421652296799884f7b5c1179 Mon Sep 17 00:00:00 2001 From: Martin Hoffmann Date: Fri, 28 Apr 2017 11:49:11 +0200 Subject: [PATCH 01/15] Update PowerShell.java Added methods for executing script from a BufferedReader. This is neccessary if you want to execute scripts that are bundled in a jar File (http://stackoverflow.com/a/8258308/3601364) +executeScript(BufferedReader srcReader) +executeScript(BufferedReader srcReader, String params) --- .../jpowershell/PowerShell.java | 157 ++++++++++++------ 1 file changed, 110 insertions(+), 47 deletions(-) diff --git a/src/main/java/com/profesorfalken/jpowershell/PowerShell.java b/src/main/java/com/profesorfalken/jpowershell/PowerShell.java index 70d67a1..eddfaaf 100644 --- a/src/main/java/com/profesorfalken/jpowershell/PowerShell.java +++ b/src/main/java/com/profesorfalken/jpowershell/PowerShell.java @@ -225,70 +225,133 @@ public static PowerShellResponse executeSingleCommand(String command) { return response; } - /** - * Executed the provided PowerShell script in PowerShell console and - * gets result. - * - * @param scriptPath the full paht of the script - * @return response with the output of the command - */ - public PowerShellResponse executeScript(String scriptPath) { - return executeScript(scriptPath, ""); - } + //Writes a temp powershell script file based on the srcReader + private File createWriteTempFile(BufferedReader srcReader) { - /** - * Executed the provided PowerShell script in PowerShell console and - * gets result. - * - * @param scriptPath the full path of the script - * @param params the parameters of the script - * @return response with the output of the command - */ - public PowerShellResponse executeScript(String scriptPath, String params) { - BufferedReader reader = null; - BufferedWriter writer = null; - + BufferedWriter tmpWriter = null; File tmpFile = null; + try { - File scriptToExecute = new File(scriptPath); - if (!scriptToExecute.exists()) { - return new PowerShellResponse(true, "Wrong script path: " + scriptToExecute, false); - } + tmpFile = File.createTempFile("psscript_" + new Date().getTime(), ".ps1"); if (tmpFile == null || !tmpFile.exists()) { - return new PowerShellResponse(true, "Cannot create temp script file", false); + return null; } - - reader = new BufferedReader(new FileReader(scriptToExecute)); - writer = new BufferedWriter(new FileWriter(tmpFile)); + + tmpWriter = new BufferedWriter(new FileWriter(tmpFile)); String line; - while ((line = reader.readLine()) != null) { - writer.write(line); - writer.newLine(); + while (srcReader != null && (line = srcReader.readLine()) != null) { + tmpWriter.write(line); + tmpWriter.newLine(); } - //Add end script line - writer.write("Write-Host \"" + END_SCRIPT_STRING + "\""); - } catch (FileNotFoundException fnfex) { - Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, "Unexpected error when processing PowerShell script", fnfex); + + // Add end script line + tmpWriter.write("Write-Host \"" + END_SCRIPT_STRING + "\""); } catch (IOException ioex) { - Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, "Unexpected error when processing PowerShell script", ioex); + Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, + "Unexpected error while writing temporary PowerShell script", ioex); } finally { try { - if (reader != null) { - reader.close(); + if (srcReader != null) { + srcReader.close(); } - if (writer != null) { - writer.close(); + if (tmpWriter != null) { + tmpWriter.close(); } } catch (IOException ex) { - Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, "Unexpected error when processing PowerShell script", ex); + Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, + "Unexpected error when processing temporary PowerShell script", ex); } } - - this.scriptMode = true; - return executeCommand(tmpFile.getAbsolutePath() + " " + params); - } + return tmpFile; + } + + /** + * Executed the provided PowerShell script in PowerShell console and gets + * result. + * + * @param scriptPath + * the full paht of the script + * @return response with the output of the command + */ + public PowerShellResponse executeScript(String scriptPath) { + return executeScript(scriptPath, ""); + } + + /** + * Executed the provided PowerShell script in PowerShell console and gets + * result. + * + * @param scriptPath + * the full path of the script + * @param params + * the parameters of the script + * @return response with the output of the command + */ + public PowerShellResponse executeScript(String scriptPath, String params) { + BufferedReader srcReader = null; + + File scriptToExecute = new File(scriptPath); + if (!scriptToExecute.exists()) { + return new PowerShellResponse(true, "Wrong script path: " + scriptToExecute, false); + } + + try { + srcReader = new BufferedReader(new FileReader(scriptToExecute)); + } catch (FileNotFoundException fnfex) { + Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, "Unexpected error when processing PowerShell script: file not found", fnfex); + } + + File tmpFile = createWriteTempFile(srcReader); + if (tmpFile != null) { + this.scriptMode = true; + + return executeCommand(tmpFile.getAbsolutePath() + " " + params); + } else { + return new PowerShellResponse(true, "Cannot create temp script file", false); + } + + } + + /** + * Executed the provided PowerShell script in PowerShell console and gets + * result. + * + * @param srcReader + * the script as BufferedReader (when loading File from jar) + * @return response with the output of the command + */ + public PowerShellResponse executeScript(BufferedReader srcReader) { + return executeScript(srcReader, ""); + } + + /** + * Executed the provided PowerShell script in PowerShell console and gets + * result. + * + * @param srcReader + * the script as BufferedReader (when loading File from jar) + * @param params + * the parameters of the script + * @return response with the output of the command + */ + public PowerShellResponse executeScript(BufferedReader srcReader, String params) { + + if(srcReader != null) { + File tmpFile = createWriteTempFile(srcReader); + if (tmpFile != null) { + this.scriptMode = true; + + return executeCommand(tmpFile.getAbsolutePath() + " " + params); + } else { + return new PowerShellResponse(true, "Cannot create temp script file!", false); + } + } else { + return new PowerShellResponse(true, "Script reader is invalid!", false); + } + + } /** * Closes all the resources used to maintain the PowerShell context From ff1478a43fcb9f7ff50bbeed19a5900f62f0c067 Mon Sep 17 00:00:00 2001 From: Martin Hoffmann Date: Fri, 28 Apr 2017 12:03:57 +0200 Subject: [PATCH 02/15] Fix encoding issue When starting a new ProcessBuilder process it does not load the defaultCharset (example: windows-1252), instead it loads some other charset. (example: ibm850). Read more about this issue here: http://stackoverflow.com/questions/22349139/utf-8-output-from-powershell/22350989 In the jPowerShell Code: Java tries to read the Stream with the defaultCharset or UTF-8 but both wont work due to the mismatch! Instead, we just need to set the Encoding that ProcessBuilder/Powershell uses to the defaultCharset. See the code of this commit how it can be done. --- src/main/java/com/profesorfalken/jpowershell/PowerShell.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/profesorfalken/jpowershell/PowerShell.java b/src/main/java/com/profesorfalken/jpowershell/PowerShell.java index eddfaaf..3bf5347 100644 --- a/src/main/java/com/profesorfalken/jpowershell/PowerShell.java +++ b/src/main/java/com/profesorfalken/jpowershell/PowerShell.java @@ -106,7 +106,9 @@ public PowerShell configuration(Map config) { //Initializes PowerShell console in which we will enter the commands private PowerShell initalize() throws PowerShellNotAvailableException { - ProcessBuilder pb = new ProcessBuilder("powershell.exe", "-NoExit", "-Command", "-"); + String cp = PowerShellCodepage.getIdentifierByCodePageName(Charset.defaultCharset().name()); + ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", "chcp", cp, ">>", "null", "&", "powershell.exe", "-NoExit", "-Command", "-"); + try { p = pb.start(); } catch (IOException ex) { From ac202d56e0f457df3bad4743435ec8d3eb6bd1d8 Mon Sep 17 00:00:00 2001 From: Martin Hoffmann Date: Fri, 28 Apr 2017 12:06:10 +0200 Subject: [PATCH 03/15] Create PowerShellCodepage.java File for Codepage lookup generated from this source: https://msdn.microsoft.com/de-de/library/windows/desktop/dd317756(v=vs.85).aspx --- .../jpowershell/PowerShellCodepage.java | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 src/main/java/com/profesorfalken/jpowershell/PowerShellCodepage.java diff --git a/src/main/java/com/profesorfalken/jpowershell/PowerShellCodepage.java b/src/main/java/com/profesorfalken/jpowershell/PowerShellCodepage.java new file mode 100644 index 0000000..1c593ea --- /dev/null +++ b/src/main/java/com/profesorfalken/jpowershell/PowerShellCodepage.java @@ -0,0 +1,178 @@ +package com.profesorfalken.jpowershell; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +public class PowerShellCodepage { + + static Map codePages = new HashMap(); + static { + codePages.put("37", "IBM037"); + codePages.put("437", "IBM437"); + codePages.put("500", "IBM500"); + codePages.put("708", "ASMO-708"); + codePages.put("709", ""); + codePages.put("710", ""); + codePages.put("720", "DOS-720"); + codePages.put("737", "ibm737"); + codePages.put("775", "ibm775"); + codePages.put("850", "ibm850"); + codePages.put("852", "ibm852"); + codePages.put("855", "IBM855"); + codePages.put("857", "ibm857"); + codePages.put("858", "IBM00858"); + codePages.put("860", "IBM860"); + codePages.put("861", "ibm861"); + codePages.put("862", "DOS-862"); + codePages.put("863", "IBM863"); + codePages.put("864", "IBM864"); + codePages.put("865", "IBM865"); + codePages.put("866", "cp866"); + codePages.put("869", "ibm869"); + codePages.put("870", "IBM870"); + codePages.put("874", "windows-874"); + codePages.put("875", "cp875"); + codePages.put("932", "shift_jis"); + codePages.put("936", "gb2312"); + codePages.put("949", "ks_c_5601-1987"); + codePages.put("950", "big5"); + codePages.put("1026", "IBM1026"); + codePages.put("1047", "IBM01047"); + codePages.put("1140", "IBM01140"); + codePages.put("1141", "IBM01141"); + codePages.put("1142", "IBM01142"); + codePages.put("1143", "IBM01143"); + codePages.put("1144", "IBM01144"); + codePages.put("1145", "IBM01145"); + codePages.put("1146", "IBM01146"); + codePages.put("1147", "IBM01147"); + codePages.put("1148", "IBM01148"); + codePages.put("1149", "IBM01149"); + codePages.put("1200", "utf-16"); + codePages.put("1201", "unicodeFFFE"); + codePages.put("1250", "windows-1250"); + codePages.put("1251", "windows-1251"); + codePages.put("1252", "windows-1252"); + codePages.put("1253", "windows-1253"); + codePages.put("1254", "windows-1254"); + codePages.put("1255", "windows-1255"); + codePages.put("1256", "windows-1256"); + codePages.put("1257", "windows-1257"); + codePages.put("1258", "windows-1258"); + codePages.put("1361", "Johab"); + codePages.put("10000", "macintosh"); + codePages.put("10001", "x-mac-japanese"); + codePages.put("10002", "x-mac-chinesetrad"); + codePages.put("10003", "x-mac-korean"); + codePages.put("10004", "x-mac-arabic"); + codePages.put("10005", "x-mac-hebrew"); + codePages.put("10006", "x-mac-greek"); + codePages.put("10007", "x-mac-cyrillic"); + codePages.put("10008", "x-mac-chinesesimp"); + codePages.put("10010", "x-mac-romanian"); + codePages.put("10017", "x-mac-ukrainian"); + codePages.put("10021", "x-mac-thai"); + codePages.put("10029", "x-mac-ce"); + codePages.put("10079", "x-mac-icelandic"); + codePages.put("10081", "x-mac-turkish"); + codePages.put("10082", "x-mac-croatian"); + codePages.put("12000", "utf-32"); + codePages.put("12001", "utf-32BE"); + codePages.put("20000", "x-Chinese_CNS"); + codePages.put("20001", "x-cp20001"); + codePages.put("20002", "x_Chinese-Eten"); + codePages.put("20003", "x-cp20003"); + codePages.put("20004", "x-cp20004"); + codePages.put("20005", "x-cp20005"); + codePages.put("20105", "x-IA5"); + codePages.put("20106", "x-IA5-German"); + codePages.put("20107", "x-IA5-Swedish"); + codePages.put("20108", "x-IA5-Norwegian"); + codePages.put("20127", "us-ascii"); + codePages.put("20261", "x-cp20261"); + codePages.put("20269", "x-cp20269"); + codePages.put("20273", "IBM273"); + codePages.put("20277", "IBM277"); + codePages.put("20278", "IBM278"); + codePages.put("20280", "IBM280"); + codePages.put("20284", "IBM284"); + codePages.put("20285", "IBM285"); + codePages.put("20290", "IBM290"); + codePages.put("20297", "IBM297"); + codePages.put("20420", "IBM420"); + codePages.put("20423", "IBM423"); + codePages.put("20424", "IBM424"); + codePages.put("20833", "x-EBCDIC-KoreanExtended"); + codePages.put("20838", "IBM-Thai"); + codePages.put("20866", "koi8-r"); + codePages.put("20871", "IBM871"); + codePages.put("20880", "IBM880"); + codePages.put("20905", "IBM905"); + codePages.put("20924", "IBM00924"); + codePages.put("20932", "EUC-JP"); + codePages.put("20936", "x-cp20936"); + codePages.put("20949", "x-cp20949"); + codePages.put("21025", "cp1025"); + codePages.put("21027", ""); + codePages.put("21866", "koi8-u"); + codePages.put("28591", "iso-8859-1"); + codePages.put("28592", "iso-8859-2"); + codePages.put("28593", "iso-8859-3"); + codePages.put("28594", "iso-8859-4"); + codePages.put("28595", "iso-8859-5"); + codePages.put("28596", "iso-8859-6"); + codePages.put("28597", "iso-8859-7"); + codePages.put("28598", "iso-8859-8"); + codePages.put("28599", "iso-8859-9"); + codePages.put("28603", "iso-8859-13"); + codePages.put("28605", "iso-8859-15"); + codePages.put("29001", "x-Europa"); + codePages.put("38598", "iso-8859-8-i"); + codePages.put("50220", "iso-2022-jp"); + codePages.put("50221", "csISO2022JP"); + codePages.put("50222", "iso-2022-jp"); + codePages.put("50225", "iso-2022-kr"); + codePages.put("50227", "x-cp50227"); + codePages.put("50229", ""); + codePages.put("50930", ""); + codePages.put("50931", ""); + codePages.put("50933", ""); + codePages.put("50935", ""); + codePages.put("50936", ""); + codePages.put("50937", ""); + codePages.put("50939", ""); + codePages.put("51932", "euc-jp"); + codePages.put("51936", "EUC-CN"); + codePages.put("51949", "euc-kr"); + codePages.put("51950", ""); + codePages.put("52936", "hz-gb-2312"); + codePages.put("54936", "GB18030"); + codePages.put("57002", "x-iscii-de"); + codePages.put("57003", "x-iscii-be"); + codePages.put("57004", "x-iscii-ta"); + codePages.put("57005", "x-iscii-te"); + codePages.put("57006", "x-iscii-as"); + codePages.put("57007", "x-iscii-or"); + codePages.put("57008", "x-iscii-ka"); + codePages.put("57009", "x-iscii-ma"); + codePages.put("57010", "x-iscii-gu"); + codePages.put("57011", "x-iscii-pa"); + codePages.put("65000", "utf-7"); + codePages.put("65001", "utf-8"); + } + + public static String getCodePageNameByIdetifier(String cpIdentifier) { + return codePages.get(cpIdentifier); + } + + public static String getIdentifierByCodePageName(String cpName) { + for (Entry codePage : codePages.entrySet()) { + if (codePage.getValue().equals(cpName)) { + return codePage.getKey(); + } + } + return ""; + } + +} From 1255f9f49d4431ef71da1730dc98b03b0d50d49c Mon Sep 17 00:00:00 2001 From: Martin Hoffmann Date: Fri, 28 Apr 2017 12:55:19 +0200 Subject: [PATCH 04/15] Missing import Whops.. forgot that while editing directly in the Webbrowser.. --- src/main/java/com/profesorfalken/jpowershell/PowerShell.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/profesorfalken/jpowershell/PowerShell.java b/src/main/java/com/profesorfalken/jpowershell/PowerShell.java index 3bf5347..d8e8c1a 100644 --- a/src/main/java/com/profesorfalken/jpowershell/PowerShell.java +++ b/src/main/java/com/profesorfalken/jpowershell/PowerShell.java @@ -26,6 +26,7 @@ import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; import java.util.Date; import java.util.Map; import java.util.concurrent.Callable; From bcbc0f87b630f81fef74217d658d97bda7ffa9b1 Mon Sep 17 00:00:00 2001 From: Martin Hoffmann Date: Fri, 28 Apr 2017 13:14:32 +0200 Subject: [PATCH 05/15] Update README.md Add description for new Methods --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index b875394..ddfebb9 100644 --- a/README.md +++ b/README.md @@ -119,3 +119,22 @@ In order to execute a PowerShell Script it is recommended to use the executeScri powerShell.close(); } ``` +#### Executing PowerShell Scripts packaged inside jar #### + +In order to execute a PowerShell Script that is bundled inside a jar you must use a BufferedReader to load the resource: + +```java + PowerShell powerShell = PowerShell.openSession(); + String script = "resourcePath/MyScript.ps1" + String scriptParams = "-Parameter value" + + //Read the resource + BufferedReader srcReader = new BufferedReader( + new InputStreamReader(getClass().getResourceAsStream(commmandOrScript))); + + if (scriptParams != null && !scriptParams.equals("")) { + response = powerShell.executeScript(srcReader, scriptParams); + } else { + response = powerShell.executeScript(srcReader); + } +``` From 55036b64934bf709a44e848b7d8794638dd6eeb9 Mon Sep 17 00:00:00 2001 From: profesorfalken Date: Fri, 5 May 2017 13:26:00 +0200 Subject: [PATCH 06/15] Prepare 1.8 version with multi-encoding support --- pom.xml | 2 +- .../jpowershell/PowerShell.java | 16 ++----- .../jpowershell/PowerShellCodepage.java | 47 ++++++++++++++++--- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/pom.xml b/pom.xml index dac6c33..8025436 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.profesorfalken jPowerShell - 1.7-SNAPSHOT + 1.8-SNAPSHOT jar jPowerShell diff --git a/src/main/java/com/profesorfalken/jpowershell/PowerShell.java b/src/main/java/com/profesorfalken/jpowershell/PowerShell.java index d8e8c1a..7964f98 100644 --- a/src/main/java/com/profesorfalken/jpowershell/PowerShell.java +++ b/src/main/java/com/profesorfalken/jpowershell/PowerShell.java @@ -107,8 +107,8 @@ public PowerShell configuration(Map config) { //Initializes PowerShell console in which we will enter the commands private PowerShell initalize() throws PowerShellNotAvailableException { - String cp = PowerShellCodepage.getIdentifierByCodePageName(Charset.defaultCharset().name()); - ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", "chcp", cp, ">>", "null", "&", "powershell.exe", "-NoExit", "-Command", "-"); + String codePage = PowerShellCodepage.getIdentifierByCodePageName(Charset.defaultCharset().name()); + ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", "chcp", codePage, ">>", "null", "&", "powershell.exe", "-NoExit", "-Command", "-"); try { p = pb.start(); @@ -116,16 +116,8 @@ private PowerShell initalize() throws PowerShellNotAvailableException { throw new PowerShellNotAvailableException( "Cannot execute PowerShell.exe. Please make sure that it is installed in your system", ex); } - - try { - commandWriter - = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(p.getOutputStream()), "UTF-8"), true); - } catch (UnsupportedEncodingException e) { - Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, "Error setting writer encoding", e); - //Set the writer with encoding by default - commandWriter - = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(p.getOutputStream())), true); - } + + commandWriter = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(p.getOutputStream())), true); //Init thread pool this.threadpool = Executors.newFixedThreadPool(this.maxThreads); diff --git a/src/main/java/com/profesorfalken/jpowershell/PowerShellCodepage.java b/src/main/java/com/profesorfalken/jpowershell/PowerShellCodepage.java index 1c593ea..4e2c85a 100644 --- a/src/main/java/com/profesorfalken/jpowershell/PowerShellCodepage.java +++ b/src/main/java/com/profesorfalken/jpowershell/PowerShellCodepage.java @@ -1,9 +1,29 @@ +/* + * 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. + */ package com.profesorfalken.jpowershell; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +/** + * Enum that contains possible CodePage values needed to correctly set the encoding in a windows console session
+ * https://msdn.microsoft.com/de-de/library/windows/desktop/dd317756(v=vs.85).aspx + * + * @author Harinus + * + */ public class PowerShellCodepage { static Map codePages = new HashMap(); @@ -162,17 +182,32 @@ public class PowerShellCodepage { codePages.put("65001", "utf-8"); } + /** + * Get the encoding value from CodePage + * + * @param cpIdentifier encoding value + * @return + */ public static String getCodePageNameByIdetifier(String cpIdentifier) { return codePages.get(cpIdentifier); } + /** + * Get the CodePage code from encoding value + * + * @param cpName + * @return + */ public static String getIdentifierByCodePageName(String cpName) { - for (Entry codePage : codePages.entrySet()) { - if (codePage.getValue().equals(cpName)) { - return codePage.getKey(); - } - } - return ""; + if (cpName != null) { + for (Entry codePage : codePages.entrySet()) { + if (codePage.getValue().toLowerCase().equals(cpName.toLowerCase())) { + return codePage.getKey(); + } + } + } + //Default UTF-8 + return "65001"; } } From a679b958b809999e197d9a86bf7f789fe8fc63bb Mon Sep 17 00:00:00 2001 From: Martin Hoffmann Date: Fri, 5 May 2017 17:31:48 +0200 Subject: [PATCH 07/15] Update README.md Corrected Typo.. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ddfebb9..7d3a8c5 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ In order to execute a PowerShell Script that is bundled inside a jar you must us //Read the resource BufferedReader srcReader = new BufferedReader( - new InputStreamReader(getClass().getResourceAsStream(commmandOrScript))); + new InputStreamReader(getClass().getResourceAsStream(script))); if (scriptParams != null && !scriptParams.equals("")) { response = powerShell.executeScript(srcReader, scriptParams); From ec095fd31dde2ebfd8f536bb830662b953ac5929 Mon Sep 17 00:00:00 2001 From: profesorfalken Date: Wed, 10 May 2017 13:43:33 +0200 Subject: [PATCH 08/15] Refactor code for reuse and clean imports. --- .../jpowershell/PowerShell.java | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/profesorfalken/jpowershell/PowerShell.java b/src/main/java/com/profesorfalken/jpowershell/PowerShell.java index 7964f98..f3580c3 100644 --- a/src/main/java/com/profesorfalken/jpowershell/PowerShell.java +++ b/src/main/java/com/profesorfalken/jpowershell/PowerShell.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Javier Garcia Alonso. + * Copyright 2016-2017 Javier Garcia Alonso. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.Date; import java.util.Map; @@ -290,23 +289,15 @@ public PowerShellResponse executeScript(String scriptPath, String params) { File scriptToExecute = new File(scriptPath); if (!scriptToExecute.exists()) { return new PowerShellResponse(true, "Wrong script path: " + scriptToExecute, false); - } + } try { srcReader = new BufferedReader(new FileReader(scriptToExecute)); } catch (FileNotFoundException fnfex) { Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, "Unexpected error when processing PowerShell script: file not found", fnfex); } - - File tmpFile = createWriteTempFile(srcReader); - if (tmpFile != null) { - this.scriptMode = true; - - return executeCommand(tmpFile.getAbsolutePath() + " " + params); - } else { - return new PowerShellResponse(true, "Cannot create temp script file", false); - } - + + return executeScript(srcReader, params); } /** @@ -337,13 +328,13 @@ public PowerShellResponse executeScript(BufferedReader srcReader, String params) File tmpFile = createWriteTempFile(srcReader); if (tmpFile != null) { this.scriptMode = true; - return executeCommand(tmpFile.getAbsolutePath() + " " + params); - } else { + } else { return new PowerShellResponse(true, "Cannot create temp script file!", false); } } else { - return new PowerShellResponse(true, "Script reader is invalid!", false); + Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, "Script buffered reader is null!"); + return new PowerShellResponse(true, "Script buffered reader is null!", false); } } From 39ba594d1ebb7881db91d43a64fb8bb2f8d30321 Mon Sep 17 00:00:00 2001 From: profesorfalken Date: Wed, 10 May 2017 13:43:59 +0200 Subject: [PATCH 09/15] Add test for new bufferedReader script execution --- .../jpowershell/PowerShellTest.java | 72 ++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/profesorfalken/jpowershell/PowerShellTest.java b/src/test/java/com/profesorfalken/jpowershell/PowerShellTest.java index 0af6bf0..7a17fa1 100644 --- a/src/test/java/com/profesorfalken/jpowershell/PowerShellTest.java +++ b/src/test/java/com/profesorfalken/jpowershell/PowerShellTest.java @@ -1,10 +1,16 @@ package com.profesorfalken.jpowershell; +import java.io.BufferedReader; import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; import java.io.FileWriter; import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + import org.junit.Assert; import org.junit.Test; @@ -344,7 +350,7 @@ public void testLongLoop() throws Exception { } /** - * Test of long command + * Test of timeout * * @throws java.lang.Exception */ @@ -384,6 +390,68 @@ public void testRemote() throws Exception { powerShell.close(); } } + + @Test + public void testScript() throws Exception { + System.out.println("testScript"); + if (OSDetector.isWindows()) { + PowerShell powerShell = PowerShell.openSession(); + Map config = new HashMap(); + PowerShellResponse response = null; + + StringBuilder scriptContent = new StringBuilder(); + scriptContent.append("Write-Host \"First message\"").append(CRLF); + + try { + response = powerShell.configuration(config).executeScript(generateScript(scriptContent.toString())); + } finally { + powerShell.close(); + } + + Assert.assertNotNull("Response null!",response); + if (!response.getCommandOutput().contains("UnauthorizedAccess")) { + Assert.assertFalse("Is in error!", response.isError()); + Assert.assertFalse("Is timeout!", response.isTimeout()); + } + System.out.println(response.getCommandOutput()); + } + } + + @Test + public void testScriptByBufferedReader() throws Exception { + System.out.println("testScriptByBufferedReader"); + if (OSDetector.isWindows()) { + PowerShell powerShell = PowerShell.openSession(); + Map config = new HashMap(); + PowerShellResponse response = null; + + StringBuilder scriptContent = new StringBuilder(); + scriptContent.append("Write-Host \"First message\"").append(CRLF); + + BufferedReader srcReader = null; + try { + srcReader = new BufferedReader(new FileReader(generateScript(scriptContent.toString()))); + } catch (FileNotFoundException fnfex) { + Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, "Unexpected error when processing PowerShell script: file not found", fnfex); + } + + Assert.assertNotNull("Cannot create reader from temp file", srcReader); + + try { + response = powerShell.configuration(config).executeScript(srcReader); + } finally { + powerShell.close(); + } + + Assert.assertNotNull("Response null!",response); + if (!response.getCommandOutput().contains("UnauthorizedAccess")) { + Assert.assertFalse("Is in error!", response.isError()); + Assert.assertFalse("Is timeout!", response.isTimeout()); + } + System.out.println(response.getCommandOutput()); + } + } + @Test public void testLongScript() throws Exception { @@ -418,7 +486,7 @@ public void testLongScript() throws Exception { } /** - * Test of long command + * Test of configuration * * @throws java.lang.Exception */ From 9224a0d348efa5b31b1c545646b2199ede64fbac Mon Sep 17 00:00:00 2001 From: ProfesorFalken Date: Thu, 11 May 2017 18:48:48 +0200 Subject: [PATCH 10/15] Update to 1.8 version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8025436..cf3d0e0 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.profesorfalken jPowerShell - 1.8-SNAPSHOT + 1.8 jar jPowerShell From 03c955de00b4630557361d6e60d88cda5926fde4 Mon Sep 17 00:00:00 2001 From: JGA Date: Thu, 11 May 2017 19:25:10 +0200 Subject: [PATCH 11/15] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7d3a8c5..df82d35 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,18 @@ Simple Java API to interact with PowerShell console ## Installation ## -To install jPowerShell you can add the dependecy to your software project management tool: http://mvnrepository.com/artifact/com.profesorfalken/jPowerShell/1.7 +To install jPowerShell you can add the dependecy to your software project management tool: http://mvnrepository.com/artifact/com.profesorfalken/jPowerShell/1.8 For example, for Maven you have just to add to your pom.xml: com.profesorfalken jPowerShell - 1.7 + 1.8 Instead, you can direct download the JAR file and add it to your classpath. -https://repo1.maven.org/maven2/com/profesorfalken/jPowerShell/1.7/jPowerShell-1.7.jar +https://repo1.maven.org/maven2/com/profesorfalken/jPowerShell/1.8/jPowerShell-1.8.jar ## Basic Usage ## From f7982368f6f539494700e94872df2358e947270b Mon Sep 17 00:00:00 2001 From: Martin Hoffmann Date: Sat, 24 Jun 2017 16:23:31 +0200 Subject: [PATCH 12/15] Timeout Handling based on Timestamp Part #1 --- .../PowerShellCommandProcessor.java | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/profesorfalken/jpowershell/PowerShellCommandProcessor.java b/src/main/java/com/profesorfalken/jpowershell/PowerShellCommandProcessor.java index 38a24d6..1f46497 100644 --- a/src/main/java/com/profesorfalken/jpowershell/PowerShellCommandProcessor.java +++ b/src/main/java/com/profesorfalken/jpowershell/PowerShellCommandProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Javier Garcia Alonso. + * Copyright 2016-2017 Javier Garcia Alonso. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.time.Instant; import java.util.concurrent.Callable; import java.util.logging.Level; import java.util.logging.Logger; @@ -45,19 +46,27 @@ class PowerShellCommandProcessor implements Callable { private final long maxWait; private final int waitPause; + private long commandStart; + private boolean ready = false; + /** * Constructor that takes the output and the input of the PowerShell session * * @param commandWriter the input to the PowerShell console * @param inputStream the stream needed to read the command output */ - public PowerShellCommandProcessor(String name, InputStream inputStream, long maxWait, int waitPause, boolean scriptMode) { + public PowerShellCommandProcessor(String name, InputStream inputStream, long maxWait, int waitPause, boolean scriptMode, long commandStart) { this.reader = new BufferedReader(new InputStreamReader( inputStream)); this.name = name; this.maxWait = maxWait; this.waitPause = waitPause; this.scriptMode = scriptMode; + this.commandStart = commandStart; + } + + public boolean isReady() { + return this.ready; } /** @@ -112,17 +121,23 @@ private void readData(StringBuilder powerShellOutput) throws IOException { //Checks when we can start reading the output. Timeout if it takes too long in order to avoid hangs private boolean startReading() throws IOException, InterruptedException { - int timeWaiting = 0; + long commandStart = this.commandStart; + long elapsedTime = Instant.now().toEpochMilli() - commandStart; while (!this.reader.ready()) { Thread.sleep(this.waitPause); - timeWaiting += this.waitPause; - if ((timeWaiting > this.maxWait) || this.closed) { - this.timeout = timeWaiting > this.maxWait; + elapsedTime = Instant.now().toEpochMilli() - commandStart; + + if ((elapsedTime > maxWait)) { + this.timeout = true; + return false; + } else if (this.closed) { return false; - } + } } - return true; + this.ready = true; + return true; + } //Checks when we the reader can continue to read. From 3ef4e4f33012c4ac56a429e1f894c03be07e6402 Mon Sep 17 00:00:00 2001 From: Martin Hoffmann Date: Sat, 24 Jun 2017 16:35:24 +0200 Subject: [PATCH 13/15] Timeout Handling Part2 For timed out commands the stdin is blocked, the close of the child process must be forced. Note: Process has no ".getPid()" (available only in Java 9). Therefore using JNA to determine the PID of the child process where powershell is running. --- .../jpowershell/PowerShell.java | 93 ++++++++++++++++--- 1 file changed, 81 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/profesorfalken/jpowershell/PowerShell.java b/src/main/java/com/profesorfalken/jpowershell/PowerShell.java index f3580c3..88b8cdd 100644 --- a/src/main/java/com/profesorfalken/jpowershell/PowerShell.java +++ b/src/main/java/com/profesorfalken/jpowershell/PowerShell.java @@ -25,7 +25,9 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; +import java.lang.reflect.Field; import java.nio.charset.Charset; +import java.time.Instant; import java.util.Date; import java.util.Map; import java.util.concurrent.Callable; @@ -34,9 +36,14 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; +import com.sun.jna.Pointer; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.WinNT; + /** * Allows to open a session into PowerShell console and launch different * commands.
@@ -58,6 +65,7 @@ public class PowerShell { //Threaded session variables private boolean closed = false; private ExecutorService threadpool; + private boolean blocked = false; //Config values private int maxThreads = 3; @@ -150,10 +158,14 @@ public static PowerShell openSession() throws PowerShellNotAvailableException { * @return PowerShellResponse the information returned by powerShell */ public PowerShellResponse executeCommand(String command) { + + Instant instant = Instant.now(); + long timeStampMillis = instant.toEpochMilli(); + Callable commandProcessor = new PowerShellCommandProcessor("standard", - p.getInputStream(), this.maxWait, this.waitPause, this.scriptMode); + p.getInputStream(), this.maxWait, this.waitPause, this.scriptMode, timeStampMillis); Callable commandProcessorError = new PowerShellCommandProcessor("error", - p.getErrorStream(), this.maxWait, this.waitPause, false); + p.getErrorStream(), this.maxWait, this.waitPause, false, timeStampMillis); String commandOutput = ""; boolean isError = false; @@ -169,25 +181,47 @@ public PowerShellResponse executeCommand(String command) { //Launch command commandWriter.println(command); + long elapsedTime = 0; + try { - while (!result.isDone() && !resultError.isDone()) { + while (!result.isDone() && !resultError.isDone() && (elapsedTime < maxWait)) { Thread.sleep(this.waitPause); + elapsedTime = Instant.now().toEpochMilli() - timeStampMillis; } - if (result.isDone()) { - if (((PowerShellCommandProcessor) commandProcessor).isTimeout()) { - timeout = true; - } else { - commandOutput = result.get(); - } - } else { + + if (elapsedTime >= maxWait) { + //Command timed out but may have some output + timeout = true; + commandOutput = result.get(waitPause, TimeUnit.MILLISECONDS); + } + + if (result.isDone()) { + //Command finished successfully + + do { + //stderr might need another wait cycle to finish, as it finishes after the stdout. + //If it is ready then, we can wait for the output to be read and the future to be done. + Thread.sleep(this.waitPause); + } while (((PowerShellCommandProcessor) commandProcessorError).isReady() && !resultError.isDone() && !timeout); + commandOutput = result.get(); + } + + if (resultError.isDone() && !timeout) { + //Command failed isError = true; commandOutput = resultError.get(); - } + } + } catch (InterruptedException ex) { + isError = true; Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, "Unexpected error when processing PowerShell command", ex); } catch (ExecutionException ex) { + isError = true; Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, "Unexpected error when processing PowerShell command", ex); - } finally { + } catch (TimeoutException e) { + Logger.getLogger(PowerShell.class.getName()).log(Level.WARNING, "Failed to read data due to timeout, close will be forced!"); + this.blocked = true; + } finally { //issue #2. Close and cancel processors/threads - Thanks to r4lly for helping me here ((PowerShellCommandProcessor) commandProcessor).close(); ((PowerShellCommandProcessor) commandProcessorError).close(); @@ -343,6 +377,39 @@ public PowerShellResponse executeScript(BufferedReader srcReader, String params) * Closes all the resources used to maintain the PowerShell context */ public void close() { + + if (blocked) { + Logger.getLogger(PowerShell.class.getName()).log(Level.INFO, "Shell is blocked, closing of PowerShell will be forced!"); + + int pid = -1; + + Field f; + try { + f = p.getClass().getDeclaredField("handle"); + f.setAccessible(true); + long handLong = f.getLong(p); + + Kernel32 kernel = Kernel32.INSTANCE; + WinNT.HANDLE handle = new WinNT.HANDLE(); + handle.setPointer(Pointer.createConstant(handLong)); + pid = kernel.GetProcessId(handle); + + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e1) { + Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, "Unexpected error while getting process handle", e1); + } + + Logger.getLogger(PowerShell.class.getName()).log(Level.INFO, "Killing process with PID: " + pid); + + try { + Runtime.getRuntime().exec("taskkill.exe /f /PID " + pid + " /T" ); + this.closed = true; + } catch (IOException e) { + Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, "Unexpected error while killing powershell process", e); + } + + + } + if (!this.closed) { try { Future closeTask = threadpool.submit(new Callable() { @@ -363,6 +430,7 @@ public String call() throws Exception { Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, "Unexpected error when when closing streams", ex); } commandWriter.close(); + if (this.threadpool != null) { try { this.threadpool.shutdownNow(); @@ -379,6 +447,7 @@ public String call() throws Exception { private void waitUntilClose(Future task) throws InterruptedException { int closingTime = 0; + while (!task.isDone()) { if (closingTime > maxWait) { Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, "Unexpected error when closing PowerShell: TIMEOUT!"); From 1ae71d25bb6a723ca4c4eee0a6e5435160a80fe9 Mon Sep 17 00:00:00 2001 From: Martin Hoffmann Date: Sat, 24 Jun 2017 16:50:06 +0200 Subject: [PATCH 14/15] Added JNA dependencies If you want to avoid including this library, an alternative way to determine the child PID of the cmd process that hosts powershell.exe would be: Get the PID of the JVM: ManagementFactory.getRuntimeMXBean().getName().split("@")[0] Use this value to Runtime.exec(wmic process where (ParentProcessId=1234) get ProcessId) Parse the PID from the response. --- pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index cf3d0e0..82d9e04 100644 --- a/pom.xml +++ b/pom.xml @@ -114,6 +114,16 @@ 4.12 test + + net.java.dev.jna + jna + 4.4.0 + + + net.java.dev.jna + jna-platform + 4.4.0 + UTF-8 From 5485fa637b2a14035dd700d619595bca097b86d5 Mon Sep 17 00:00:00 2001 From: Martin Hoffmann Date: Sat, 24 Jun 2017 17:28:22 +0200 Subject: [PATCH 15/15] Removed Multicatch (Travis CI build) not available in 1.5 --- src/main/java/com/profesorfalken/jpowershell/PowerShell.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/profesorfalken/jpowershell/PowerShell.java b/src/main/java/com/profesorfalken/jpowershell/PowerShell.java index 88b8cdd..a9ef09f 100644 --- a/src/main/java/com/profesorfalken/jpowershell/PowerShell.java +++ b/src/main/java/com/profesorfalken/jpowershell/PowerShell.java @@ -394,7 +394,7 @@ public void close() { handle.setPointer(Pointer.createConstant(handLong)); pid = kernel.GetProcessId(handle); - } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e1) { + } catch (Exception e1) { Logger.getLogger(PowerShell.class.getName()).log(Level.SEVERE, "Unexpected error while getting process handle", e1); }