1

Тема: AHK: Как добыть информацию из окна cmd для последующего парсинга

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

Из всех решений был выбран win api http://www.script-coding.com/AutoHotkey/DllCall.html
С win api раньше не работал, предполагаю что мне поможет функция ReadConsole http://vsokovikov.narod.ru/New_MSDN_API … onsole.htm

Но все попытки внедрить эту функцию провалились

!e::
;Предполагается что командная строка уже открыта
 sendinput, test
;Отправлям какую то информацию в cmd
 Rect=123
;Переменной куда информация из cmd Должна сливаться присваиваем значение(просто, для проверки
 FileDelete fff.txt
;Удаляем файл куда будет переноситься информация
 WinGet, active_id, ID, A
;Получаем идентификатор активного окна(командной строки
 DllCall("ReadConsole", Ptr, active_id, ptr, Rect, UInt, 500, Ptr, xx, UInt, 0)
;Выполняем функцию win api
 FileAppend, %Rect%`n, fff.txt
 FileAppend, %active_id%`n, fff.txt
 FileAppend, %ErrorLevel%`n, fff.txt
;Сливаем данные в файл
 return

После этих операций файл имеет следующее содержание

123
0x39060a
0

Что я делаю не так, как делать правильно? Может есть альтернативные решения?

2

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

ReadConsole читает вводимую информацию, а не уже введённую. Для получения информации, выведенной в консоль нужно юзать ReadConsoleOutput или ReadConsoleOutputCharacter. Далее, консольные и оконные дескрипторы- это совершенно разные вещи. Если у нас уже есть консоль в которую выведены данные, то для получения её дескриптора нужно к ней подключится(AttachConsole) и вызвать функцию GetStdHandle(или CreateFile). Простейший пример:

1::
   WinGet, dwProcessId, PID, A ;окно консоли должно быть активно.
   bResult := DllCall("AttachConsole", "uint", dwProcessId, "int")
   if(!bResult)
   {
      msgbox не удалось подключиться к консоли
      return 
   }
   ;hConsole := DllCall("CreateFileW", "str", "CONOUT$", "uint", 0x80000000, "uint", 1, "ptr", 0, "uint", 3, "uint", 0x80, "ptr", 0, "ptr")
   hConsole := DllCall("GetStdHandle", "uint", -11, "ptr")
   if(hConsole==-1)
   {
      msgbox не удалось получить хендл консоли
	  return 
   }
   VarSetCapacity(Buffer, 202, 0) ;консоль должна быть юникодной
   bResult := DllCall("ReadConsoleOutputCharacter", "ptr", hConsole, "ptr", &Buffer, "uint", 100, "ptr", 0, "uint*", x, "int")
   if(!bResult)
   {
      msgbox не удалось получить данные
      return 
   }
   msgbox % StrGet(&Buffer, 1000, "UTF-16")
   ExitApp
return

Обычно сначала получают информацию о консоли и читают построчно.

3 (изменено: Malcev, 2014-01-17 07:47:18)

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

А кто может подсказать, что тут изменить, чтобы при появлении определенных слов в cmd, скрипт выдавал сообщение?

4

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

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

5

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

Проблема в том, что данный скрипт при нажатии 1 показывает только первые 2 строчки:

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

6

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

Ну так размер буфера ограничен и указывается в вызове функции чтения. Т.е. функция прочтёт столько, сколько влезет в буфер.

7

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

Вписываю:

VarSetCapacity(Buffer, 2202, 0) ;консоль должна быть юникодной

Ничего не меняется.

8

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

Кол-во считанных символов тоже поправь.

bResult := DllCall("ReadConsoleOutputCharacter", "ptr", hConsole, "ptr", &Buffer, "uint", 100, "ptr", 0, "uint*", x, "int")

9

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

Только учтите, что 1 юникодный символ = 2 байтам.

10 (изменено: Malcev, 2014-01-17 10:05:54)

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

Заработало, но в cmd постоянно появляются новые строчки.
Какие максимальные величины можно поставить в этих местах:

VarSetCapacity(Buffer, 20000, 0) ;консоль должна быть юникодной
bResult := DllCall("ReadConsoleOutputCharacter", "ptr", hConsole, "ptr", &Buffer, "uint", 10000, "ptr", 0, "uint*", x, "int")
msgbox % StrGet(&Buffer, 10000, "UTF-16")

Или может быть можно считывать только последнюю (вновь появившеюсю) строчку?

11

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

В последнем параметре указывается символ, с которого начинать чтение. Нижние 16 байт — колонка, верхние — строка. Но чтобы вычислять, надо знать сколько в буфере консоли знаков в строку влазит, наверно. Или, может быть, достаточно сдвигать только колонку, а на новую строку само будет переходить. В свойствах консольного окна можно посмотреть размер буфера экрана (в символах). Может, просто размер буфера по вертикали уменьшить, чтобы старые строки быстрее уходили из него.

12 (изменено: Malcev, 2014-01-17 10:38:59)

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

YMP, если честно, то я ничего не понял.
1) Где об этом можно подробнее почитать, желательно с картинками?
2) Если я делаю цикл всего этого скрипта, то после первого его прохождения выдает

не удалось подключиться к консоли

Наверное надо что-то обнулить?

13

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

Так зачем всего скрипта? Только функций, которые читают в буфер и из буфера. Имею в виду переменную Buffer.
Что касается буфера окна консоли (окна командной строки), то в свойства окна можно зайти через меню правой кнопки на заголовке окна. Там на одной из вкладок можно задать размер (по горизонтали и вертикали, в символах) буфера экрана. В этот буфер попадает вывод вашей программы. Из него и читает функция ReadConsoleOutputCharacter. Там вы можете задать размер этого буфера. И столько символов и считывать, сколько их в этом буфере. А переменную Buffer сделайте в 2 раза больше, т.к. она в байтах.

Вообще, что за задача у вас стоит? Я в Коллекции постил код, запускающий консольную программу и перехватывающий её ввод и вывод. Может, проще им воспользоваться.

14 (изменено: Malcev, 2014-01-17 11:11:19)

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

Задача такая, запускаем ссылку с определенным названием на батник, в котором запускается cmd и запускается chkdsk. Как только появляется надпись "Windows has checked the file system" закрываем cmd с определенным названием, так как их может быть несколько, запускаем снова этот батник и опять следим. Поэтому и цикл я взял всего скрипта.

15

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

Но ведь запуск чекдиска вы не скриптом делаете? Он должен подключиться к существующему окну консоли, а затем в цикле из него читать. Подключение-то к окну не нужно в цикл ставить.

16

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

При желании можно сделать вывод программы в контрол Edit. См. пример ниже. Запустите скрипт и введите что-нибудь в нижнее поле, например, ping localhost или т.п.


Gui, Add, Edit, w480 h300 HWNDhwndOutput
Gui, Add, Edit, w480 r1 vCmdLine
Gui, Add, Button, Default w80 h20 gExec, Выполнить
GuiControl, Focus, CmdLine
Gui, Show
Return

Exec:
    GuiControlGet, CmdLine
    RunConAppToEdit(CmdLine, "", hwndOutput)
Return

GuiClose:
    ExitApp

RunConAppToEdit(CmdLine, Input, hwndEdit)
{
    static BufSizeChar := 1024, hParent := 0
    static Show := 0, Flags := 0x101  ; STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW
    static Buf, BufSizeByte, ProcessInfo, StartupInfo, PipeAttribs
    static piSize, siSize, paSize, flOffset, shOffset, ihOffset
    static inOffset, outOffset, errOffset, thrOffset
    If (!hParent) {
        BufSizeByte := A_IsUnicode ? BufSizeChar * 2 : BufSizeChar
        If (A_PtrSize = 8) {
            piSize := 24, siSize := 104, paSize = 24
            flOffset := 60, shOffset := 64, ihOffset := 16
            inOffset := 80, outOffset := 88, errOffset := 96
            thrOffset := 8
        }
        Else {
            piSize := 16, siSize := 68, paSize = 12
            flOffset := 44, shOffset := 48, ihOffset := 8
            inOffset := 56, outOffset := 60, errOffset := 64
            thrOffset := 4
        }
        VarSetCapacity(Buf, BufSizeByte, 0),    VarSetCapacity(ProcessInfo, piSize, 0)
        VarSetCapacity(StartupInfo, siSize, 0), VarSetCapacity(PipeAttribs, paSize, 0)
        NumPut(siSize, StartupInfo, 0, "uint"), NumPut(Flags, StartupInfo, flOffset, "uint")
        NumPut(Show, StartupInfo, shOffset, "ushort")
        NumPut(paSize, PipeAttribs, 0, "uint"), NumPut(1, PipeAttribs, ihOffset, "int")
        hParent := DllCall("GetCurrentProcess", "ptr")
    }
    DllCall("CreatePipe", "ptr *", hRead1_tmp, "ptr *", hWrite2
                        , "ptr", &PipeAttribs, "uint", 0)
    DllCall("CreatePipe", "ptr *", hRead2, "ptr *", hWrite1_tmp
                        , "ptr", &PipeAttribs, "uint", 0)

    NumPut(hRead2,  StartupInfo, inOffset, "ptr") 
    NumPut(hWrite2, StartupInfo, outOffset, "ptr")
    NumPut(hWrite2, StartupInfo, errOffset, "ptr")
    
    DllCall("DuplicateHandle", "ptr", hParent, "ptr", hRead1_tmp
                             , "ptr", hParent, "ptr *", hRead1
                             , "uint", 0, "uint", 0
                             , "uint", 2)    ; DUPLICATE_SAME_ACCESS
    DllCall("CloseHandle", "ptr", hRead1_tmp)
    DllCall("DuplicateHandle", "ptr", hParent, "ptr", hWrite1_tmp
                             , "ptr", hParent, "ptr *", hWrite1
                             , "uint", 0, "uint", 0
                             , "uint", 2)
    DllCall("CloseHandle", "ptr", hWrite1_tmp)
    
    DllCall("ExpandEnvironmentStrings", "str", CmdLine, "str", Buf, "uint", BufSizeChar)
    CmdLine := Buf
    Ret := DllCall("CreateProcess", "ptr", 0, "str", CmdLine, "ptr", 0, "ptr", 0
                                  , "uint", 1, "uint", 0, "ptr", 0, "ptr", 0
                                  , "ptr", &StartupInfo, "ptr", &ProcessInfo)
    If (!Ret) {
        MsgBox,, %A_ThisFunc%, Не удалось создать процесс.
        Output := ""
        Return 1
    }
    hChild := NumGet(ProcessInfo, 0, "ptr")
    DllCall("CloseHandle", "ptr", NumGet(ProcessInfo, thrOffset, "ptr"))
    DllCall("CloseHandle", "ptr", hRead2)
    DllCall("CloseHandle", "ptr", hWrite2)
    If (Input) {
        InLen := StrLen(Input) + 2
        VarSetCapacity(InBuf, InLen, 0)
        StrPut(Input . "`r`n", &InBuf, "cp866")
        DllCall("WriteFile", "ptr", hWrite1, "ptr", &InBuf, "uint", InLen
                           , "uint *", BytesWritten, "uint", 0)
    }
    DllCall("CloseHandle", "ptr", hWrite1)
    Output := ""
    Loop {
        If not DllCall("ReadFile", "ptr", hRead1, "ptr", &Buf, "uint", BufSizeByte
                                 , "uint *", BytesRead, "uint", 0)
            Break
        NumPut(0, Buf, BytesRead, "Char")
        Output := StrGet(&Buf, "cp866") . "`n"
        SendMessage, 0xC2, False, &Output,, ahk_id %hwndEdit%
    }
    DllCall("CloseHandle", "ptr", hRead1)
    DllCall("GetExitCodeProcess", "ptr", hChild, "int *", ExitCode)
    DllCall("CloseHandle", "ptr", hChild)
    Return ExitCode
}

17

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

Спасибо! Это, как альтернатива - хорошо.
Но если таки вернуться к исходному коду.
Почему нельзя загрузить весь код в цикл?
Ведь если при закрытии CMD, я открою новое у него не поменяется хендл?

18

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

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

Ведь если при закрытии CMD, я открою новое у него не поменяется хендл?

Тут мне ход мысли не понятен.

19

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

YMP пишет:

Запустите скрипт и введите что-нибудь в нижнее поле

Чаще всего понадобится префикс cmd /c.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

20

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

Тут мне ход мысли не понятен.

Вероятно, это потому что я не понимаю алгоритм кода в первом сообщении.
Может вы сможете написать комментарии?

21

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

teadrinker пишет:

Чаще всего понадобится префикс cmd /c.

Вообще-то это не префикс, а запуск программы cmd.exe с передачей ей аргументов. Если нужно выполнить внутреннюю команду cmd вроде dir, ren, del и т.п., то нужно запускать cmd.exe. Но отдельные программы вроде chkdsk.exe запускаются сами по себе. Разве что их нужно между собой связать, чтобы одна обрабатывала вывод другой.

Malcev пишет:

Вероятно, это потому что я не понимаю алгоритм кода в первом сообщении.
Может вы сможете написать комментарии?

Но там ведь есть комментарии. Какая именно часть кода не понятна?

22

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

 bResult := DllCall("AttachConsole", "uint", dwProcessId, "int")

   ;hConsole := DllCall("CreateFileW", "str", "CONOUT$", "uint", 0x80000000, "uint", 1, "ptr", 0, "uint", 3, "uint", 0x80, "ptr", 0, "ptr")
   hConsole := DllCall("GetStdHandle", "uint", -11, "ptr")

   bResult := DllCall("ReadConsoleOutputCharacter", "ptr", hConsole, "ptr", &Buffer, "uint", 100, "ptr", 0, "uint*", x, "int")

23

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

Подключаемся к окну консоли указанного процесса, получаем хэндл выходного потока, который в данном случае будет также хэндлом активного экранного буфера консоли, и читаем текст из него.

Понял, чего не хватает в коде: в конце должно быть

DllCall("FreeConsole")

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

24

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

О! Спасибо большое, YMP!
А правильней может будет так?

 DllCall("FreeConsole", "int")

И в чем разница между ними?

25

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

Если возвращаемое функцией значение вам не нужно, то и тип его указывать незачем.

26

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

Подскажите, пожалуйста,

msgbox % StrGet(&Buffer, 1000, "UTF-16")

избавляется от символов  `n, `r
Поэтому непонятно, как ее дальше парсить.
Если же тупо скопировать в клипбоард с CMD, либо экспортировать в файл, то `n, `r остаются.

27 (изменено: Malcev, 2014-02-27 00:20:09)

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

Нарыл такой код.
Работает, но есть вопрос:
Как захватить  хендл экрана консоли не ожидая 5 секунд (sleep 5000)?
То есть инфа вывелась - пошел захват.

#NoTrayIcon
DetectHiddenWindows, on
loop
{
   Run, %comspec% /k echo list volume | diskpart.exe, , hide, pid
   sleep 5000

   ; AttachConsole accepts a process ID.
   if !DllCall("AttachConsole","uint",pid)
   {
      MsgBox AttachConsole failed - error %A_LastError%.
      ExitApp
   }

   ; If it succeeded, console functions now operate on the target console window.
   ; Use CreateFile to retrieve a handle to the active console screen buffer.
   hConOut := DllCall("CreateFile", "str", "CONOUT$", "uint", 0xC0000000, "uint", 7, "PTR", 0, "uint", 3, "uint", 0, "PTR", 0, "PTR")
   if hConOut = -1 ; INVALID_HANDLE_VALUE
   {
      MsgBox CreateFile failed - error %A_LastError%.
      ExitApp
   }

   ; Allocate memory for a CONSOLE_SCREEN_BUFFER_INFO structure.
   VarSetCapacity(info, 24, 0)

   ; Get info about the active console screen buffer.
   if(!DllCall("GetConsoleScreenBufferInfo", "PTR", hConOut, "PTR", &info))
   {
      MsgBox GetConsoleScreenBufferInfo failed - error %A_LastError%.
      ExitApp
   }

   ; Determine which section of the buffer is on display.
   ConWinLeft := NumGet(info, 10, "Short")     ; info.srWindow.Left
   ConWinTop := NumGet(info, 12, "Short")      ; info.srWindow.Top
   ConWinRight := NumGet(info, 14, "Short")    ; info.srWindow.Right
   ConWinBottom := NumGet(info, 16, "Short")   ; info.srWindow.Bottom
   ConWinWidth := ConWinRight-ConWinLeft+1
   ConWinHeight := ConWinBottom-ConWinTop+1
 
  ; Allocate memory to read into.
   VarSetCapacity(text, ConWinWidth*ConWinHeight*(A_IsUnicode ? 2 : 1), 0)

   ; Read text.
   if(!DllCall("ReadConsoleOutputCharacter", "PTR", hConOut, "str", text, "uint", ConWinWidth*ConWinHeight, "uint", 0, "PTR*", numCharsRead, "uint"))
   {
      MsgBox ReadConsoleOutputCharacter failed - error %A_LastError%.
      ExitApp
   }

   ; Optional: insert line breaks every %ConWinWidth% characters.
   text := RegExReplace(text, "`a).{" ConWinWidth "}(?=.)", "$0`n")
   text := RegExReplace(text, "^\s+|\s+$")

   DllCall("FreeConsole")
   WinClose, ahk_pid %pid%

   Loop, Parse, text, `n, `r
   {
      If (instr(A_LoopField, "Failed") || instr(A_LoopField, "At Risk"))
      {
         errors = 1
         StringMid, disk, A_LoopField, 16, 1
         break
      }
   }
   If errors = 1
   {
      Menu, Tray, Icon
      Gui,+AlwaysOnTop
      Gui, -MinimizeBox -MaximizeBox
      Gui, Font, s80, Times New Roman
      Gui, Add, Text,, Problema s Raid Disk: %disk%
      Gui, Show,, RaidError
      Gui, +LastFound
      OnMessage(0x112, "WM_SYSCOMMAND")
      Return
   }
   sleep, 3600000
}

WM_SYSCOMMAND(wParam)
{
   if (A_Gui = 1 && wParam = 0xF060) ; SC_CLOSE
      return 0
}

28 (изменено: Malcev, 2014-02-27 13:44:20)

Re: AHK: Как добыть информацию из окна cmd для последующего парсинга

Вот отличный вариант.

#NoTrayIcon
DllCall("AllocConsole")
WinHide % "ahk_id " DllCall("GetConsoleWindow", "ptr")
loop
{
   objShell := ComObjCreate("WScript.Shell")
   objExec := objShell.Exec(ComSpec " /C echo list volume | C:\Windows\System32\diskpart.exe")
   strStdOut := ""
   while, !objExec.StdOut.AtEndOfStream
      strStdOut := objExec.StdOut.ReadAll()
   Loop, Parse, strStdOut, `n, `r
   {
      If (instr(A_LoopField, "Failed") || instr(A_LoopField, "At Risk"))
      {
         errors = 1
         StringMid, disk, A_LoopField, 16, 1
         break
      }
   }
   If errors = 1
   {
      Menu, Tray, Icon
      Gui,+AlwaysOnTop
      Gui, -MinimizeBox -MaximizeBox
      Gui, Font, s80, Times New Roman
      Gui, Add, Text,, Problema s Raid Disk: %disk%
      Gui, Show,, RaidError
      Gui, +LastFound
      OnMessage(0x112, "WM_SYSCOMMAND")
      Return
   }
   sleep, 3600000
}

WM_SYSCOMMAND(wParam)
{
   if (A_Gui = 1 && wParam = 0xF060) ; SC_CLOSE
      return 0
}