Выход программы теряется при передаче через PsExec

(Это вопрос, который мой коллега разместил в другом месте, но я думал, что разместил его здесь, чтобы посмотреть, могу ли я попасть в другую аудиторию.)

Привет всем, Я тестирую возможность написания небольшого java-приложения, которое будет использовать Psexec для запуска удаленных заданий. В ходе тестирования, связывающего stdin и stdout java-программы с psexec, я столкнулся с нечетной ошибкой.

Моя тестовая программа - базовая эхо-программа. Он запускает поток для чтения из stdin, а затем выводит вывод read непосредственно в stdout. При запуске на локальном компьютере, а не в psexec, он работает красиво. Точно так, как должно быть.

Однако, когда я вызываю это из PsExec, первый раз, когда вход передается напрямую в stdout, он теряется. Что делает ошибку действительно bizzare в том, что это только первый раз, когда вход передается непосредственно в stdout, что он потерян. Если входная строка добавлена ​​в другую строку, она работает нормально. Либо строковый литерал, либо переменная String. Однако, если входная строка отправляется непосредственно в stdout, она не проходит. Второй раз, когда он отправляется на stdout, он проходит через штраф - и каждый раз после этого.

У меня полная потеря относительно того, что происходит здесь. Я пытался проверить все возможные ошибки, о которых я могу думать. У меня нет идей. Я пропустил один или это просто что-то внутри psexec?

Вот код, о котором идет речь, он состоит из трех классов (один из которых реализует интерфейс, который представляет собой единую функцию interace).

Основной класс:

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, который является классом, который возвращает его обратно в стандартный вывод:

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 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 toString().
Outputting it cleanly to standard out:

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

Ответы

Ответ 1

Вы пытались перенаправить вывод в файл (java... > c:\output.txt)? таким образом, вы можете сделать двойной выбор, если все идет в stdout и, возможно, просто будет съедено psexec

Ответ 2

PsExec ест выход. Следующая интересная вещь может быть там, где она есть выход. Вы можете проверить это, получив копию Wireshark и проверяя, проходит ли этот вывод по сети или нет. Если это не так, то это будет съедено на отдаленной стороне. Если это так, его едят локально.

Не то, чтобы я действительно был уверен, куда идти оттуда, но сбор дополнительной информации, безусловно, кажется хорошим путем, чтобы следовать...

Ответ 3

Не настроен ли System.out для автозапуска? После первой печати попробуйте System.out.flush() и посмотрите, появляется ли первая строка без печати строк.

(о, да, серьезно, "ПОЛУЧЕН", а не "ПОЛУЧЕН" ).

Ответ 4

Хорошо, я думал об этом в течение выходных, и я с тех пор, как вы прыгаете с машины на машину, мне интересно, может ли быть проблема с CharSet? Может быть, он питается строкой в ​​первый раз и имеет дело с другой кодовой страницей или проблемой набора символов? Java - это 16-битные символы, а окна - 8 бит с кодовыми страницами или utf-8 в наши дни.

Есть ли вероятность, что локальные и удаленные машины имеют разные наборы символов по умолчанию? Если вы отправляете локализованные данные по сети, это может привести к неправильной работе.

Ответ 5

То, что я вижу при запуске psexec, заключается в том, что оно создает дочернее окно для выполнения этой работы, но не возвращает эту программу в консольное окно. Я бы предложил использовать WMI или какую-то форму API-интерфейсов для обработки окон, чтобы получить уровень управления, которого вам не хватает с помощью psexec. Разумеется, java имеет эквивалент класса .Net System.Diagnotics.Process.

Ответ 6

Возможно, вы могли бы попробовать передать копию ввода своим слушателям:

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

У меня были аналогичные проблемы с слушателями, где переданная переменная оставалась пустой, если только я не передал ее явную копию.

Ответ 7

У меня не обязательно есть ответ, но некоторые комментарии могут оказаться полезными.

  • Идея "передать копию" не имеет значения, так как ваш вывод успешно печатает строку дважды перед сбоем, а затем снова завершается.
  • Авто-флеш тоже не имеет значения, как вы уже упоминали
  • Предложение Niko имеет некоторые достоинства в диагностических целях. Смешанный с предложением Марка, это заставляет меня задаться вопросом, не связаны ли какие-то невидимые управляющие символы где-то. Что делать, если вы напечатали значения байтов символов в качестве диагностического шага?
  • Вы знаете, что это значение "Test" (по крайней мере, на том выходе, который вы нам дали). Что произойдет, если вы передадите "Тест" непосредственно в неудачный оператор printLn?
  • В подобных ситуациях вы хотите получить как можно больше информации. Вставьте точки останова и проанализируйте символы. Отправляйте байты в файлы и открывайте их в шестнадцатеричных редакторах. Сделайте все возможное, чтобы проследить все как можно точнее.
  • Придумайте странные тестовые сценарии и попробуйте их, даже если они не помогут. Вы никогда не знаете, какую хорошую идею вы могли бы при анализе результатов безнадежной идеи.

Ответ 8

Я бы предположил, что там есть фиктивный байт, предваряющий T. Согласно JavaDocs, InputStreamReader будет читать один или несколько байтов и декодировать их в символы.

У вас может быть escape-последовательность или ложный байт, маскирующийся как многобайтовый символ.

Быстрая проверка - см., если "текущий" когда-либо > 128 или < 33.

Что делать, если вы использовали CharArrayReader для получения отдельных байтов без перевода charset?

Теория заключается в том, что во время первой попытки вывести String с помощью println, она отправляет некоторый escape-символ, поедая оставшуюся часть строки. Во время последующих распечаток либо Java, либо сетевой канал обрабатывают или удаляют его, поскольку ранее он получил эту escape-последовательность, возможно, каким-то образом изменил обработку.

Как несвязанный nit, sb.toString() возвращает новую строку, поэтому нет необходимости вызывать "new String (sb.toString())"

Ответ 9

Как вы выполняете 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?

Ответ 10

Я имею дело с этой же проблемой, и мне интересно, связано ли это с тем, как работает cmd-окно и трубы в окнах, пока у вас нет истинного оконного сеанса. Подавленный выход происходит, когда возникает новый процесс. Вы могли бы подумать, что если вы создадите процесс, который stdout/stderr/stdin будет унаследован от процесса, который породил его; в конце концов, это произойдет, если вы создадите процесс из обычного CMD-окна, а выход из нового процесса будет отправлен обратно на вашу собственную консоль. Однако, если где-то в наследовании труб что-то должно было пойти не так, как будто не пропускать объект WINDOW.GUI, потому что нет физического окна, окна не позволяют наследовать stdin/stdout/stdin. Может ли кто-нибудь провести какое-либо расследование или открыть билет на поддержку Windows?

Ответ 11

То же самое происходит здесь, я снова и снова переживаю этот пост в эти дни, надеясь, что смогу найти какое-то решение. Затем я решил отказаться от psexec и найти альтернативу. Так вот что: PAExec. Работает идеально для получения выходных команд.

Ответ 12

У меня была такая же проблема, и я попытался использовать несколько комбинаций переадресаций. Вот что сработало:

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

Redirect.INHERIT для processBuilder.redirectInput получил мне отсутствующий вывод удаленной команды.

Ответ 13

Нет простого решения. Моя работа в недавнем проекте использует продукт paexec.exe. Он легко записывает выходные/ошибки в JAVA (java-8), но зависает после завершения выполнения удаленной команды. При запуске этого внутри сервера на размещенной машине я должен отменить новый дочерний процесс JVM для запуска paexec.exe и принудительно убить его через свой PID после завершения, чтобы освободить все ресурсы. Если у кого-то есть лучшее решение, отправьте его.