通过 PsExec 时程序输出丢失

发布于 2024-08-02 03:17:30 字数 4754 浏览 8 评论 0原文

(这是我的同事在其他地方发布的问题,但我想我应该发布放在这里是为了看看我是否能吸引不同的观众。)

大家好, 我正在测试编写一个小型 java 应用程序的可能性,该应用程序将使用 Psexec 启动远程作业。 在测试将 java 程序的 stdin 和 stdout 绑定到 psexec 的过程中,我遇到了一个奇怪的错误。

我的测试程序是一个基本的 echo 程序。 它启动一个线程从 stdin 读取数据,然后将读取的输出直接传送回 stdout。 当在本地计算机上运行而不是通过 psexec 运行时,它运行得非常好。 正是如此。

但是,当我第一次从 PsExec 调用它时,输入直接通过管道传输到 stdout 中,它会丢失。 这个 bug 真正奇怪的地方在于,只有当输入第一次直接通过管道传输到 stdout 时,它才会丢失。 如果输入字符串附加到另一个字符串,它就可以正常工作。 字符串文字或字符串变量。 但是,如果输入字符串直接发送到标准输出,则它不会通过。 第二次将其发送到标准输出时,它会顺利进行 - 此后每次都会如此。

我完全不知道这里发生了什么。 我尝试测试我能想到的每一个可能的错误。 我没主意了。 我是否错过了一个或者这只是 psexec 中的东西?

这是有问题的代码,它位于三个类中(其中一个类实现了一个接口,该接口是单个函数接口)。

Main 类:

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 类,它是从 stdin 读取的线程:

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 类,是将其通过管道返回到 stdout 的类:

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 - Execute processes remotely
Copyright (C) 2001-2009 Mark Russinovich
Sysinternals - www.sysinternals.com


Starting up.
Successfully started up.  Awaiting input.
Test
Recieved character: T
Recieved character: e
Recieved character: s
Recieved character: t
Input string has made it to fireInputRecieved: Test


START INPUT RECIEVED
The input that has been recieved is: Test
It is a String, that has been copied from a StringBuilder's toString().
Outputting it cleanly to standard out:

Outputting it cleanly to standard out again:
Test
Finished example outputs of input: Test
END INPUT RECIEVED

(This is a question my coworker posted elsewhere, but I thought I'd post it here to see if I could hit a different audience.)

Hello all,
I'm testing the possibility of writing a small java application the will use Psexec to kick off remote jobs. In the course of testing binding the stdin and stdout of a java program to psexec I came across an odd bug.

My test program is a basic echo program. It starts a thread to read from stdin and then pipes the read output directly back to stdout. When run on the local machine, not from psexec, it works beautifully. Exactly as it should.

However, when I call it from PsExec the first time the input is piped directly into stdout it is lost. What makes the bug really bizzare is that it is only the first time the input is piped directly into stdout that it is lost. If the input String is appended to another string it works fine. Either a String literal or a String variable. However, if the input String is sent directly to stdout it doesn't go through. The second time it is sent to stdout it goes through fine - and everytime there after.

I'm at a complete loss as to what's going on here. I've tried to test for every possible bug I can think of. I'm out of ideas. Did I miss one or is this just something inside psexec?

Here is the code in question, it's in three classes (one of which implements an interface which is a single function interace).

The Main class:

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.");
    }
}

The CReader class which is the thread that reads from stdin:

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;
            }
        }       
    }
}

The CEcho class, which is the class that pipes it back to stdout:

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");
    }
}

And finally, here is the program output:

>psexec \\remotecomputer "C:\Program Files\Java\jre1.6.0_05\bin\java.exe" -jar "C:\Documents and Settings\testProram.jar"

PsExec v1.96 - Execute processes remotely
Copyright (C) 2001-2009 Mark Russinovich
Sysinternals - www.sysinternals.com


Starting up.
Successfully started up.  Awaiting input.
Test
Recieved character: T
Recieved character: e
Recieved character: s
Recieved character: t
Input string has made it to fireInputRecieved: Test


START INPUT RECIEVED
The input that has been recieved is: Test
It is a String, that has been copied from a StringBuilder's toString().
Outputting it cleanly to standard out:

Outputting it cleanly to standard out again:
Test
Finished example outputs of input: Test
END INPUT RECIEVED

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

苏佲洛 2024-08-09 03:17:30

您是否尝试过将输出重定向到文件( java... >c:\output.txt )? 这样你就可以仔细检查所有内容是否都进入标准输出,并且可能只是被 psexec 吃掉

have you tried redirecting the output into a file ( java... >c:\output.txt )? this way you could doublecheck if everything is going into stdout and maybe just getting eaten by psexec

在巴黎塔顶看东京樱花 2024-08-09 03:17:30

PsExec 正在吃掉输出。 下一个有趣的事情可能是它在哪里吃掉输出。 您可以通过获取 Wireshark 的副本并检查有问题的输出是否遍历网络来检查这一点。 如果不是,那么它就会在远端被吃掉。 如果是的话,那就是当地吃的。

并不是说我真的确定从哪里开始,但收集更多信息似乎是一条不错的道路......

PsExec is eating the output. Next interesting thing might be where it's eating the output. You could check this by getting a copy of Wireshark and checking whether the output in question is traversing the network or not. If it's not, then it's being eaten on the remote side. If it is, it's being eaten locally.

Not that I'm really sure where to go from there, but collecting more information certainly seems like a good path to be following...

初见终念 2024-08-09 03:17:30

我遇到了同样的问题并尝试了多种重定向组合。
这就是有效的:

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 为我提供了丢失的远程命令输出。

I was having the same issue and tried multiple combinations of redirects.
This is what worked:

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();

The Redirect.INHERIT for processBuilder.redirectInput got me the missing remote command output.

比忠 2024-08-09 03:17:30

System.out 是否未配置自动刷新? 第一次打印后,尝试 System.out.flush() 并查看是否出现第一行而没有打印更多行。

(哦,是的,说真的,这是“已收到”,而不是“已收到”。)

Is System.out not configured for autoflush? After the first print try System.out.flush() and see if the first line appears without more lines being printed.

(oh yeah, seriously, it is "RECEIVED", not "RECIEVED".)

你是暖光i 2024-08-09 03:17:30

好吧,我整个周末一直在考虑这个问题,自从你从一台机器跳到另一台机器后,我想知道是否可能存在字符集问题? 也许它是第一次吃字符串并处理不同的代码页或字符集问题? Java 通常是 16 位字符,而 Windows 要么是带代码页的 8 位字符,要么是现在的 utf-8。

本地和远程计算机有可能具有不同的默认字符集吗? 如果您通过网络发送本地化数据,它可能会出现错误。

OK, I've been thinking about this over the weekend and I since you are jumping from machine to machine I wonder if maybe there is a CharSet issue? Maybe it is eating the string the first time and dealing with a different code page or character set issue? Java is 16bit characters normally and windows is either 8bit with code pages or utf-8 these days.

Any chance the local and remote machines have different default character sets? If you are sending localized data over the net it might misbehave.

忆悲凉 2024-08-09 03:17:30

我在运行 psexec 时看到的是,它会生成一个子窗口来完成工作,但不会将该程序的输出返回到它的控制台窗口。 我建议使用 WMI 或某种形式的 Windows 进程 API 框架来获得 psexec 所缺乏的控制级别。 当然,java 有一个相当于 .Net 的 System.Diagnotics.Process 类。

What I see when running psexec is that it spawns a child window to do the work but doesnt return that program's output to it's console window. I would suggest using WMI or some form of windows process API framework to gain a level of control you appear to lack with psexec. Surely java has an equivalent to .Net's System.Diagnotics.Process class.

吃素的狼 2024-08-09 03:17:30

也许您可以尝试将输入的副本传递给侦听器:

 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);
}

我对侦听器也有类似的问题,除非我确实传递了它的显式副本,否则传递的变量最终会为空。

Maybe you could try passing a copy of input to your listeners:

 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);
}

I had similar problems with listeners where a passed variable would end up empty unless I did pass an explicit copy of it.

年华零落成诗 2024-08-09 03:17:30

我不一定有答案,但一些评论可能会有所帮助。

  • “传递副本”的想法并不重要,因为您的输出在失败之前成功打印了字符串两次,然后再次成功。
  • 自动刷新也不重要,因为您已经提到
  • 尼科的建议对于诊断目的有一些优点。 加上马克的建议,这让我想知道是否有一些隐形的控制角色参与其中。 如果打印字符字节值作为诊断步骤会怎样?
  • 知道该值为“Test”(至少在您提供给我们的输出中)。 如果将“Test”直接传递给失败的 printLn 语句会发生什么?
  • 在这种情况下,您希望获得尽可能多的信息。 插入断点并分析字符。 将字节发送到文件并在十六进制编辑器中打开它们。 尽一切努力尽可能准确和精确地追踪事物。
  • 提出奇怪的测试场景并尝试它们,即使它们可能没有帮助。 在分析无望想法的结果时,你永远不知道自己可能会产生什么好想法。

I don't necessarily have an answer, but some comments may prove helpful.

  • The "pass a copy" idea shouldn't matter, since your output successfully prints the string twice before the failure, then succeeds again afterward.
  • auto-flush shouldn't matter either, as you've already mentioned
  • Niko's suggestion has some merit, for diagnostic purposes. Mixed with Mark's suggestion, it makes me wonder if there aren't some invisible control characters getting involved somewhere. What if you printed the characters byte values as a diagnostic step?
  • You know that the value is "Test" (at least in the output you gave us). What happens if you pass "Test" directly to the failing printLn statement?
  • In situations like this, you want to gain as much information as possible. Insert breakpoints and analyze characters. Send the bytes to files and open them in hex editors. Do whatever you can to trace things as accurately and as precisely as possible.
  • Come up with weird test scenarios and try them, even if they shouldn't possibly help. You never know what good idea you might have while analyzing the results of the hopeless idea.
亢潮 2024-08-09 03:17:30

我猜测 T 前面有一个假字节。根据 JavaDocs,InputStreamReader 将读取一个或多个字节,并将它们解码为字符。

那里可能有转义序列或虚假字节,伪装成多字节字符。

快速检查 - 看看“当前”是否曾经 > 128或< 33.

如果您使用 CharArrayReader 来获取单个字节而不进行任何字符集转换会怎么样?

理论是,在第一次尝试使用 println 输出字符串时,它会发送某种转义字符,吃掉字符串的其余部分。 在以后的打印过程中,Java 或网络管道正在处理或删除它,因为它之前获得了转义序列,可能以某种方式改变了处理。

作为一个不相关的 nit,sb.toString() 返回一个新的 String,因此没有必要调用“new String(sb.toString())”

I'd guess that there is a bogus byte in there prefacing the T. According to JavaDocs, an InputStreamReader will read one or more bytes, and decode them into characters.

You could have an escape sequence or spurious byte in there, masquerading as a multibyte character.

Quick check - see if "current" is ever > 128 or < 33.

What if you used a CharArrayReader to get individual bytes, without any charset translation?

The theory is that during the first attempt to output the String using println, it's sending an escape character of some sort, eating the rest of the string. During later prints, either Java or the network pipe are handling or removing it, since it previously got that escape sequence, perhaps changing the handling in some way.

As an unrelated nit, sb.toString() returns a new String, so it's unnecessary to call "new String(sb.toString())"

小情绪 2024-08-09 03:17:30

同样的问题,这些天我一遍又一遍地浏览这篇文章,希望能找到一些解决方案。 然后我决定放弃 psexec 并寻找其他选择。 这就是事情:PAExec。 非常适合获取命令输出。

Same issue here, I'm going through this post again and again these days, hoping I can find some solution. Then I decide I should give up psexec and find some alternative. So this is the thing: PAExec. Works perfect for getting command output.

一曲琵琶半遮面シ 2024-08-09 03:17:30

你如何执行 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

How are you executing PsExec? My suspicion is that this is some code within PsExec which is actually doing echo suppression, possibly for the purposes of protecting a password. One way to test this hypothesis would be to change this code:

    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);

to this:

    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);

...thereby causing the output to be (if I'm right):

Outputting it cleanly to standard out:
 Test
Outputting it cleanly to standard out again:
Test
Finished example outputs of input: Test

In particular, it's noticeable that the apparently-suppressed line is the first line which consists solely of Test - which is exactly the text you've just sent to the remote system. This sounds like PsExec attempting to suppress a remote system which is echoing its input in addition to producing its own output.

Is the password of the user on the remote machine perhaps Test? Are you using PsExec's -p parameter? Are you specifying -i?

不语却知心 2024-08-09 03:17:30

我正在处理同样的问题,我想知道它是否与 Windows 中的 cmd 窗口和管道在没有真正的窗口会话时工作的方式有关。 当任何新进程产生时,就会发生抑制的输出。 您可能会认为,如果生成一个进程,则 stdout/stderr/stdin 将从生成它的进程继承; 毕竟,如果您从普通的 cmd 窗口生成进程,并且新进程的输出通过管道传输回您自己的控制台,就会发生这种情况。 但是,如果管道继承中的某个地方出现问题,例如由于没有物理窗口而未传递 WINDOW.GUI 对象,则 Windows 不会让 stdin/stdout/stdin 被继承。 有人可以对此进行一些调查或开具 Windows 支持票吗?

I am dealing with this same issue and I am wondering if it has to do with how the cmd window and pipes in windows work while you don't have a true windowed session. The suppressed output happens when any new process is spawned. You would think that if you spawn a process that the stdout/stderr/stdin would be inherited from the process that spawned it; after all that is what happens if you spawn the process from a normal cmd window and the output from the new process is piped back to your own console. However if somewhere in the inheritance of the pipes something were to go wrong, like say not passing a WINDOW.GUI object because there is no physical window, windows doesn't let the stdin/stdout/stdin to be inherited. Can any one do some investigation or open a windows support ticket for this?

生活了然无味 2024-08-09 03:17:30

似乎没有简单的解决方案。 我在最近的项目中的解决方法是使用 paexec.exe 产品。 它可以在 JAVA(java-8) 中轻松捕获输出/错误,但在远程命令执行完成后挂起。 当在托管计算机上的服务器内运行此程序时,我必须拒绝一个新的子 JVM 进程来运行 paexec.exe,并在完成后通过其 PID 强制终止它,以释放所有资源。
如果有人有更好的解决方案,请发布。

Seems no easy solution. My work-around in a recent project is using paexec.exe product. It captures output/error easily in JAVA(java-8), but hangs up upon completion of the remote command execution. When running this inside a server on the hosted machine, I have to spurn a new child JVM process to run paexec.exe and force kill it via its PID upon completion in order to release all the resources.
If anyone has better solution, please post it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文