通过 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 时程序输出丢失的主要内容,如果未能解决你的问题,请参考以下文章

Python 子进程丢失了程序标准输出的 10%

通过自定义 CIFilter 运行图像时 CIImage 属性丢失

应用程序关闭时如何接收 Android Nougat 网络丢失的回调?

执行颤振运行时获取此日志输出的原因可能是啥 - 与设备的连接丢失?

在通知单击时启动 Fragment 而不会丢失状态

丢失 OpenGL 输出