Hide command console when using RunWaitOne() example Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Hide command console when using RunWaitOne() example

Post by teadrinker » 14 Nov 2021, 21:51

This approach doesn't give an ability to interrupt waiting for data since ReadFile is called in the synchronous mode and completely blocks the script.
However I tried the asynchronous approach:

Code: Select all

MsgBox, % ReadStdOut("notepad",,, 3000)
ExitApp

ReadStdOut(cmd, encoding := "", callBack := "", timeout := 0xFFFFFFFF) {
   (encoding = "" && encoding := "cp" . DllCall("GetOEMCP", "UInt"))
   Output := new CmdStdOutAsync(cmd, encoding, callBack)
   start := A_TickCount
   while !Output.complete && A_TickCount - start < timeout
      Sleep, 100
   Sleep, 200
   Return Output.complete ? Output.outData : "time is out"
}

class CmdStdOutAsync
{
   __New(cmd, encoding, callBackFunc := "") {
      UserFunc := IsObject(callBackFunc) ? callBackFunc : Func(callBackFunc)
      this._OutData := []
      this._complete := [false]
      this.Event := new this._Event()
      this.SetCapacity("buffer", 4096)
      pBuffer := this.GetAddress("buffer")
      this.SetCapacity("overlapped", A_PtrSize*3 + 8)
      this.pOverlapped := this.GetAddress("overlapped")
      
      params := [ pBuffer, this.pOverlapped, this.Event.handle
                , this._OutData, encoding, this._complete, UserFunc ]
      if !this.Process := new this._Process(cmd, params*) {
         MsgBox, Failed to create process
         Return false
      }
      this.EventSignal := new this._EventSignal(this.Process, params*)
      this.Process.Read()
   }
   
   complete[] {
      get {
         Return this._complete[1]
      }
   }
   
   outData[] {
      get {
         str := ""
         for k, v in this._OutData
            str .= v
         Return str
      }
   }
   
   __Delete() {
      DllCall("CancelIoEx", "Ptr", this.Process.hPipeRead, "Ptr", this.pOverlapped)
      this.Event.Set()
      this.EventSignal.Clear()
      this.Process.Clear()
      this.SetCapacity("buffer", 0)
      this.buffer := this._OutData := ""
   }
   
   class _Event {
      __New() {
         this.handle := DllCall("CreateEvent", "Int", 0, "Int", 0, "Int", 0, "Int", 0, "Ptr")
      }
      Set() {
         DllCall("SetEvent", "Ptr", this.handle)
      }
      __Delete() {
         DllCall("CloseHandle", "Ptr", this.handle)
      }
   }
   
   class _Process {
      __New(cmd, params*) {
         for k, v in ["pBuffer", "pOverlapped", "hEvent", "Data", "encoding", "complete", "UserFunc"]
            this[v] := params[k]
         this.CreatePipes()
         Return this.CreateProcess(cmd) ? this : false
      }
      
      Clear() {
         DllCall("CloseHandle", "Ptr", this.hPipeRead)
         ( this.hPipeWrite && DllCall("CloseHandle", "Ptr", this.hPipeWrite) )
      }
      
      CreatePipes() {
         static FILE_FLAG_OVERLAPPED := 0x40000000, PIPE_ACCESS_INBOUND := 0x1
              , PIPE_TYPE_BYTE := 0, PIPE_WAIT := 0
              , GENERIC_WRITE := 0x40000000, OPEN_EXISTING := 0x3
              , FILE_ATTRIBUTE_NORMAL := 0x80, HANDLE_FLAG_INHERIT := 0x1
            
         this.hPipeRead := DllCall("CreateNamedPipe", "Str", pipeName := "\\.\pipe\StdOut_" . A_TickCount
                                                    , "UInt", PIPE_ACCESS_INBOUND|FILE_FLAG_OVERLAPPED
                                                    , "UInt", PIPE_TYPE_BYTE|PIPE_WAIT, "UInt", 1
                                                    , "UInt", 4096, "UInt", 4096, "UInt", 120000, "Ptr", 0, "Ptr")
                                                    
         this.hPipeWrite := DllCall("CreateFile", "Str", pipeName, "UInt", GENERIC_WRITE, "UInt", 0, "Ptr", 0
                                                , "UInt", OPEN_EXISTING, "UInt", FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, "Ptr", 0, "Ptr")
         DllCall("SetHandleInformation", "Ptr", this.hPipeWrite, "UInt", HANDLE_FLAG_INHERIT, "UInt", HANDLE_FLAG_INHERIT)
      }
      
      CreateProcess(cmd) {
         static STARTF_USESTDHANDLES := 0x100, CREATE_NO_WINDOW := 0x8000000
         VarSetCapacity(STARTUPINFO , siSize :=    A_PtrSize*9 + 4*8, 0)
         NumPut(siSize              , STARTUPINFO)
         NumPut(STARTF_USESTDHANDLES, STARTUPINFO, A_PtrSize*4 + 4*7)
         NumPut(this.hPipeWrite     , STARTUPINFO, siSize - A_PtrSize*2)
         NumPut(this.hPipeWrite     , STARTUPINFO, siSize - A_PtrSize)
         
         VarSetCapacity(PROCESS_INFORMATION, A_PtrSize*2 + 4*2, 0)
         if !DllCall("CreateProcess", "Ptr", 0, "Str", cmd, "Ptr", 0, "Ptr", 0, "UInt", true, "UInt", CREATE_NO_WINDOW
                                    , "Ptr", 0, "Ptr", 0, "Ptr", &STARTUPINFO, "Ptr", &PROCESS_INFORMATION)
            Return this.Clear()
         DllCall("CloseHandle", "Ptr", this.hPipeWrite), this.hPipeWrite := 0
         Return true
      }
      
      Read() {
         DllCall("RtlZeroMemory", "Ptr", this.pOverlapped, "Ptr", A_PtrSize*3 + 8)
         NumPut(this.hEvent, this.pOverlapped + A_PtrSize*2 + 8)
         
         bool := DllCall("ReadFile", "Ptr", this.hPipeRead, "Ptr", this.pBuffer, "UInt", 4096, "UIntP", size := 0, "Ptr", this.pOverlapped)
         if bool {
            this.Data.Push( str := StrGet(this.pBuffer, size, this.encoding) )
            ( this.UserFunc && this.UserFunc.Call(str) )
            this.Read()
         }
         else if !bool && A_LastError != ERROR_IO_PENDING := 997
            this.complete[1] := true
      }
   }
   
   class _EventSignal {
      __New(params*) {
         for k, v in ["StdOut", "pBuffer", "pOverlapped", "hEvent", "Data", "encoding", "complete", "UserFunc"]
            this[v] := params[k]
         this.msg := DllCall("RegisterWindowMessage", "Str", "WM_EVENTSIGNAL")
         OnMessage(this.msg, this.OnEvent := ObjBindMethod(this, "WM_EVENTSIGNAL"))
         this.WaitFn := new this.WaitFunc(this.hEvent, A_ScriptHwnd, this.msg)
         this.Thread := new this._Thread( this.WaitFn.startAddress )
      }
      
      Clear() {
         this.Thread.Wait()
         OnMessage(this.msg, this.OnEvent, 0)
         this.WaitFn := this.OnEvent := ""
      }
      
      WM_EVENTSIGNAL(hEvent) {
         if (this.hEvent != hEvent)
            Return
         if !DllCall("GetOverlappedResult", "Ptr", hEvent, "Ptr", this.pOverlapped, "UIntP", size := 0, "UInt", false)
            Return this.complete[1] := true
         this.Data.Push( str := StrGet(this.pBuffer, size, this.encoding) )
         ( this.UserFunc && this.UserFunc.Call(str) )
         this.StdOut.Read()
         this.Thread.Wait()
         this.Thread := new this._Thread( this.WaitFn.startAddress )
      }
      
      class WaitFunc {
         MEM_COMMIT := 0x1000, MEM_DECOMMIT := 0x4000, PAGE_EXECUTE_READWRITE := 0x40, size := A_PtrSize = 4 ? 49 : 85
         __New(hEvent, hWnd, msg, timeout := -1) {
            this.ptr := ptr := DllCall("VirtualAlloc", "Ptr", 0, "Ptr", this.size, "UInt", this.MEM_COMMIT
                                                     , "UInt", this.PAGE_EXECUTE_READWRITE, "Ptr")
            for dll, api in {kernel32: "WaitForSingleObject", user32: "PostMessageW"} {
               hModule := DllCall("GetModuleHandle", "Str", dll, "Ptr")
               pFunc   := DllCall("GetProcAddress" , "Ptr", hModule, "AStr", api, "Ptr")
               NumPut(pFunc, ptr + A_PtrSize*(A_Index - 1))
            }
            if (A_PtrSize = 4) {
               NumPut(0x68   , ptr +  8)
               NumPut(timeout, ptr +  9)          , NumPut(0x68  , ptr + 13)
               NumPut(hEvent , ptr + 14)          , NumPut(0x15FF, ptr + 18)
               NumPut(ptr    , ptr + 20)          , NumPut(0x6850, ptr + 24)
               NumPut(hEvent , ptr + 26)          , NumPut(0x68  , ptr + 30)
               NumPut(msg    , ptr + 31)          , NumPut(0x68  , ptr + 35)
               NumPut(hWnd   , ptr + 36)          , NumPut(0x15FF, ptr + 40)
               NumPut(ptr + 4, ptr + 42)          , NumPut(0xC2  , ptr + 46, "UChar")
               NumPut(4      , ptr + 47, "UShort")
            }
            else {
               NumPut(0x53      , ptr + 16)
               NumPut(0x20EC8348, ptr + 17)        , NumPut(0xBACB8948, ptr + 21)
               NumPut(timeout   , ptr + 25)        , NumPut(0xB948    , ptr + 29)
               NumPut(hEvent    , ptr + 31)        , NumPut(0x15FF    , ptr + 39)
               NumPut(-45       , ptr + 41)        , NumPut(0xB849    , ptr + 45)
               NumPut(hEvent    , ptr + 47)        , NumPut(0xBA      , ptr + 55)
               NumPut(msg       , ptr + 56)        , NumPut(0xB948    , ptr + 60)
               NumPut(hWnd      , ptr + 62)        , NumPut(0xC18941  , ptr + 70)
               NumPut(0x15FF    , ptr + 73)        , NumPut(-71       , ptr + 75)
               NumPut(0x20C48348, ptr + 79, "UInt"), NumPut(0xC35B    , ptr + 83, "UShort")
            }
            this.startAddress := ptr + A_PtrSize*2
         }
         __Delete() {
            DllCall("VirtualFree", "Ptr", this.ptr, "Ptr", this.size, "UInt", this.MEM_DECOMMIT)
         }
      }
      
      class _Thread {
         __New(startAddress) {
            if !this.handle := DllCall("CreateThread", "Int", 0, "Int", 0, "Ptr", startAddress, "Int", 0, "UInt", 0, "Int", 0, "Ptr")
               throw Exception("Failed to create thread.`nError code: " . A_LastError)
         }
         Wait() {
            DllCall("WaitForSingleObject", "Ptr", this.handle, "Int", -1)
         }
         __Delete() {
            DllCall("CloseHandle", "Ptr", this.handle)
         }
      }
   }
}
Last edited by teadrinker on 06 Apr 2022, 03:16, edited 1 time in total.

User avatar
fade2gray
Posts: 85
Joined: 21 Apr 2015, 12:28

Re: Hide command console when using RunWaitOne() example

Post by fade2gray » 18 Mar 2022, 06:10

@teadrinker Having to remind my self how to achieve this again, I stumbled on the following, courtesy Dieisson Silva dos Santos...

Code: Select all

MsgBox % RunWaitOne("dir " A_ScriptDir)

RunWaitOne(command) {
    DetectHiddenWindows On
    Run %ComSpec%,, Hide, pid
    WinWait ahk_pid %pid%
    DllCall("AttachConsole", "UInt", pid)

    shell := ComObjCreate("WScript.Shell")
    exec := shell.Exec(ComSpec " /C " command)

    DllCall( "FreeConsole" )
    return exec.StdOut.ReadAll()
}[/ccode]

teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Hide command console when using RunWaitOne() example

Post by teadrinker » 18 Mar 2022, 07:41

If this approach suits your needs, it's ok. The disadvantage is that you have no ability to read stdout until the process finishes. The best code I've seen for stdout reading is this one.

User avatar
fade2gray
Posts: 85
Joined: 21 Apr 2015, 12:28

Re: Hide command console when using RunWaitOne() example

Post by fade2gray » 18 Mar 2022, 09:15

It does, but thanks for the link - good for future reference.

Edit: @teadrinker My above solution actually caused me problems. Thank you for providing the link to SKAN's RunCMD.

c7aesa7r
Posts: 209
Joined: 02 Jun 2016, 21:09

Re: Hide command console when using RunWaitOne() example

Post by c7aesa7r » 28 Apr 2022, 14:39

teadrinker wrote:
14 Nov 2021, 21:51
This approach doesn't give an ability to interrupt waiting for data since ReadFile is called in the synchronous mode and completely blocks the script.
However I tried the asynchronous approach:

Code: Select all

MsgBox, % ReadStdOut("notepad",,, 3000)
ExitApp

ReadStdOut(cmd, encoding := "", callBack := "", timeout := 0xFFFFFFFF) {
   (encoding = "" && encoding := "cp" . DllCall("GetOEMCP", "UInt"))
   Output := new CmdStdOutAsync(cmd, encoding, callBack)
   start := A_TickCount
   while !Output.complete && A_TickCount - start < timeout
      Sleep, 100
   Sleep, 200
   Return Output.complete ? Output.outData : "time is out"
}

class CmdStdOutAsync
{
   __New(cmd, encoding, callBackFunc := "") {
      UserFunc := IsObject(callBackFunc) ? callBackFunc : Func(callBackFunc)
      this._OutData := []
      this._complete := [false]
      this.Event := new this._Event()
      this.SetCapacity("buffer", 4096)
      pBuffer := this.GetAddress("buffer")
      this.SetCapacity("overlapped", A_PtrSize*3 + 8)
      this.pOverlapped := this.GetAddress("overlapped")
      
      params := [ pBuffer, this.pOverlapped, this.Event.handle
                , this._OutData, encoding, this._complete, UserFunc ]
      if !this.Process := new this._Process(cmd, params*) {
         MsgBox, Failed to create process
         Return false
      }
      this.EventSignal := new this._EventSignal(this.Process, params*)
      this.Process.Read()
   }
   
   complete[] {
      get {
         Return this._complete[1]
      }
   }
   
   outData[] {
      get {
         str := ""
         for k, v in this._OutData
            str .= v
         Return str
      }
   }
   
   __Delete() {
      DllCall("CancelIoEx", "Ptr", this.Process.hPipeRead, "Ptr", this.pOverlapped)
      this.Event.Set()
      this.EventSignal.Clear()
      this.Process.Clear()
      this.SetCapacity("buffer", 0)
      this.buffer := this._OutData := ""
   }
   
   class _Event {
      __New() {
         this.handle := DllCall("CreateEvent", "Int", 0, "Int", 0, "Int", 0, "Int", 0, "Ptr")
      }
      Set() {
         DllCall("SetEvent", "Ptr", this.handle)
      }
      __Delete() {
         DllCall("CloseHandle", "Ptr", this.handle)
      }
   }
   
   class _Process {
      __New(cmd, params*) {
         for k, v in ["pBuffer", "pOverlapped", "hEvent", "Data", "encoding", "complete", "UserFunc"]
            this[v] := params[k]
         this.CreatePipes()
         Return this.CreateProcess(cmd) ? this : false
      }
      
      Clear() {
         DllCall("CloseHandle", "Ptr", this.hPipeRead)
         ( this.hPipeWrite && DllCall("CloseHandle", "Ptr", this.hPipeWrite) )
      }
      
      CreatePipes() {
         static FILE_FLAG_OVERLAPPED := 0x40000000, PIPE_ACCESS_INBOUND := 0x1
              , PIPE_TYPE_BYTE := 0, PIPE_WAIT := 0
              , GENERIC_WRITE := 0x40000000, OPEN_EXISTING := 0x3
              , FILE_ATTRIBUTE_NORMAL := 0x80, HANDLE_FLAG_INHERIT := 0x1
            
         this.hPipeRead := DllCall("CreateNamedPipe", "Str", pipeName := "\\.\pipe\StdOut_" . A_TickCount
                                                    , "UInt", PIPE_ACCESS_INBOUND|FILE_FLAG_OVERLAPPED
                                                    , "UInt", PIPE_TYPE_BYTE|PIPE_WAIT, "UInt", 1
                                                    , "UInt", 4096, "UInt", 4096, "UInt", 120000, "Ptr", 0, "Ptr")
                                                    
         this.hPipeWrite := DllCall("CreateFile", "Str", pipeName, "UInt", GENERIC_WRITE, "UInt", 0, "Ptr", 0
                                                , "UInt", OPEN_EXISTING, "UInt", FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, "Ptr", 0, "Ptr")
         DllCall("SetHandleInformation", "Ptr", this.hPipeWrite, "UInt", HANDLE_FLAG_INHERIT, "UInt", HANDLE_FLAG_INHERIT)
      }
      
      CreateProcess(cmd) {
         static STARTF_USESTDHANDLES := 0x100, CREATE_NO_WINDOW := 0x8000000
         VarSetCapacity(STARTUPINFO , siSize :=    A_PtrSize*9 + 4*8, 0)
         NumPut(siSize              , STARTUPINFO)
         NumPut(STARTF_USESTDHANDLES, STARTUPINFO, A_PtrSize*4 + 4*7)
         NumPut(this.hPipeWrite     , STARTUPINFO, siSize - A_PtrSize*2)
         NumPut(this.hPipeWrite     , STARTUPINFO, siSize - A_PtrSize)
         
         VarSetCapacity(PROCESS_INFORMATION, A_PtrSize*2 + 4*2, 0)
         if !DllCall("CreateProcess", "Ptr", 0, "Str", cmd, "Ptr", 0, "Ptr", 0, "UInt", true, "UInt", CREATE_NO_WINDOW
                                    , "Ptr", 0, "Ptr", 0, "Ptr", &STARTUPINFO, "Ptr", &PROCESS_INFORMATION)
            Return this.Clear()
         DllCall("CloseHandle", "Ptr", this.hPipeWrite), this.hPipeWrite := 0
         Return true
      }
      
      Read() {
         DllCall("RtlZeroMemory", "Ptr", this.pOverlapped, "Ptr", A_PtrSize*3 + 8)
         NumPut(this.hEvent, this.pOverlapped + A_PtrSize*2 + 8)
         
         bool := DllCall("ReadFile", "Ptr", this.hPipeRead, "Ptr", this.pBuffer, "UInt", 4096, "UIntP", size := 0, "Ptr", this.pOverlapped)
         if bool {
            this.Data.Push( str := StrGet(this.pBuffer, size, this.encoding) )
            ( this.UserFunc && this.UserFunc.Call(str) )
            this.Read()
         }
         else if !bool && A_LastError != ERROR_IO_PENDING := 997
            this.complete[1] := true
      }
   }
   
   class _EventSignal {
      __New(params*) {
         for k, v in ["StdOut", "pBuffer", "pOverlapped", "hEvent", "Data", "encoding", "complete", "UserFunc"]
            this[v] := params[k]
         this.msg := DllCall("RegisterWindowMessage", "Str", "WM_EVENTSIGNAL")
         OnMessage(this.msg, this.OnEvent := ObjBindMethod(this, "WM_EVENTSIGNAL"))
         this.WaitFn := new this.WaitFunc(this.hEvent, A_ScriptHwnd, this.msg)
         this.Thread := new this._Thread( this.WaitFn.startAddress )
      }
      
      Clear() {
         this.Thread.Wait()
         OnMessage(this.msg, this.OnEvent, 0)
         this.WaitFn := this.OnEvent := ""
      }
      
      WM_EVENTSIGNAL(hEvent) {
         if (this.hEvent != hEvent)
            Return
         if !DllCall("GetOverlappedResult", "Ptr", hEvent, "Ptr", this.pOverlapped, "UIntP", size := 0, "UInt", false)
            Return this.complete[1] := true
         this.Data.Push( str := StrGet(this.pBuffer, size, this.encoding) )
         ( this.UserFunc && this.UserFunc.Call(str) )
         this.StdOut.Read()
         this.Thread.Wait()
         this.Thread := new this._Thread( this.WaitFn.startAddress )
      }
      
      class WaitFunc {
         MEM_COMMIT := 0x1000, MEM_DECOMMIT := 0x4000, PAGE_EXECUTE_READWRITE := 0x40, size := A_PtrSize = 4 ? 49 : 85
         __New(hEvent, hWnd, msg, timeout := -1) {
            this.ptr := ptr := DllCall("VirtualAlloc", "Ptr", 0, "Ptr", this.size, "UInt", this.MEM_COMMIT
                                                     , "UInt", this.PAGE_EXECUTE_READWRITE, "Ptr")
            for dll, api in {kernel32: "WaitForSingleObject", user32: "PostMessageW"} {
               hModule := DllCall("GetModuleHandle", "Str", dll, "Ptr")
               pFunc   := DllCall("GetProcAddress" , "Ptr", hModule, "AStr", api, "Ptr")
               NumPut(pFunc, ptr + A_PtrSize*(A_Index - 1))
            }
            if (A_PtrSize = 4) {
               NumPut(0x68   , ptr +  8)
               NumPut(timeout, ptr +  9)          , NumPut(0x68  , ptr + 13)
               NumPut(hEvent , ptr + 14)          , NumPut(0x15FF, ptr + 18)
               NumPut(ptr    , ptr + 20)          , NumPut(0x6850, ptr + 24)
               NumPut(hEvent , ptr + 26)          , NumPut(0x68  , ptr + 30)
               NumPut(msg    , ptr + 31)          , NumPut(0x68  , ptr + 35)
               NumPut(hWnd   , ptr + 36)          , NumPut(0x15FF, ptr + 40)
               NumPut(ptr + 4, ptr + 42)          , NumPut(0xC2  , ptr + 46, "UChar")
               NumPut(4      , ptr + 47, "UShort")
            }
            else {
               NumPut(0x53      , ptr + 16)
               NumPut(0x20EC8348, ptr + 17)        , NumPut(0xBACB8948, ptr + 21)
               NumPut(timeout   , ptr + 25)        , NumPut(0xB948    , ptr + 29)
               NumPut(hEvent    , ptr + 31)        , NumPut(0x15FF    , ptr + 39)
               NumPut(-45       , ptr + 41)        , NumPut(0xB849    , ptr + 45)
               NumPut(hEvent    , ptr + 47)        , NumPut(0xBA      , ptr + 55)
               NumPut(msg       , ptr + 56)        , NumPut(0xB948    , ptr + 60)
               NumPut(hWnd      , ptr + 62)        , NumPut(0xC18941  , ptr + 70)
               NumPut(0x15FF    , ptr + 73)        , NumPut(-71       , ptr + 75)
               NumPut(0x20C48348, ptr + 79, "UInt"), NumPut(0xC35B    , ptr + 83, "UShort")
            }
            this.startAddress := ptr + A_PtrSize*2
         }
         __Delete() {
            DllCall("VirtualFree", "Ptr", this.ptr, "Ptr", this.size, "UInt", this.MEM_DECOMMIT)
         }
      }
      
      class _Thread {
         __New(startAddress) {
            if !this.handle := DllCall("CreateThread", "Int", 0, "Int", 0, "Ptr", startAddress, "Int", 0, "UInt", 0, "Int", 0, "Ptr")
               throw Exception("Failed to create thread.`nError code: " . A_LastError)
         }
         Wait() {
            DllCall("WaitForSingleObject", "Ptr", this.handle, "Int", -1)
         }
         __Delete() {
            DllCall("CloseHandle", "Ptr", this.handle)
         }
      }
   }
}
Hello i was testing your code with this sample:

Code: Select all

Loop, {
    command = Query User %A_UserName%
    Ret := ReadStdOut(command)
    Sleep, 50
}
Its leaking memory, usage of ram keep increasing with time, any idea?

teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Hide command console when using RunWaitOne() example

Post by teadrinker » 28 Apr 2022, 15:01

Hi
Can't reproduce, there is no leak on my computer.

c7aesa7r
Posts: 209
Joined: 02 Jun 2016, 21:09

Re: Hide command console when using RunWaitOne() example

Post by c7aesa7r » 28 Apr 2022, 15:25

teadrinker wrote:
28 Apr 2022, 15:01
Hi
Can't reproduce, there is no leak on my computer.
Im on win10 and you?
also when the script trigger MsgBox, Failed to create process it stuck, never read the return line.

teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Hide command console when using RunWaitOne() example

Post by teadrinker » 28 Apr 2022, 15:36

I tested on Windows 10, and I didn't get "Failed to create process" messages.

c7aesa7r
Posts: 209
Joined: 02 Jun 2016, 21:09

Re: Hide command console when using RunWaitOne() example

Post by c7aesa7r » 28 Apr 2022, 15:39

teadrinker wrote:
28 Apr 2022, 15:36
I tested on Windows 10, and I didn't get "Failed to create process" messages.
I get failed to create process with this command:

command = for /f "skip=1 tokens=3" `%s in ('query user Guest11') do (tscon.exe `%s /dest:console)
Ret := ReadStdOut(command)

teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Hide command console when using RunWaitOne() example

Post by teadrinker » 28 Apr 2022, 15:54

You can't call this command without cmd.exe. I'm not an expert in cmd, but you have to try something like this:

Code: Select all

command := "cmd.exe for /f ""skip=1 tokens=3"" %s in ('query user Guest11') do (tscon.exe %s /dest:console)"
MsgBox, % res := ReadStdOut(command)

teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Hide command console when using RunWaitOne() example

Post by teadrinker » 28 Apr 2022, 16:00

For me it doesn't return anything, I'm not sure that it's possible to launch such expressions by this way.

Chappier
Posts: 44
Joined: 21 Aug 2021, 21:58

Re: Hide command console when using RunWaitOne() example

Post by Chappier » 25 May 2022, 13:25

teadrinker wrote:
14 Nov 2021, 21:51
This approach doesn't give an ability to interrupt waiting for data since ReadFile is called in the synchronous mode and completely blocks the script.
However I tried the asynchronous approach:

Code: Select all

MsgBox, % ReadStdOut("notepad",,, 3000)
ExitApp

ReadStdOut(cmd, encoding := "", callBack := "", timeout := 0xFFFFFFFF) {
   (encoding = "" && encoding := "cp" . DllCall("GetOEMCP", "UInt"))
   Output := new CmdStdOutAsync(cmd, encoding, callBack)
   start := A_TickCount
   while !Output.complete && A_TickCount - start < timeout
      Sleep, 100
   Sleep, 200
   Return Output.complete ? Output.outData : "time is out"
}

class CmdStdOutAsync
{
   __New(cmd, encoding, callBackFunc := "") {
      UserFunc := IsObject(callBackFunc) ? callBackFunc : Func(callBackFunc)
      this._OutData := []
      this._complete := [false]
      this.Event := new this._Event()
      this.SetCapacity("buffer", 4096)
      pBuffer := this.GetAddress("buffer")
      this.SetCapacity("overlapped", A_PtrSize*3 + 8)
      this.pOverlapped := this.GetAddress("overlapped")
      
      params := [ pBuffer, this.pOverlapped, this.Event.handle
                , this._OutData, encoding, this._complete, UserFunc ]
      if !this.Process := new this._Process(cmd, params*) {
         MsgBox, Failed to create process
         Return false
      }
      this.EventSignal := new this._EventSignal(this.Process, params*)
      this.Process.Read()
   }
   
   complete[] {
      get {
         Return this._complete[1]
      }
   }
   
   outData[] {
      get {
         str := ""
         for k, v in this._OutData
            str .= v
         Return str
      }
   }
   
   __Delete() {
      DllCall("CancelIoEx", "Ptr", this.Process.hPipeRead, "Ptr", this.pOverlapped)
      this.Event.Set()
      this.EventSignal.Clear()
      this.Process.Clear()
      this.SetCapacity("buffer", 0)
      this.buffer := this._OutData := ""
   }
   
   class _Event {
      __New() {
         this.handle := DllCall("CreateEvent", "Int", 0, "Int", 0, "Int", 0, "Int", 0, "Ptr")
      }
      Set() {
         DllCall("SetEvent", "Ptr", this.handle)
      }
      __Delete() {
         DllCall("CloseHandle", "Ptr", this.handle)
      }
   }
   
   class _Process {
      __New(cmd, params*) {
         for k, v in ["pBuffer", "pOverlapped", "hEvent", "Data", "encoding", "complete", "UserFunc"]
            this[v] := params[k]
         this.CreatePipes()
         Return this.CreateProcess(cmd) ? this : false
      }
      
      Clear() {
         DllCall("CloseHandle", "Ptr", this.hPipeRead)
         ( this.hPipeWrite && DllCall("CloseHandle", "Ptr", this.hPipeWrite) )
      }
      
      CreatePipes() {
         static FILE_FLAG_OVERLAPPED := 0x40000000, PIPE_ACCESS_INBOUND := 0x1
              , PIPE_TYPE_BYTE := 0, PIPE_WAIT := 0
              , GENERIC_WRITE := 0x40000000, OPEN_EXISTING := 0x3
              , FILE_ATTRIBUTE_NORMAL := 0x80, HANDLE_FLAG_INHERIT := 0x1
            
         this.hPipeRead := DllCall("CreateNamedPipe", "Str", pipeName := "\\.\pipe\StdOut_" . A_TickCount
                                                    , "UInt", PIPE_ACCESS_INBOUND|FILE_FLAG_OVERLAPPED
                                                    , "UInt", PIPE_TYPE_BYTE|PIPE_WAIT, "UInt", 1
                                                    , "UInt", 4096, "UInt", 4096, "UInt", 120000, "Ptr", 0, "Ptr")
                                                    
         this.hPipeWrite := DllCall("CreateFile", "Str", pipeName, "UInt", GENERIC_WRITE, "UInt", 0, "Ptr", 0
                                                , "UInt", OPEN_EXISTING, "UInt", FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, "Ptr", 0, "Ptr")
         DllCall("SetHandleInformation", "Ptr", this.hPipeWrite, "UInt", HANDLE_FLAG_INHERIT, "UInt", HANDLE_FLAG_INHERIT)
      }
      
      CreateProcess(cmd) {
         static STARTF_USESTDHANDLES := 0x100, CREATE_NO_WINDOW := 0x8000000
         VarSetCapacity(STARTUPINFO , siSize :=    A_PtrSize*9 + 4*8, 0)
         NumPut(siSize              , STARTUPINFO)
         NumPut(STARTF_USESTDHANDLES, STARTUPINFO, A_PtrSize*4 + 4*7)
         NumPut(this.hPipeWrite     , STARTUPINFO, siSize - A_PtrSize*2)
         NumPut(this.hPipeWrite     , STARTUPINFO, siSize - A_PtrSize)
         
         VarSetCapacity(PROCESS_INFORMATION, A_PtrSize*2 + 4*2, 0)
         if !DllCall("CreateProcess", "Ptr", 0, "Str", cmd, "Ptr", 0, "Ptr", 0, "UInt", true, "UInt", CREATE_NO_WINDOW
                                    , "Ptr", 0, "Ptr", 0, "Ptr", &STARTUPINFO, "Ptr", &PROCESS_INFORMATION)
            Return this.Clear()
         DllCall("CloseHandle", "Ptr", this.hPipeWrite), this.hPipeWrite := 0
         Return true
      }
      
      Read() {
         DllCall("RtlZeroMemory", "Ptr", this.pOverlapped, "Ptr", A_PtrSize*3 + 8)
         NumPut(this.hEvent, this.pOverlapped + A_PtrSize*2 + 8)
         
         bool := DllCall("ReadFile", "Ptr", this.hPipeRead, "Ptr", this.pBuffer, "UInt", 4096, "UIntP", size := 0, "Ptr", this.pOverlapped)
         if bool {
            this.Data.Push( str := StrGet(this.pBuffer, size, this.encoding) )
            ( this.UserFunc && this.UserFunc.Call(str) )
            this.Read()
         }
         else if !bool && A_LastError != ERROR_IO_PENDING := 997
            this.complete[1] := true
      }
   }
   
   class _EventSignal {
      __New(params*) {
         for k, v in ["StdOut", "pBuffer", "pOverlapped", "hEvent", "Data", "encoding", "complete", "UserFunc"]
            this[v] := params[k]
         this.msg := DllCall("RegisterWindowMessage", "Str", "WM_EVENTSIGNAL")
         OnMessage(this.msg, this.OnEvent := ObjBindMethod(this, "WM_EVENTSIGNAL"))
         this.WaitFn := new this.WaitFunc(this.hEvent, A_ScriptHwnd, this.msg)
         this.Thread := new this._Thread( this.WaitFn.startAddress )
      }
      
      Clear() {
         this.Thread.Wait()
         OnMessage(this.msg, this.OnEvent, 0)
         this.WaitFn := this.OnEvent := ""
      }
      
      WM_EVENTSIGNAL(hEvent) {
         if (this.hEvent != hEvent)
            Return
         if !DllCall("GetOverlappedResult", "Ptr", hEvent, "Ptr", this.pOverlapped, "UIntP", size := 0, "UInt", false)
            Return this.complete[1] := true
         this.Data.Push( str := StrGet(this.pBuffer, size, this.encoding) )
         ( this.UserFunc && this.UserFunc.Call(str) )
         this.StdOut.Read()
         this.Thread.Wait()
         this.Thread := new this._Thread( this.WaitFn.startAddress )
      }
      
      class WaitFunc {
         MEM_COMMIT := 0x1000, MEM_DECOMMIT := 0x4000, PAGE_EXECUTE_READWRITE := 0x40, size := A_PtrSize = 4 ? 49 : 85
         __New(hEvent, hWnd, msg, timeout := -1) {
            this.ptr := ptr := DllCall("VirtualAlloc", "Ptr", 0, "Ptr", this.size, "UInt", this.MEM_COMMIT
                                                     , "UInt", this.PAGE_EXECUTE_READWRITE, "Ptr")
            for dll, api in {kernel32: "WaitForSingleObject", user32: "PostMessageW"} {
               hModule := DllCall("GetModuleHandle", "Str", dll, "Ptr")
               pFunc   := DllCall("GetProcAddress" , "Ptr", hModule, "AStr", api, "Ptr")
               NumPut(pFunc, ptr + A_PtrSize*(A_Index - 1))
            }
            if (A_PtrSize = 4) {
               NumPut(0x68   , ptr +  8)
               NumPut(timeout, ptr +  9)          , NumPut(0x68  , ptr + 13)
               NumPut(hEvent , ptr + 14)          , NumPut(0x15FF, ptr + 18)
               NumPut(ptr    , ptr + 20)          , NumPut(0x6850, ptr + 24)
               NumPut(hEvent , ptr + 26)          , NumPut(0x68  , ptr + 30)
               NumPut(msg    , ptr + 31)          , NumPut(0x68  , ptr + 35)
               NumPut(hWnd   , ptr + 36)          , NumPut(0x15FF, ptr + 40)
               NumPut(ptr + 4, ptr + 42)          , NumPut(0xC2  , ptr + 46, "UChar")
               NumPut(4      , ptr + 47, "UShort")
            }
            else {
               NumPut(0x53      , ptr + 16)
               NumPut(0x20EC8348, ptr + 17)        , NumPut(0xBACB8948, ptr + 21)
               NumPut(timeout   , ptr + 25)        , NumPut(0xB948    , ptr + 29)
               NumPut(hEvent    , ptr + 31)        , NumPut(0x15FF    , ptr + 39)
               NumPut(-45       , ptr + 41)        , NumPut(0xB849    , ptr + 45)
               NumPut(hEvent    , ptr + 47)        , NumPut(0xBA      , ptr + 55)
               NumPut(msg       , ptr + 56)        , NumPut(0xB948    , ptr + 60)
               NumPut(hWnd      , ptr + 62)        , NumPut(0xC18941  , ptr + 70)
               NumPut(0x15FF    , ptr + 73)        , NumPut(-71       , ptr + 75)
               NumPut(0x20C48348, ptr + 79, "UInt"), NumPut(0xC35B    , ptr + 83, "UShort")
            }
            this.startAddress := ptr + A_PtrSize*2
         }
         __Delete() {
            DllCall("VirtualFree", "Ptr", this.ptr, "Ptr", this.size, "UInt", this.MEM_DECOMMIT)
         }
      }
      
      class _Thread {
         __New(startAddress) {
            if !this.handle := DllCall("CreateThread", "Int", 0, "Int", 0, "Ptr", startAddress, "Int", 0, "UInt", 0, "Int", 0, "Ptr")
               throw Exception("Failed to create thread.`nError code: " . A_LastError)
         }
         Wait() {
            DllCall("WaitForSingleObject", "Ptr", this.handle, "Int", -1)
         }
         __Delete() {
            DllCall("CloseHandle", "Ptr", this.handle)
         }
      }
   }
}
is possible to support reading output of powershell?

teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Hide command console when using RunWaitOne() example

Post by teadrinker » 25 May 2022, 15:03

Yep.

Code: Select all

MsgBox, % ReadStdOut("powershell $PSVersionTable")

c7aesa7r
Posts: 209
Joined: 02 Jun 2016, 21:09

Re: Hide command console when using RunWaitOne() example

Post by c7aesa7r » 25 May 2022, 15:19

teadrinker wrote:
25 May 2022, 15:03
Yep.

Code: Select all

MsgBox, % ReadStdOut("powershell $PSVersionTable")
Im testing with powershell, Its also leaking mem:
Spoiler

teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Hide command console when using RunWaitOne() example

Post by teadrinker » 25 May 2022, 16:07

I tested your code, my private working set does not exceed 1900 KB.

Post Reply

Return to “Ask for Help (v1)”