Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

[CLASS] Command Line Interface


  • Please log in to reply
8 replies to this topic
segalion
  • Members
  • 50 posts
  • Last active: Oct 16 2014 09:20 AM
  • Joined: 02 Feb 2011
Command Line Interface class, with stdout and stdin support, unicode and codepage select support, and supposed 64bits too.
Based on code in Sean StdoutToVar thread... (http://www.autohotke...16823&start=105), ONLY FOR AHK_L version.

; command line interface class, based on stdouttovar sean code and others ; v1.0, by segalion
class cli {
    __New(sCmd, sDir="",codepage="") {
      DllCall("CreatePipe","Ptr*",hStdInRd,"Ptr*",hStdInWr,"Uint",0,"Uint",0)
      DllCall("CreatePipe","Ptr*",hStdOutRd,"Ptr*",hStdOutWr,"Uint",0,"Uint",0)
      DllCall("SetHandleInformation","Ptr",hStdInRd,"Uint",1,"Uint",1)
      DllCall("SetHandleInformation","Ptr",hStdOutWr,"Uint",1,"Uint",1)
      if (A_PtrSize=4) {
         VarSetCapacity(pi, 16, 0)
         sisize:=VarSetCapacity(si,68,0)
         NumPut(sisize, si,  0, "UInt"), NumPut(0x100, si, 44, "UInt"),NumPut(hStdInRd , si, 56, "Ptr"),NumPut(hStdOutWr, si, 60, "Ptr"),NumPut(hStdOutWr, si, 64, "Ptr")
         }
      else if (A_PtrSize=8) {
         VarSetCapacity(pi, 24, 0)
         sisize:=VarSetCapacity(si,96,0)
         NumPut(sisize, si,  0, "UInt"),NumPut(0x100, si, 60, "UInt"),NumPut(hStdInRd , si, 80, "Ptr"),NumPut(hStdOutWr, si, 88, "Ptr"), NumPut(hStdOutWr, si, 96, "Ptr")
         }
      pid:=DllCall("CreateProcess", "Uint", 0, "Ptr", &sCmd, "Uint", 0, "Uint", 0, "Int", True, "Uint", 0x08000000, "Uint", 0, "Ptr", sDir ? &sDir : 0, "Ptr", &si, "Ptr", &pi)
      DllCall("CloseHandle","Ptr",NumGet(pi,0))
      DllCall("CloseHandle","Ptr",NumGet(pi,A_PtrSize))
      DllCall("CloseHandle","Ptr",hStdOutWr)
      DllCall("CloseHandle","Ptr",hStdInRd)
         ; Create an object.
		this.hStdInWr:= hStdInWr, this.hStdOutRd:= hStdOutRd, this.pid:=pid
		this.codepage:=(codepage="")?A_FileEncoding:codepage
	}
    __Delete() {
        this.close()
    }
    close() {
       hStdInWr:=this.hStdInWr
       hStdOutRd:=this.hStdOutRd
       DllCall("CloseHandle","Ptr",hStdInWr)
       DllCall("CloseHandle","Ptr",hStdOutRd)
      }
   write(sInput="")  {
		If   sInput <>
			FileOpen(this.hStdInWr, "h", this.codepage).Write(sInput)
      }
	readline() {
       fout:=FileOpen(this.hStdOutRd, "h", this.codepage)
	   this.AtEOF:=fout.AtEOF
       if (IsObject(fout) and fout.AtEOF=0)
         return fout.ReadLine()
      return ""
      }
	read(chars="") {
       fout:=FileOpen(this.hStdOutRd, "h", this.codepage)
       this.AtEOF:=fout.AtEOF
	   if (IsObject(fout) and fout.AtEOF=0)
         return chars=""?fout.Read():fout.Read(chars)
      return ""
      }
}

And a example to use it (with netsh)
netsh:= new cli("netsh.exe","","CP850")
msgbox % "hStdInWr=" netsh.hStdInWr "`thStdOutRd=" netsh.hStdOutRd
sleep 300
netsh.write("firewall`r`n")
sleep 100
netsh.write("show config`r`n")
sleep 1000
out:=netsh.read()
msgbox,, FIREWALL CONFIGURATION:, %out%
netsh.write("bye`r`n")
netsh.close()

Further...
- include stderror support
- serve as origin for expect.like framework for interact with shells (http://en.wikipedia.org/wiki/Expect)

Leef_me
  • Moderators
  • 8510 posts
  • Last active: Sep 10 2015 05:50 AM
  • Joined: 08 Apr 2009
For those of use who don't know "netsh" would you provide a simple example
like "dir *.ahk" ?

segalion
  • Members
  • 50 posts
  • Last active: Oct 16 2014 09:20 AM
  • Joined: 02 Feb 2011
just because of "dir *.ahk" hasn't stdin support (don't wait for commands...)
for this... probably better stdouttovar
This code...
dir_obj:= new cli("dir *.ahk") ; create object for commandline interaction
msgbox, % dir_obj.read() ; read from stdout and output

Probably show empty msg because "dir" end (and close pipes) prior to read the stdout stream...

This code is oriented to interact with command line apps that prompt for commands (telnet, ssh, plink, etc.)

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
My post ended up longer than I intended, so I'll summarize first:
[*:373op0ah]new cli("dir") won't work, but not for the reason you think.
[*:373op0ah]The AtEOF checks should probably be removed.
[*:373op0ah]cli.read() doesn't wait, so can return nothing if the command is still running.
[*:373op0ah]Calling FileOpen() for each call to read() or readline() may cause data loss.

Your dir example just creates an object with some empty pipes and pid = 0. I suggest you add error-checking to __New to avoid this situation. If you have it throw an exception when CreateProcess fails, the behaviour will be similar to the Run command (without UseErrorLevel).

The example fails because dir is not a program, but a command which is interpreted by cmd.exe. To invoke it you need to run something like cmd /c dir. You will need to specifically wait for the command to complete before attempting to read any data.

MSDN states the following:

You cannot use the GetFileSize function with a handle of a nonseeking device such as a pipe or a communications device.
Source: GetFileSize function

You cannot use the SetFilePointerEx function with a handle to a nonseeking device such as a pipe or a communications device.
Source: SetFilePointerEx function

I assumed this also applies to GetFileSizeEx, and interpreted it as meaning that these functions fail when given a handle to a pipe, which would mean:
[*:373op0ah]File.Pos always returns -1, causing File.AtEOF to always returns true.
[*:373op0ah]File.Length always returns 0, causing File.Read() without parameters to always read 0 bytes.
However, your script disproves that - AtEOF is false and Read() returns data if any is available. This is what actually happens:
[*:373op0ah]GetFileSizeEx returns the amount of available data; i.e. how much has been written into the pipe but not yet been read.
[*:373op0ah]SetFilePointerEx indicates that the current position is 0.
Basically, File.AtEOF indicates whether data is available right now and File.Length indicates how much. A false value for AtEOF does not mean there won't be more data, since the command might still be running. If you remove the AtEOF checks, cli.read(chars) and cli.readline() will be more reliable: if the data is not yet available, they will wait until either the required amount of data has been read or the pipe has been closed.

However, cli.read() will still return only the data currently available, not all of the data. If you require all of the data, either specify a large enough value for chars or repeatedly call result .= cli.read() until the cli process terminates. If you call cli.read(n) where n > 0, I believe it will only return less than n characters if the pipe has closed.

There is another problem with your script: creating a new File object each time you call read() or readline() may cause data loss. When you read text from (or write text to) a File object, the actual file I/O is done in blocks. If you call cli.read(n), an arbitrary amount of data >= n is read into a temporary File object's buffer. Any extra data that was read into the buffer is discarded when the object is freed.

What you need to do is call FileOpen once to wrap the handle, and re-use it for each call to read() or readline(). This may also improve performance. However, you still need to be sure to close the handle when the cli object is freed.

Similarly, when you write text, the File object buffers data until the buffer is full, then writes it out to file all at once. Unlike read-buffering, the File object knows what to do with the buffered data when the object is being freed: write it to file. As a result, calling FileOpen each time you call write() forces each write operation to happen immediately (which is good). Alternatively, you could use a single File object and either of the following to "flush" the write buffer:
file.Read(0)
file.__Handle  ; Also discards the read buffer and attempts to roll back the file pointer.


just me
  • Members
  • 1496 posts
  • Last active: Nov 03 2015 04:32 PM
  • Joined: 28 May 2011
@lexikos:

What you need to do is call FileOpen once to wrap the handle, and re-use it for each call to read() or readline(). This may also improve performance. However, you still need to be sure to close the handle when the cli object is freed.

Did you try that? If so, did you get more then one line from readline()? I didn't.

Prefer ahkscript.org for the time being.


Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

Did you try that?

Yes, I did.

If so, did you get more then one line from readline()?

After changing it to use a single File object, yes. Before changing it, no. That was the whole point.

Either way you'll have problems if you don't remove the AtEOF check, as I explained.

segalion
  • Members
  • 50 posts
  • Last active: Oct 16 2014 09:20 AM
  • Joined: 02 Feb 2011
Thanks a lot Lexikos for your post. Is an honor for me to get your attention...

I have been testing lots of stdin/stdout with some apps, and have found some deadlocks in read from stdout precisely (I suppose) "waiting until either the required amount of data has been read or the pipe has been closed."
Thats why I put AtEOF check, and seems to work fine until now...

Thats the code with some of your recomendations... and a rude "send_expect" aproximation...
class cli {
    __New(sCmd, sDir="",codepage="") {
      DllCall("CreatePipe","Ptr*",hStdInRd,"Ptr*",hStdInWr,"Uint",0,"Uint",0)
      DllCall("CreatePipe","Ptr*",hStdOutRd,"Ptr*",hStdOutWr,"Uint",0,"Uint",0)
      DllCall("SetHandleInformation","Ptr",hStdInRd,"Uint",1,"Uint",1)
      DllCall("SetHandleInformation","Ptr",hStdOutWr,"Uint",1,"Uint",1)
      if (A_PtrSize=4) {
         VarSetCapacity(pi, 16, 0)
         sisize:=VarSetCapacity(si,68,0)
         NumPut(sisize, si,  0, "UInt"), NumPut(0x100, si, 44, "UInt"),NumPut(hStdInRd , si, 56, "Ptr"),NumPut(hStdOutWr, si, 60, "Ptr"),NumPut(hStdOutWr, si, 64, "Ptr")
         }
      else if (A_PtrSize=8) {
         VarSetCapacity(pi, 24, 0)
         sisize:=VarSetCapacity(si,96,0)
         NumPut(sisize, si,  0, "UInt"),NumPut(0x100, si, 60, "UInt"),NumPut(hStdInRd , si, 80, "Ptr"),NumPut(hStdOutWr, si, 88, "Ptr"), NumPut(hStdOutWr, si, 96, "Ptr")
         }
      if (DllCall("CreateProcess", "Uint", 0, "Ptr", &sCmd, "Uint", 0, "Uint", 0, "Int", True, "Uint", 0x08000000, "Uint", 0, "Ptr", sDir ? &sDir : 0, "Ptr", &si, "Ptr", &pi)) {
         DllCall("CloseHandle","Ptr",NumGet(pi,0))
         DllCall("CloseHandle","Ptr",NumGet(pi,A_PtrSize))
         DllCall("CloseHandle","Ptr",hStdOutWr)
         DllCall("CloseHandle","Ptr",hStdInRd)
         pid:=NumGet(pi, A_PtrSize*2, "uint") ;DWORD dwProcessId NumGet(pi,A_PtrSize))
         ; Create the object containing object_files and pipe_handlers
         this.codepage:=(codepage="")?A_FileEncoding:codepage
         this.fStdOutRd:=FileOpen(hStdOutRd, "h", this.codepage)
         ;this.fStdInWr:=FileOpen(hStdInWr, "h", this.codepage) ; the write file objet needs to be open for each write
         this.hStdInWr:= hStdInWr, this.hStdOutRd:= hStdOutRd, this.pid:=pid
      }
	}
    __Delete() {
      ;this.fStdInWr.Close()
      this.fStdOutRd.Close()
      this.close()
    }
   close() {
       hStdInWr:=this.hStdInWr
       hStdOutRd:=this.hStdOutRd
       DllCall("CloseHandle","Ptr",hStdInWr)
       DllCall("CloseHandle","Ptr",hStdOutRd)
      }
   write(sInput="")  {
		If   sInput <>
            FileOpen(this.hStdInWr, "h", this.codepage).Write(sInput)
      }
   read(chars="") {
       if (this.fStdOutRd.AtEOF=0)
         return chars=""?this.fStdOutRd.Read():this.fStdOutRd.Read(chars)
      }
   readline() {
       if (this.fStdOutRd.AtEOF=0)
         return this.fStdOutRd.ReadLine(chars)
      }
   send_expect(s1="",e1="",s2="",e2="",s3="",e3="",s4="",e4="",s5="",e5="") {
      gout:=""
      loop, 5 {
		out:=""
        if (s%A_Index%<>"")
			this.write(s%A_Index% "`r`n")
		if ((expect:=e%A_Index%)<>"")  {
			loop, 10 { ;first expect aprox!!!
				out.=this.read()
				if  InStr(out,expect)
					break
				else 
					sleep 25*A_Index ;25,50,75 ... to 250 ms
			}
         }
         gout.=out
      }
   return gout
   }
}

netsh:= new cli("netsh.exe","","CP850")
netsh.send_expect("","netsh>") ;wait for prompt
out:=netsh.send_expect("firewall","netsh firewall>","show config","netsh firewall>")
msgbox,, FIREWALL CONFIGURATION:, %out%
netsh.write("bye`r`n")
netsh.close() 


Please, Lexikos, could you take into account to include full CommandLine support in AHK_L? I think that an automation tool has to be a good commandline interaction support, and I have been looking a lot ago for good stdin support...

PD. You´re right about "dir command"... ;)

ZeLen1y
  • Members
  • 44 posts
  • Last active: Oct 13 2014 09:43 PM
  • Joined: 11 Oct 2006

help please - i can`t make it work sad.png

Windows XP / AutoHotkey Unicode 32-bit 1.1.10.01

telnet := new cli("telnet.exe", "", "CP850")
pr := "Microsoft Telnet> "
out := telnet.send_expect("", pr, "help", pr)
MsgBox, % out
telnet.close()
ps: netsh example works fine!


JonTheNiceGuy
  • Members
  • 22 posts
  • Last active: Oct 03 2013 01:33 PM
  • Joined: 22 Apr 2004

Has anyone had any luck with this for tools like plink?

class cli {
    __New(sCmd, sDir="",codepage="") {
      DllCall("CreatePipe","Ptr*",hStdInRd,"Ptr*",hStdInWr,"Uint",0,"Uint",0)
      DllCall("CreatePipe","Ptr*",hStdOutRd,"Ptr*",hStdOutWr,"Uint",0,"Uint",0)
      DllCall("SetHandleInformation","Ptr",hStdInRd,"Uint",1,"Uint",1)
      DllCall("SetHandleInformation","Ptr",hStdOutWr,"Uint",1,"Uint",1)
      if (A_PtrSize=4) {
         VarSetCapacity(pi, 16, 0)
         sisize:=VarSetCapacity(si,68,0)
         NumPut(sisize, si,  0, "UInt"), NumPut(0x100, si, 44, "UInt"),NumPut(hStdInRd , si, 56, "Ptr"),NumPut(hStdOutWr, si, 60, "Ptr"),NumPut(hStdOutWr, si, 64, "Ptr")
         }
      else if (A_PtrSize=8) {
         VarSetCapacity(pi, 24, 0)
         sisize:=VarSetCapacity(si,96,0)
         NumPut(sisize, si,  0, "UInt"),NumPut(0x100, si, 60, "UInt"),NumPut(hStdInRd , si, 80, "Ptr"),NumPut(hStdOutWr, si, 88, "Ptr"), NumPut(hStdOutWr, si, 96, "Ptr")
         }
      if (DllCall("CreateProcess", "Uint", 0, "Ptr", &sCmd, "Uint", 0, "Uint", 0, "Int", True, "Uint", 0x08000000, "Uint", 0, "Ptr", sDir ? &sDir : 0, "Ptr", &si, "Ptr", &pi)) {
         DllCall("CloseHandle","Ptr",NumGet(pi,0))
         DllCall("CloseHandle","Ptr",NumGet(pi,A_PtrSize))
         DllCall("CloseHandle","Ptr",hStdOutWr)
         DllCall("CloseHandle","Ptr",hStdInRd)
         pid:=NumGet(pi, A_PtrSize*2, "uint") ;DWORD dwProcessId NumGet(pi,A_PtrSize))
         ; Create the object containing object_files and pipe_handlers
         this.codepage:=(codepage="")?A_FileEncoding:codepage
         this.fStdOutRd:=FileOpen(hStdOutRd, "h", this.codepage)
         ;this.fStdInWr:=FileOpen(hStdInWr, "h", this.codepage) ; the write file objet needs to be open for each write
         this.hStdInWr:= hStdInWr, this.hStdOutRd:= hStdOutRd, this.pid:=pid
      }
	}
    __Delete() {
      ;this.fStdInWr.Close()
      this.fStdOutRd.Close()
      this.close()
    }
   close() {
       hStdInWr:=this.hStdInWr
       hStdOutRd:=this.hStdOutRd
       DllCall("CloseHandle","Ptr",hStdInWr)
       DllCall("CloseHandle","Ptr",hStdOutRd)
      }
   write(sInput="")  {
		If   sInput <>
            FileOpen(this.hStdInWr, "h", this.codepage).Write(sInput)
      }
   read(chars="") {
       if (this.fStdOutRd.AtEOF=0)
         return chars=""?this.fStdOutRd.Read():this.fStdOutRd.Read(chars)
      }
   readline() {
       if (this.fStdOutRd.AtEOF=0)
         return this.fStdOutRd.ReadLine(chars)
      }
   send_expect(s1="",e1="",s2="",e2="",s3="",e3="",s4="",e4="",s5="",e5="") {
      gout:=""
      loop, 5 {
		out:=""
        if (s%A_Index%<>"")
			this.write(s%A_Index% "`r`n")
		if ((expect:=e%A_Index%)<>"")  {
			loop, 10 { ;first expect aprox!!!
				out.=this.read()
				if  InStr(out,expect)
					break
				else 
					sleep 25*A_Index ;25,50,75 ... to 250 ms
			}
         }
         gout.=out
      }
   return gout
   }
}

netsh:= new cli("cmd /c plink.exe -V","C:\PROGRAM FILES\PuTTY\","CP850")
out:= netsh.readline()
msgbox, %out%
netsh.close() 

Even this simple script should return "plink: Release 0.60"

 

Ideally, I would be using this to run an SSH session against a remote device...