通过 PsExec 时程序输出丢失
Posted
技术标签:
【中文标题】通过 PsExec 时程序输出丢失【英文标题】:Program output lost when passed through PsExec 【发布时间】:2009-08-14 19:41:49 【问题描述】:(这是我的同事在elsewhere 发布的一个问题,但我想我会在这里发布它,看看我是否可以吸引不同的受众。)
大家好, 我正在测试编写一个小型 Java 应用程序的可能性,该应用程序将使用 Psexec 来启动远程作业。在测试将 java 程序的标准输入和标准输出绑定到 psexec 的过程中,我遇到了一个奇怪的错误。
我的测试程序是一个基本的 echo 程序。它启动一个线程从标准输入读取,然后将读取的输出直接通过管道传输回标准输出。当在本地机器上运行而不是从 psexec 上运行时,它运行良好。正是它应该的。
但是,当我第一次从 PsExec 调用它时,输入直接通过管道传输到 stdout,它会丢失。使该错误真正奇怪的是,只有第一次将输入直接通过管道传输到 stdout 时才会丢失。如果输入字符串附加到另一个字符串,它工作正常。字符串文字或字符串变量。但是,如果输入字符串直接发送到标准输出,它不会通过。第二次将其发送到标准输出时,它会正常运行 - 之后每次都可以。
我完全不知道这里发生了什么。我试图测试我能想到的每一个可能的错误。我没主意了。我错过了一个还是这只是 psexec 内部的东西?
这里是有问题的代码,它在三个类中(其中一个实现了一个接口,它是一个单一的函数接口)。
主类:
public class Main
public static void main(String[] args)
System.out.println("Starting up.");
CReader input = new CReader(new BufferedReader(
new InputStreamReader(System.in)));
CEcho echo = new CEcho();
input.addInputStreamListener(echo);
input.start();
System.out.println("Successfully started up. Awaiting input.");
CReader 类是从标准输入读取的线程:
public class CReader extends Thread
private ArrayList<InputStreamListener> listeners =
new ArrayList<InputStreamListener>();
private boolean exit = false;
private Reader in;
public CReader(Reader in)
this.in = in;
public void addInputStreamListener(InputStreamListener listener)
listeners.add(listener);
public void fireInputRecieved(String input)
if(input.equals("quit"))
exit = true;
System.out.println("Input string has made it to fireInputRecieved: "
+ input);
for(int index = 0; index < listeners.size(); index++)
listeners.get(index).inputRecieved(input);
@Override
public void run()
StringBuilder sb = new StringBuilder();
int current = 0, last = 0;
while (!exit)
try
current = in.read();
catch (IOException e)
System.out.println("Encountered IOException.");
if (current == -1)
break;
else if (current == (int) '\r')
if(sb.toString().length() == 0)
// Extra \r, don't return empty string.
continue;
fireInputRecieved(new String(sb.toString()));
sb = new StringBuilder();
else if(current == (int) '\n')
if(sb.toString().length() == 0)
// Extra \n, don't return empty string.
continue;
fireInputRecieved(new String(sb.toString()));
sb = new StringBuilder();
else
System.out.println("Recieved character: " + (char)current);
sb.append((char) current);
last = current;
CEcho 类,该类通过管道将其返回到标准输出:
public class CEcho implements InputStreamListener
public void inputRecieved(String input)
System.out.println("\n\nSTART INPUT RECIEVED");
System.out.println("The input that has been recieved is: "+input);
System.out.println("It is a String, that has been copied from a " +
"StringBuilder's toString().");
System.out.println("Outputting it cleanly to standard out: ");
System.out.println(input);
System.out.println("Outputting it cleanly to standard out again: ");
System.out.println(input);
System.out.println("Finished example outputs of input: "+input);
System.out.println("END INPUT RECIEVED\n\n");
最后,这是程序输出:
>psexec \\remotecomputer "C:\Program Files\Java\jre1.6.0_05\bin\java.exe" -jar "C:\Documents and Settings\testProram.jar" PsExec v1.96 - 远程执行进程 版权所有 (C) 2001-2009 Mark Russinovich Sysinternals - www.sysinternals.com 启动。 成功启动。等待输入。 测试 收到字符:T 收到的字符:e 收到的字符:s 收到的字符:t 输入字符串已进入 fireInputRecieved: Test 收到的开始输入 收到的输入是:测试 它是一个字符串,从 StringBuilder 的 toString() 复制而来。 将其干净地输出到标准输出: 再次将其干净地输出到标准输出: 测试 输入的完成示例输出:测试 收到的最终输入
【问题讨论】:
为了提高透明度(如果事实证明我只是做了一些愚蠢的事情,马克也不会受到指责),我是最初发布问题的同事。我不知道我是否只是在做一些愚蠢的事情,或者这是否是 psexec 中的一个合法错误。我真的更希望是前者——因为如果是后者,我必须做更多的工作。 输入多少个字符是否重要,或者这种奇怪的行为仅适用于 4 字符字符串? 输入多少个字符都没有关系。 我希望您能找到解决方案 - 如果是,请务必发布! 是的...我希望有办法将它转移到爱尔康...这确实是他的问题,直到我发布它之后他才意识到 SO 有多棒。跨度> 【参考方案1】:您是否尝试过将输出重定向到文件(java...>c:\output.txt)?这样,您可以仔细检查所有内容是否都进入标准输出,并且可能只是被 psexec 吃掉
【讨论】:
好主意。让我试试看会发生什么。 不错的电话,确实 psexec 正在吃它。好的,这是 psexec 的问题。现在有办法解决吗? 我接受这个,因为这是第一个说这是 psexec 的错的人,赏金需要去某个地方。【参考方案2】:PsExec 正在吃掉输出。下一个有趣的事情可能是在哪里它吃掉了输出。您可以通过获取Wireshark 的副本并检查有问题的输出是否正在遍历网络来检查这一点。如果不是,那么它正在远程被吃掉。如果是,那就是在当地吃。
并不是说我真的很确定从那里去哪里,但收集更多信息似乎是一条不错的道路……
【讨论】:
【参考方案3】:我遇到了同样的问题并尝试了多种重定向组合。 这是有效的:
processBuilder.redirectErrorStream(true);
processBuilder.redirectOutput(Redirect.PIPE);
processBuilder.redirectInput(Redirect.INHERIT);
final Process process = processBuilder.start();
// Using Apache Commons IOUtils to get output in String
StringWriter writer = new StringWriter();
IOUtils.copy(process.getInputStream(), writer, StandardCharsets.UTF_8);
String result = writer.toString();
logger.info(result);
final int exitStatus = process.waitFor();
processBuilder.redirectInput 的 Redirect.INHERIT 让我得到了缺少的远程命令输出。
【讨论】:
【参考方案4】:System.out
是否未配置为自动刷新?第一次打印后尝试System.out.flush()
,看看第一行是否出现而没有更多行被打印。
(哦,是的,说真的,这是“收到”,而不是“收到”。)
【讨论】:
没有什么不同。我很确定 System.out 默认配置为自动刷新。如果不是,其他 println 也不应该被发送。关于该错误的真正奇怪之处在于,这只是您第一次尝试发送不附加任何字符的 JUST 输入。只有在使用 StringBuilder 设置输入之后。任何其他打印尝试都可以正常工作。不过还是值得一试,谢谢。此外,这是一个错字。在编码时,我通常不会太认真地考虑拼写。尤其是当我被这样的错误分心时:p @Alcon - 而 我 应该是不会拼字的人! @mwalling 你是一个不会拼写的人:p 我只是一个懒惰的人。【参考方案5】:好的,我周末一直在考虑这个问题,自从你从一台机器跳到另一台机器以来,我想知道是否可能存在 CharSet 问题?也许它是第一次吃字符串并处理不同的代码页或字符集问题? Java 通常是 16 位字符,而 Windows 是带有代码页的 8 位字符或 utf-8。
本地和远程机器有不同的默认字符集吗?如果您通过网络发送本地化数据,它可能会出现异常。
【讨论】:
好吧,我想这是有可能的,但它们都是 Windows XP SP2,由相同的核心负载构建并运行相同的 JRE。所以它不应该是一个字符集差异问题。但是可以想象,它在读入时以某种方式转换为 java char 集,然后在被输出时没有转换回来。我可以尝试通过在返回途中强制图表集来解决此问题。不过我认为这不太可能。 我担心 PSExec 对字符集也有不同的想法。当您附加文本时,它可以完美地工作,但是如果您将其原始打印,它第一次会失败……也许 PSExec 必须扩展其字符集的概念并在此过程中丢失字符串?诚然,它是一个 SWAG,但是一旦你开始在管道/端口之间传递字符串,就会发生这种情况。 我会尝试强制使用一些字符集,看看会发生什么。 好的,尝试强制传递给 CReader 的 BufferedReader 使用 Latin-1 和 US-ASCII 字符集。两者都没有改变。并不意味着它不是 charset snafu,但如果这两个不起作用,那么我有点怀疑强制 charset 会解决它:( 除非在其他地方我需要强制它。【参考方案6】:我在运行 psexec 时看到的是它会生成一个子窗口来完成工作,但不会将该程序的输出返回到它的控制台窗口。我建议使用 WMI 或某种形式的 Windows 进程 API 框架来获得 psexec 似乎缺乏的控制级别。当然 java 有一个等价于 .Net 的 System.Diagnotics.Process 类。
【讨论】:
【参考方案7】:也许您可以尝试将输入副本传递给您的听众:
public void fireInputRecieved(String input)
if(input.equals("quit"))
exit = true;
String inputCopy = new String(input);
System.out.println("Input string has made it to fireInputRecieved: "
+ input);
for(int index = 0; index < listeners.size(); index++)
listeners.get(index).inputRecieved(inputCopy);
我在侦听器中遇到了类似的问题,其中传递的变量最终会为空,除非我确实传递了它的显式副本。
【讨论】:
我想到了这一点并尝试了类似的事情,但我们现在已经确定绝对是 psexec 而不是 java 在吃输出。不过谢谢!【参考方案8】:我不一定有答案,但有些 cmets 可能会有所帮助。
“传递副本”的想法并不重要,因为您的输出在失败前成功打印了两次字符串,然后再次成功。 自动刷新也不重要,正如您已经提到的那样 Niko 的建议有一些优点,用于诊断目的。再加上马克的建议,这让我怀疑是否有一些无形的控制角色参与其中。如果您打印字符字节值作为诊断步骤怎么办? 您知道该值为“Test”(至少在您提供给我们的输出中)。如果将“Test”直接传递给失败的 printLn 语句会发生什么? 在这种情况下,您希望获得尽可能多的信息。插入断点并分析字符。将字节发送到文件并在十六进制编辑器中打开它们。尽你所能尽可能准确、准确地追踪事物。 想出奇怪的测试场景并尝试它们,即使它们可能没有帮助。在分析无望想法的结果时,您永远不知道自己可能有什么好想法。【讨论】:
尝试在许多地方直接将测试传递给 println,结果很好。还尝试使用测试以外的输入,但它们也没有回来。所以它与值“test”无关。使用我们已经确定的输出捕获建议,它现在肯定是 psexec 吃掉了输出。问题是为什么。 基于这些 cmets,我必须假设您作为输入收到的“测试”与您在代码中输入的“测试”之间存在差异。有什么区别? 您可以通过将这一行放在 inputReceived 函数的顶部更彻底地测试我的最后一条评论:'input = "Test"'。如果它总是有效,但“测试”不能通过正常的程序流程工作,那么在接收到的文本字符串中隐藏着一些不为人知和恶意的东西,而不是在编码字符串中。【参考方案9】:我猜想在 T 前面有一个虚假字节。根据 JavaDocs,InputStreamReader 将读取一个或多个字节,并将它们解码为字符。
你可能有一个转义序列或虚假字节,伪装成一个多字节字符。
快速检查 - 查看“当前”是否曾经 > 128 或
如果您使用 CharArrayReader 获取单个字节,而不进行任何字符集转换会怎样?
理论上,在第一次尝试使用 println 输出字符串时,它会发送某种转义字符,吃掉字符串的其余部分。在以后的打印过程中,Java 或网络管道正在处理或删除它,因为它之前获得了该转义序列,可能以某种方式改变了处理方式。
sb.toString() 作为一个不相关的 nit,返回一个新的 String,所以不需要调用 "new String(sb.toString())"
【讨论】:
新的 String() 只是我试图找出问题所在的无数事情之一。我不认为它应该有所作为,但我想尽可能地将输入与输出隔离开来。你知道调试是如何进行的,你会愿意尝试任何东西,即使它没有真正意义;) 我会尝试检查,看看会发生什么。【参考方案10】:同样的问题,这些天我一遍又一遍地浏览这篇文章,希望我能找到一些解决方案。然后我决定我应该放弃 psexec 并找到一些替代方案。所以这就是事情:PAExec。非常适合获取命令输出。
【讨论】:
欢迎堆栈溢出!请通过this link。在此处的答案中包含链接中的一些摘录。【参考方案11】:你是如何执行 PsExec 的?我怀疑这是 PsExec 中的一些代码,它实际上正在执行回声抑制,可能是为了保护密码。检验此假设的一种方法是更改此代码:
System.out.println("Outputting it cleanly to standard out: ");
System.out.println(input);
System.out.println("Outputting it cleanly to standard out again: ");
System.out.println(input);
到这里:
System.out.println("Outputting it cleanly to standard out: ");
System.out.print(' ');
System.out.println(input);
System.out.println("Outputting it cleanly to standard out again: ");
System.out.println(input);
...从而导致输出为(如果我是对的):
Outputting it cleanly to standard out:
Test
Outputting it cleanly to standard out again:
Test
Finished example outputs of input: Test
特别值得注意的是,明显被抑制的行是仅包含Test
的第一行——这正是您刚刚发送到远程系统的文本。这听起来像是 PsExec 试图抑制一个远程系统,该系统除了产生自己的输出之外还回显其输入。
远程机器上用户的密码可能是Test
吗?您是否使用 PsExec 的 -p
参数?你是在指定-i
吗?
【讨论】:
既没有参数也没有,密码不是Test。不过,您可能正在做一些事情-也许 Psexec 确实会抑制回显文本。当然,如果是这样的话,我会期望有人现在已经在 SysInternals 论坛上发布了它。 去检查那些参数的作用 试试额外的空间。如果这行得通,那么我们很可能在回声抑制方面走上了正确的道路。如果没有,那么您可以放心地忽略我:-) 不走运。它仍然会吃掉输出。 哎呀,我评论的时候才看到你的第二条评论。这是个好主意,但没有成功。输出测试表明,一些帖子已经确定它肯定是 psexec 吃掉了输出。但这似乎与回声抑制无关。【参考方案12】:我正在处理同样的问题,我想知道它是否与 cmd 窗口和 windows 中的管道在您没有真正的窗口会话时如何工作有关。当产生任何新进程时,会发生抑制输出。您会认为,如果您生成一个进程,stdout/stderr/stdin 将从生成它的进程继承;毕竟,如果您从普通 cmd 窗口生成进程并将新进程的输出通过管道传送回您自己的控制台,就会发生这种情况。但是,如果在管道继承的某个地方出现问题,例如说因为没有物理窗口而不传递 WINDOW.GUI 对象,则 Windows 不会让 stdin/stdout/stdin 被继承。任何人都可以为此进行一些调查或打开 Windows 支持票吗?
【讨论】:
【参考方案13】:似乎没有简单的解决方案。我在最近的一个项目中的解决方法是使用 paexec.exe 产品。它在 JAVA(java-8) 中轻松捕获输出/错误,但在远程命令执行完成后挂断。当在托管机器上的服务器内运行它时,我必须拒绝一个新的子 JVM 进程来运行 paexec.exe 并在完成后通过其 PID 强制终止它以释放所有资源。 如果有人有更好的解决方案,请发布。
【讨论】:
以上是关于通过 PsExec 时程序输出丢失的主要内容,如果未能解决你的问题,请参考以下文章
通过自定义 CIFilter 运行图像时 CIImage 属性丢失
应用程序关闭时如何接收 Android Nougat 网络丢失的回调?