PhilHibbs Posted April 26, 2010 Posted April 26, 2010 (edited) I'm trying to get Valik's ScreenScrape.au3 working. I've made a _WinAPI_MakeDWord function which has been removed from AutoIt to get it to compile, maybe something else has changed since this script was written.It doesn't pick up the percentage, it just jumps to 100% when the process terminates. Any ideas? Here's the full script that I am running:expandcollapse popup#Region Includes #include <WinAPI.au3> #EndRegion Includes #Region Options/Singleton Opt("MustDeclareVars", True) #EndRegion Options/Singleton #Region Global Variables Global Const $STD_INPUT_HANDLE = -10 Global Const $STD_OUTPUT_HANDLE = -11 Global Const $STD_ERROR_HANDLE = -12 Global Const $_CONSOLE_SCREEN_BUFFER_INFO = "short dwSizeX; short dwSizeY;" & _ "short dwCursorPositionX; short dwCursorPositionY; short wAttributes;" & _ "short Left; short Top; short Right; short Bottom; short dwMaximumWindowSizeX; short dwMaximumWindowSizeY" Global Const $_COORD = "short X; short Y" Global Const $_CHAR_INFO = "wchar UnicodeChar; short Attributes" Global Const $_SMALL_RECT = "short Left; short Top; short Right; short Bottom" #EndRegion Global Variables #Region Main body of code Global $g_nExitCode = Main() Exit $g_nExitCode #EndRegion Main body of code #Region Main() ; =================================================================== ; Main() ; ; The main program body. ; Parameters: ; None. ; Returns: ; None. ; =================================================================== Func Main() ; Sanity check to ensure the script is compiled. If Not @Compiled Then MsgBox(4096 + 16, "Error", "This script must be compiled.") Return -1 EndIf ; The 7-Zip command line. Local Const $sCmd = '"c:\Program Files\7-Zip\7z.exe" a c:\txt c:\Existing_Contracts_401.SCUN.txt' ; The show flag, change to @SW_HIDE to prove it works while hidden. Local Const $nShow = @SW_SHOW ; Run the 7-Zip command line. Local $pid = Run($sCmd, "", $nShow) ; Brief pause to let 7-Zip get started. Sleep(1000) ; Create a progress meter. ProgressOn("7-Zip percent", "Backing up...", "0%", 0, 0) ; Open the 7-Zip percent reader. Local $hPercent = Open7ZipPercent($pid) ; Loop on the process existence. While ProcessExists($pid) ; Read the percent and update the progress bar. Local $nPercent = Read7ZipPercent($hPercent) If $nPercent >= 0 Then ProgressSet($nPercent, $nPercent & "%") Sleep(50) WEnd ; We're finished. ProgressSet(100, "100%", "Done") ; Give time to see we're finished. Sleep(1000) ; Cleanup. Close7ZipPercent($hPercent) FileDelete(@ScriptDir & "\Backup.7z") EndFunc ; Main() #EndRegion Main() #Region Primary Functions Func Open7ZipPercent($pid) ; Try to attach to the console of the PID. If _AttachConsole($pid) Then ; The user should treat this as an opaque handle, but internally it contains a handle ; and some structures. Local $vHandle[4] $vHandle[0] = _GetStdHandle($STD_OUTPUT_HANDLE) ; STDOUT Handle $vHandle[1] = DllStructCreate($_CONSOLE_SCREEN_BUFFER_INFO) ; Screen Buffer structure $vHandle[2] = DllStructCreate("dword[4]") ; Data buffer $vHandle[3] = DllStructCreate($_SMALL_RECT) ; SMALL_RECT structure ; Return the handle on success. Return $vHandle EndIf ; Return 0 on failure. Return 0 EndFunc ; Open7ZipPercent() Func Close7ZipPercent(ByRef $vHandle) ; Basic sanity check to validate the handle. If UBound($vHandle) <> 4 Then Return False ; Detach the console. _FreeConsole() ; Destroy the handle. $vHandle = 0 ; Return success. Return True EndFunc ; Close7ZipPercent() Func Read7ZipPercent(ByRef $vHandle) ; Basic sanity check to validate the handle. If UBound($vHandle) = 4 Then ; Create some variables for convenience. Local Const $hStdOut = $vHandle[0] Local Const $pConsoleScreenBufferInfo = $vHandle[1] Local Const $pBuffer = $vHandle[2] Local Const $pSmallRect = $vHandle[3] ; Try to get the screen buffer information. If _GetConsoleScreenBufferInfo($hStdOut, $pConsoleScreenBufferInfo) Then ; Set up the coordinate structures. Local $coordBufferSize = _WinAPI_MakeDWord(4, 1) Local $coordBufferCoord = _WinAPI_MakeDWord(0, 0) ; Load the SMALL_RECT with the projected text position. DllStructSetData($pSmallRect, "Left", DllStructGetData($pConsoleScreenBufferInfo, "dwCursorPositionX") - 4) DllStructSetData($pSmallRect, "Top", DllStructGetData($pConsoleScreenBufferInfo, "dwCursorPositionY")) DllStructSetData($pSmallRect, "Right", DllStructGetData($pConsoleScreenBufferInfo, "dwCursorPositionX")) DllStructSetData($pSmallRect, "Bottom", DllStructGetData($pConsoleScreenBufferInfo, "dwCursorPositionY")) ; Read the console output. If _ReadConsoleOutput($hStdOut, $pBuffer, $coordBufferSize, $coordBufferCoord, $pSmallRect) Then ; This variable holds the output string. Local $sPercent = "" ; We iterate over 3 characters because that's all we read. For $i = 0 To 3 ; We offset the buffer each iteration by 4 bytes because that is the size of the CHAR_INFO ; structure. We do this so we can read each individual character. Local $pCharInfo = DllStructCreate($_CHAR_INFO, DllStructGetPtr($pBuffer) + ($i * 4)) ; Append the character. $sPercent &= DllStructGetData($pCharInfo, "UnicodeChar") Next ; Ensure we read a valid percentage. If so return the cast to a number. If StringRight($sPercent, 1) = "%" Then Return Number($sPercent) EndIf EndIf EndIf ; On failure we return -1 which is obviously not a valid percentage. Return -1 EndFunc ; Read7ZipPercent() #EndRegion Primary Functions #Region Helper Functions Func _GetStdHandle($nHandle) Local $aRet = DllCall("kernel32.dll", "hwnd", "GetStdHandle", "dword", $nHandle) If @error Then Return SetError(@error, @extended, $INVALID_HANDLE_VALUE) Return $aRet[0] EndFunc ; _GetStdHandle() Func _AttachConsole($nPid) Local $aRet = DllCall("kernel32.dll", "int", "AttachConsole", "dword", $nPid) If @error Then Return SetError(@error, @extended, False) Return $aRet[0] EndFunc ; _AttachConsole() Func _FreeConsole() Local $aRet = DllCall("kernel32.dll", "int", "FreeConsole") If @error Then Return SetError(@error, @extended, False) Return $aRet[0] EndFunc ; _FreeConsole() Func _GetConsoleScreenBufferInfo($hConsoleOutput, $pConsoleScreenBufferInfo) Local $aRet = DllCall("kernel32.dll", "int", "GetConsoleScreenBufferInfo", "hwnd", $hConsoleOutput, _ "ptr", _SafeGetPtr($pConsoleScreenBufferInfo)) If @error Then Return SetError(@error, @extended, False) Return $aRet[0] EndFunc ; _GetConsoleScreenBufferInfo() Func _ReadConsoleOutput($hConsoleOutput, $pBuffer, $coordBufferSize, $coordBufferCoord, $pSmallRect) ; We lie about the types for the COORD structures. Since they are the size of an int we expect a packed ; int. Otherwise we may crash or just pass garbage. Local $aRet = DllCall("kernel32.dll", "int", "ReadConsoleOutputW", "ptr", $hConsoleOutput, _ "ptr", _SafeGetPtr($pBuffer), "int", $coordBufferSize, "int", $coordBufferCoord, _ "ptr", _SafeGetPtr($pSmallRect)) If @error Then SetError(@error, @extended, False) ; MsgBox( 0, "ok", $aRet[0] ) Return $aRet[0] EndFunc ; _ReadConsoleOutput() Func _SafeGetPtr(Const ByRef $ptr) Local $_ptr = DllStructGetPtr($ptr) If @error Then $_ptr = $ptr Return $_ptr EndFunc ; _SafeGetPtr() Func _WinAPI_MakeDWord($LoWORD, $HiWORD) Local $tDWord = DllStructCreate("uint32") Local $tWords = DllStructCreate("word;word", DllStructGetPtr($tDWord)) DllStructSetData($tWords, 1, $LoWORD) DllStructSetData($tWords, 2, $HiWORD) Return DllStructGetData($tDWord, 1) EndFunc ;==>_WinAPI_MakeQWord #EndRegion Helper FunctionsIt seems to be failing after the ReadConsoleOutputW function call, which is returning 0 in the first array element.*Update:* Why is it that you only notice your stupid mistakes after you've posted them on a public forum? File this under "you can't just make shit up and expect the computer to know what you are talking about". My _WinAPI_MakeDWord was wrong, I needed to say DllStructCreate("dword") and not DllStructCreate("uint32"). D'oh.Why was _WinAPI_MakeDWord removed? Edited April 26, 2010 by PhilHibbs
PhilHibbs Posted April 26, 2010 Author Posted April 26, 2010 (edited) OK I've got most things working now, I've expanded the code so it picks up the whole window contents, but as soon as the screen scrolls I get nothing back from the _ReadConsoleInput call. Here's my CmdA.au3 library: expandcollapse popup#include-Once #include <WinAPI.au3> ; #INDEX# ======================================================================================================================= ; Title .........: Cmd ; AutoIt Version : 3.3.6++ ; Language ......: English ; Description ...: Functions for manipulating command prompt windows. ; Author(s) .....: PhilHibbs ; =============================================================================================================================== ; #CONSTANTS# =================================================================================================================== Global Const $STD_INPUT_HANDLE = -10 Global Const $STD_OUTPUT_HANDLE = -11 Global Const $STD_ERROR_HANDLE = -12 Global Const $_CONSOLE_SCREEN_BUFFER_INFO = "short dwSizeX; short dwSizeY;" & _ "short dwCursorPositionX; short dwCursorPositionY; short wAttributes;" & _ "short Left; short Top; short Right; short Bottom; short dwMaximumWindowSizeX; short dwMaximumWindowSizeY" Global Const $_COORD = "short X; short Y" Global Const $_CHAR_INFO = "wchar UnicodeChar; short Attributes" Global Const $_SMALL_RECT = "short Left; short Top; short Right; short Bottom" ; =============================================================================================================================== ; #CURRENT# ===================================================================================================================== ;_CmdGetWindow ;_CmdAttachConsole ;_CmdWaitFor ;_CmdWaitList ; =============================================================================================================================== ; #FUNCTION# ==================================================================================================================== ; Name...........: _CmdGetWindow ; Description ...: Locates the window handle for a given Command Prompt process. ; Syntax.........: _CmdGetWindow($pCmd) ; Parameters ....: $pCmd - Process id of the Command Prommpt application ; Return values .: Success - Window handle ; Failure - -1, sets @error ; |1 - Process $pCmd not found ; Author ........: Phil Hibbs (phil at hibbs dot me dot uk) ; Modified.......: ; Remarks .......: ; Related .......: ; Link ..........: ; Example .......: ; =============================================================================================================================== Func _CmdGetWindow( $pCmd ) Local $WinList, $i While True $WinList = WinList() For $i = 1 to $WinList[0][0] If $WinList[$i][0] <> "" And WinGetProcess( $WinList[$i][1] ) = $pCmd Then Return $WinList[$i][1] EndIf Next WEnd EndFunc ;==>_CmdGetWindow ; #FUNCTION# ==================================================================================================================== ; Name...........: _CmdAttachConsole ; Description ...: Locates the console handle for a given Command Prompt process. ; Syntax.........: _CmdAttachConsole($pCmd) ; Parameters ....: $pCmd - Process id of the Command Prommpt application ; Return values .: Success - Window handle structure ; Failure - -1, sets @error ; |1 - Unable to attach console ; |2 - Unable to create file handle ; Author ........: Phil Hibbs (phil at hibbs dot me dot uk) ; Modified.......: ; Remarks .......: ; Related .......: ; Link ..........: ; Example .......: ; =============================================================================================================================== Func _CmdAttachConsole($nPid) ; Try to attach to the console of the PID. Local $aRet = DllCall("kernel32.dll", "int", "AttachConsole", "dword", $nPid) If @error Then Return SetError(@error, @extended, False) If $aRet[0] Then ; The user should treat this as an opaque handle, but internally it contains a handle ; and some structures. Local $vHandle[3] $vHandle[0] = _GetStdHandle($STD_OUTPUT_HANDLE) ; STDOUT Handle $vHandle[1] = DllStructCreate($_CONSOLE_SCREEN_BUFFER_INFO) ; Screen Buffer structure $vHandle[2] = DllStructCreate($_SMALL_RECT) ; SMALL_RECT structure ; Return the handle on success. Return $vHandle EndIf ; Return 0 on failure. Return 0 EndFunc ; _CmdAttachConsole() Func _GetStdHandle($nHandle) Local $aRet = DllCall("kernel32.dll", "hwnd", "GetStdHandle", "dword", $nHandle) If @error Then Return SetError(@error, @extended, $INVALID_HANDLE_VALUE) Return $aRet[0] EndFunc ; _GetStdHandle() ; #FUNCTION# ==================================================================================================================== ; Name...........: _CmdGetText ; Description ...: Gets all the text in a Command Prompt window ; Syntax.........: _CmdGetText($hWin, $hConsole ) ; Parameters ....: $hWin - Window handle ; $hConsole - Console handle ; Return values .: Success - True ; Failure - False ; |1 - Window does not exist ; Author ........: Phil Hibbs (phil at hibbs dot me dot uk) ; Modified.......: ; Remarks .......: ; Related .......: ; Link ..........: ; Example .......: ; =============================================================================================================================== Func _CmdGetText(ByRef $vHandle) ; Basic sanity check to validate the handle. If UBound($vHandle) = 3 Then ; Create some variables for convenience. Local Const $hStdOut = $vHandle[0] Local Const $pConsoleScreenBufferInfo = $vHandle[1] Local Const $pRect = $vHandle[2] ; Try to get the screen buffer information. If _GetConsoleScreenBufferInfo($hStdOut, $pConsoleScreenBufferInfo) Then ; Load the SMALL_RECT with the projected text position. Local $iLeft = DllStructGetData( $pConsoleScreenBufferInfo, "Left") Local $iRight = DllStructGetData( $pConsoleScreenBufferInfo, "Right") Local $iTop = DllStructGetData( $pConsoleScreenBufferInfo, "Top") Local $iBottom = DllStructGetData( $pConsoleScreenBufferInfo, "Bottom") DllStructSetData( $pRect, "Left", $iLeft ) DllStructSetData( $pRect, "Right", $iRight ) DllStructSetData( $pRect, "Top", $iTop ) DllStructSetData( $pRect, "Bottom", $iBottom ) Local $iWidth = $iRight - $iLeft + 1 Local $iHeight = $iBottom - $iTop + 1 ; Set up the coordinate structures. Local $coordBufferCoord = _WinAPI_MakeDWord($iLeft, $iTop) Local $coordBufferSize = _WinAPI_MakeDWord($iWidth, $iHeight) Local $pBuffer = DllStructCreate("dword[" & $iWidth * $iHeight & "]") ; Read the console output. If _CmdReadConsoleOutput($hStdOut, $pBuffer, $coordBufferSize, $coordBufferCoord, $pRect) Then ; This variable holds the output string. Local $sText = "" For $j = 0 To $iHeight - 1 Local $sLine = "" For $i = 0 To $iWidth - 1 ; We offset the buffer each iteration by 4 bytes because that is the size of the CHAR_INFO ; structure. We do this so we can read each individual character. Local $pCharInfo = DllStructCreate($_CHAR_INFO, DllStructGetPtr($pBuffer) + ($j * $iWidth * 4) + ($i * 4)) ; Append the character. $sLine &= DllStructGetData($pCharInfo, "UnicodeChar") Next $sText &= StringStripWS( $sLine, 2 ) & @CRLF Next $sText = StringStripWS( $sText, 2 ) ; Ensure we read a valid percentage. If so return the cast to a number. Return $sText EndIf EndIf EndIf ; On failure we return -1 which is obviously not a valid percentage. Return -1 EndFunc ;==>_CmdGetText Func _GetConsoleScreenBufferInfo($hConsoleOutput, $pConsoleScreenBufferInfo) Local $aRet = DllCall("kernel32.dll", "int", "GetConsoleScreenBufferInfo", "hwnd", $hConsoleOutput, _ "ptr", _SafeGetPtr($pConsoleScreenBufferInfo)) If @error Then Return SetError(@error, @extended, False) Return $aRet[0] EndFunc ; _GetConsoleScreenBufferInfo() Func _CmdReadConsoleOutput($hConsoleOutput, $pBuffer, $coordBufferSize, $coordBufferCoord, $pRect) ; We lie about the types for the COORD structures. Since they are the size of an int we expect a packed ; int. Otherwise we may crash or just pass garbage. Local $aRet = DllCall("kernel32.dll", "int", "ReadConsoleOutputW", "ptr", $hConsoleOutput, _ "ptr", _SafeGetPtr($pBuffer), "int", $coordBufferSize, "int", $coordBufferCoord, _ "ptr", _SafeGetPtr($pRect)) If @error Then SetError(@error, @extended, False) Return $aRet[0] EndFunc ; _CmdReadConsoleOutput() ; #FUNCTION# ==================================================================================================================== ; Name...........: _CmdWaitFor ; Description ...: Waits for a particular string to be found in a Command Prompt window ; Syntax.........: _CmdWaitFor( $hWin, $vHandle, $text, $timeout = -1, $period, $prefix = "" ) ; Parameters ....: $hWin - Window handle ; $text - String to search for ; $timeout - How long to wait for in ms, 0 = look once and return, -1 = keep looking for ever ; $period - How long to pause between each content grab ; $prefix - Prefix string, anything prior to this prefix is discarded before searching for $text ; Return values .: Success - True ; Failure - False ; |1 - Text is not found within the time limit ; |2 - Window does not exist ; Author ........: Phil Hibbs (phil at hibbs dot me dot uk) ; Modified.......: ; Remarks .......: The prefix is for searching for something that might occur multiple times, for instance if you issue a command ; and want to wait for the User@ prompt, the command itself should be the preifx. If you are issuing the same ; command multiple times, you could echo a unique string and use that as the prefix, e.g. ; Send( "echo :cmd123:;ls -l{Enter}" ) ; _CmdWaitFor( $hTelnet, $wTelnet, $User & "@", -1, ":cmd123:" ) ; Related .......: ; Link ..........: ; Example .......: ; =============================================================================================================================== Func _CmdWaitFor( $hWin, ByRef $vHandle, $text, $timeout = Default, $period = Default, $prefix = "" ) Local $bScrInfo, $bScrContent, $timer, $con, $i If $timeout = Default Then $timeout = -1 If $period = Default Then $period = 1000 $timer = TimerInit() While ($timeout <= 0 Or TimerDiff($timer) < $timeout) And WinExists( $hWin ) $con = _CmdGetText( $vHandle ) If $prefix <> "" Then $con = StringMid( $con, StringInStr( $con, $prefix, False, -1 ) + StringLen( $prefix ) ) EndIf If StringInStr( $con, $text ) > 0 Then Return True EndIf If $timeout = 0 Then ExitLoop Sleep($period) WEnd Return False EndFunc ;==>_CmdWaitFor ; #FUNCTION# ==================================================================================================================== ; Name...........: _CmdWaitList ; Description ...: Waits for one of a set of strings to be found in a Command Prompt window ; Syntax.........: _CmdWaitList($hWin, $vHandle, $aText, $timeout = -1, $period, $prefix = "" ) ; Parameters ....: $hWin - Window handle ; $aText - Array of strings to search for ; $timeout - How long to wait for in ms, 0 = look once and return, -1 = keep looking for ever ; $period - How long to pause between each content grab ; $prefix - Prefix string, anything prior to this prefix is discarded before searching for $text ; Return values .: Success - Element number found ; Failure - -1, sets @error ; |1 - Text is not found within the time limit ; |2 - Window does not exist ; Author ........: Phil Hibbs (phil at hibbs dot me dot uk) ; Modified.......: ; Remarks .......: The prefix is for searching for something that might occur multiple times, for instance if you issue a command ; and want to wait for the User@ prompt, the command itself should be the preifx. If you are issuing the same ; command multiple times, you could echo a unique string and use that as the prefix. ; Related .......: ; Link ..........: ; Example .......: ; =============================================================================================================================== Func _CmdWaitList( $hWin, ByRef $vHandle, ByRef $aText, $timeout = Default, $period = Default, $prefix = "" ) Local $timer, $con, $i If $timeout = Default Then $timeout = -1 If $period = Default Then $period = 1000 SendKeepActive( $hWin ) $timer = TimerInit() While ($timeout <= 0 Or TimerDiff($timer) < $timeout) And WinExists( $hWin ) $con = _CmdGetText( $vHandle ) If $prefix <> "" Then $con = StringMid( $con, StringInStr( $con, $prefix, False, -1 ) + StringLen( $prefix ) ) EndIf For $i = 0 To UBound( $aText ) - 1 If StringInStr( $con, $aText[$i] ) > 0 Then Return $i EndIf Next If $timeout = 0 Then ExitLoop Sleep($period) WEnd If Not(WinExists( $hWin )) Then Return SetError(2, 0, -1) Return SetError(1, 0, -1) EndFunc ;==>_CmdWaitList Func _SafeGetPtr(Const ByRef $ptr) Local $_ptr = DllStructGetPtr($ptr) If @error Then $_ptr = $ptr Return $_ptr EndFunc ; _SafeGetPtr() Func _WinAPI_MakeDWord($LoWORD, $HiWORD) Local $tDWord = DllStructCreate("dword") Local $tWords = DllStructCreate("word;word", DllStructGetPtr($tDWord)) DllStructSetData($tWords, 1, $LoWORD) DllStructSetData($tWords, 2, $HiWORD) Return DllStructGetData($tDWord, 1) EndFunc ;==>_WinAPI_MakeDWord And here's my test script: #include <WinAPI.au3> #include "CmdA.au3" $pCmd = Run( "cmd.exe" ) Sleep(1000) $hCmd = _CmdGetWindow( $pCmd ) $hCon = _CmdAttachConsole( $pCmd ) While True $cmdtext = _CmdGetText( $hCon ) MsgBox(0,"prompt",$cmdtext) Sleep(5000) WEnd The demo just launches a command prompt and displays the contents every 5 seconds. *Update*: Looks like my wild guess of writing the Left and Top values into the $coordBufferCoord was wrong, leaving it as 0,0 grabs the visible screen area. Maybe I need to use the Left and Top values as negative offsets to get the scrolled-off-the-top text. Edited April 26, 2010 by PhilHibbs
PhilHibbs Posted April 27, 2010 Author Posted April 27, 2010 (edited) *Update*: Looks like my wild guess of writing the Left and Top values into the $coordBufferCoord was wrong, leaving it as 0,0 grabs the visible screen area. Maybe I need to use the Left and Top values as negative offsets to get the scrolled-off-the-top text.Mmm, nope. If I put anything other then 0,0 in the $coordBufferCoord, it crashes. Any ideas how I get the content that is scrolled off the top of a command prompt window?*Edit:* I think it's something to do with the $pRect value - I tried setting this to 0,0,dwSizeX,dwSizeY but that didn't work. Edited April 27, 2010 by PhilHibbs
BAM5 Posted July 13, 2012 Posted July 13, 2012 To get all the text within the console buffer you'd need to set the following variables like so: Local $iLeft = 0 Local $iTop = 0 Local $iRight = DllStructGetData( $pConsoleScreenBufferInfo, 1) -1 Local $iBottom = DllStructGetData( $pConsoleScreenBufferInfo, 2) -1 Anything that scrolls up past that is gone. dwSizeX and dwSizeY unmodified won't work because those are the max width/height, not the max value for each x/y coordinate. It's like an array, you have 5 elements in an array, but to access the 5th element you use the index 4. Also, the $coordBufferCoord variable must be equal to 0, it wouldn't make sense any other way. It took me hours to find that error from the code I coppied. (I really should've read your last post >_< ) Anyway, not sure if you've figured that out yet, thought I'd help you out since this code will do nicely for a project of mine [center]JSON Encoding UDF[/center]
JohnQSmith Posted July 16, 2012 Posted July 16, 2012 Anyway, not sure if you've figured that out yet, thought I'd help you out since this code will do nicely for a project of mine It took you two years to help out. Had you posted this eight months ago, the last time he was active, he might have read it. Whenever someone says "pls" because it's shorter than "please", I say "no" because it's shorter than "yes".
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now