0

I'm trying to create some AutoIT code for work to make launching our camera systems a bit easier. As of right now, I've been able to get my code to launch a gui, display a list of camera servers that you can add onto, and be able to add new servers to an internal directory. What I'm having a bit of trouble with is the section in which the program is supposed to switch to the newly launched Milestone software, type in the information, and click enter. I've found that the issue is linked to part of the start() function, in which WinWaitActive($handle) either isn't using the correct handle, or maybe the function I'm using is incorrect?

My end goal is to be able to wait 4 seconds, switch focus from whatever current window is selected to the newly opened Milestone window using either its handle or its PID, then send 2 TABs, the ip address, then send ENTER

#include <MsgBoxConstants.au3>
#include <Array.au3>
#include <ComboConstants.au3>
#include <ButtonConstants.au3>
#include <EditConstants.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <GuiComboBox.au3>

#Region EDIT THE BELOW INFORMATION-------------------------------------------------------------------------------------------------------------------------------------------
Local Const $milestone = "C:\Program Files\Milestone\XProtect Smart Client\Client.exe"          ;Path to software
Local $Title = "Milestone Camera Selector"                                                         
#EndRegion-------------------------------------------------------------------------------------------------------------------------------------------------------------------

#Region Script---------------------------------------------------------------------------------------------------------------------------------------------------------------
Local $iFileExists = FileExists($milestone)                                                 ;Check if the file exists, returns a 1 or 0
Global $list = IniReadSection(@TempDir & "\" & $Title & "\config.ini","Server")             ;Read the ini file, this builds a 2D Array [X][0] is the key [X][1] is the value
HotKeySet("{F1}", "help")

If $iFileExists Then                                                                        ;If FileExists = 1 (exists) then
    If Not FileExists(@TempDir & "\" & $Title) Then                                         ;If config directory does not exists then
        Do
        DirCreate(@TempDir & "\" & $Title)                                                  ;Create a directory in TempDir for the config.ini file to be stored locally
        Until FileExists(@TempDir & "\" & $Title)
        IniWrite(@TempDir & "\" & $Title & "\config.ini", "Server", "", "")
    EndIf
    Local $GUI = GUICreate($Title, 267, 115)                                                ;Create the GUI
    $start = GUICtrlCreateButton("Start", 40, 72, 65, 25)                                   ;Create a button
    $create = GUICtrlCreateButton("Add Server", 160, 72, 65, 25)
    $server = GUICtrlCreateCombo("Select Server", 8, 8, 249, 25, $CBS_DROPDOWN)             ;Create the combo box
    For $i = 1 To UBound($list) - 1                                                         ;Populate combo box with first value (name) from array
        If $list[$i][0] <> "" Then GUICtrlSetData($server, $list[$i][0])                    ;Ensure array is not empty and fill combox with KEYS
    Next
    $servername = GUICtrlCreateInput("Server Name", 8, 40, 121, 21)
    GUICtrlSetState(-1, $GUI_HIDE)
    $serverip = GUICtrlCreateInput("IP Address", 136, 40, 121, 21)
    GUICtrlSetState(-1, $GUI_HIDE)

    GUISetState(@SW_SHOW)

    While 1                                                                                 ;Enter loop until user closes or presses button
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE                                                           ;Exit when closed
                Exit
            Case $start                                                                     ;Store selection into variable, delete the GUI, and run the start function
                $selection = GUICtrlRead($server)
                If $selection <> "Select Server" Then
                    GUIDelete()
                    start()
                EndIf
            Case $create
                If GUICtrlRead($create) = "Add Server" Then
                    GUICtrlSetState($servername, $GUI_SHOW)
                    GUICtrlSetState($serverip, $GUI_SHOW)
                    GUICtrlSetData($create, "Create")
                Else
                    If (GUICtrlRead($servername) <> "" And GUICtrlRead($servername) <> "Server Name" And GUICtrlRead($serverip) <> "" And GUICtrlRead($serverip) <> "IP Address") Then
                        IniWrite(@TempDir & "\" & $Title & "\config.ini", "Server", GUICtrlRead($servername), GUICtrlRead($serverip))
                        GUICtrlSetState($servername, $GUI_HIDE)
                        GUICtrlSetState($serverip, $GUI_HIDE)
                        GUICtrlSetData($create, "Add Server")
                    Else
                        MsgBox($MB_ICONINFORMATION, $Title, "Invalid Server Name and IP Address.")
                    EndIf
                    For $i = UBound($list) - 1 To 0 Step -1
                        _GUICtrlComboBox_DeleteString($server, $i)
                    Next
                    Local $list = IniReadSection(@TempDir & "\" & $Title & "\config.ini","Server")
                    For $i = 1 To UBound($list) - 1
                        If $list[$i][0] <> "" Then GUICtrlSetData($server, $list[$i][0])
                    Next
                EndIf
        EndSwitch
    WEnd
Else                                                                                        ;otherwise, message
    MsgBox($MB_SYSTEMMODAL, "", "Milestone XProtect wasn't found on this computer" & @CRLF)
EndIf

Func start()
    $iPID = Run($milestone,"", @SW_SHOWNOACTIVATE)                                          ;Runs the software and returns the Process ID
    Sleep(4000)                                                                             ;sleep for 4 seconds
    If @error Then MsgBox(0, $Title, "The program could not launch.")                       ;If Run returns a 0 or error, there was a problem
    $handle = _GetHandleFromPID($iPID)                                                      ;Retrieve the handle of the program ran, jump to the function below
    If $handle = 0 Then MsgBox(0, $Title, "The handle could not be found.")                 ;If the Handle returned is 0, there was no match
    Sleep(1000)
    WinWaitActive($handle)                                                                  ;Wait for the program to be activated 
    Send("{TAB}")                                                                           ;send keystrokes to active window
    Send("{TAB}")
    For $i = 0 to UBound($list) - 1                                                         ;Find the IP address that matches the selection and send it
        If $list[$i][0] = $selection Then Send($list[$i][1])
    Next
    Send("{ENTER}")
    Exit                                                                                    ;Exit for now until you can capture a succesful run
EndFunc

func _GetHandleFromPID($PID)                                                                ;Call function with the PID returned from Run function
    $WinList = WinList()                                                                    ;Assign WinList to a variable
    for $i = 1 to $WinList[0][0]                                                            ;Run through each Window in WinList, 2D array [titles][handles]
        If WinGetProcess($WinList[$i][1]) = $PID then                                       ;Look for a Window with the correct PID
            Return $WinList[$i][1]                                                          ;Assign the matching Windows handle to the variable
        EndIf
    Next
    Return 0                                                                                ;Return 0 if no matches were found
EndFunc

Func help()
    ShellExecute(@TempDir & "\" & $Title & "\config.ini")
EndFunc
#EndRegion--------------------------------------------------------------------------------------------------------------------------------------------------------------------

Once again, any help would be greatly appreciated in helping me finish this project I started months ago. Also, if you think this would be easier to achieve in a different programming language like python or AutoHotkey, please let me know! :)

  • I would go for Control... functions in lieu of Send ... I cannot test your script, that is why it is difficult to help. – Xenobiologist Feb 07 '22 at 15:49
  • @Xenobiologist could you explain a bit about Control? I'm pretty new to AutoIT, so I'm not very familiar with what you're referencing – Jamie Small Feb 07 '22 at 16:17
  • Look into the helpfile for ControlSend and ControlSetText and so on. – Xenobiologist Feb 07 '22 at 16:18
  • Oh no, I'm sorry. I must've not been clear. The program is sending text just fine if I don't include the line of code: "WinWaitActive($handle)". What I'm trying to do is wait 4 seconds, switch to the Milestone window, and type in the information I need to type in. The trouble is lying in how to switch focus to the current Milestone window using either its handle or its PID – Jamie Small Feb 07 '22 at 16:27
  • You might be assuming that you can find the handle of the window you want from the PID of the running program, however I have found that a process may have many strange hidden windows in addition to the visible one(s) that will be returned by WinList(). Instead of using the PID to get the window handle, try using a function like WinGetHandle() with the title of the window as a parameter. Then you can use WinGetProcess() if you want to verify that the window belongs to the desired PID. – garbb Feb 08 '22 at 00:33
  • Or, maybe easier: when you are iterating through the windows for the given PID, use WinGetState() to verify that the window is not hidden. – garbb Feb 08 '22 at 00:36
  • My one thought process is this: I plan on having multiple iterations of the same program open at the same time, and Milestone doesn't have any other distinguishing features other than the PID. If I have four Milestone windows open at the same time, they all have the same Title, and are completely indistinguishable from each other, other than their specific process IDs – Jamie Small Feb 11 '22 at 12:24
  • Could it be possible if you could send me an example of what you're thinking of? – Jamie Small Feb 11 '22 at 12:25
  • Get list of windows with winlist(), check each window to see if it is associated with desired PID, then check that window to see if the title matches what you expect (if WinGetTitle($hwnd) = "Title") , also check if window is visible (if (BitAND(WinGetState($hwnd), 2) ) – garbb Feb 14 '22 at 05:08

1 Answers1

0

My apologies on going MIA. I got really busy with things and did not find time to look into the details of the software.

Essentially what is happening is the PID returned is associated with a process called CiceroUIWndFrame (a parent process running in the background) and it is returning the handle of that process. Since that process never get's activated the script just stays paused waiting for it to pop up. Unfortunately due to the fact that the Milestone software does not have it's own PID (look at the Details tab within task manager, you will notice the Milestone software is just called client.exe) we cannot use a good method to obtain the correct handle.

The only solution that I am aware of is to obtain the handle of the active window. So this modified script will run the Milestone X Protect Software, wait 1 second (hopefully long enough to start) and obtain the handle of whatever program is active. It then sleeps for 4 additional seconds, and waits for you to activate the client again. So if you are in another window and wait several minutes, the moment you activate the client login again it will send the keystrokes.

The downfall to this method is that you are relying on the active window after launch to be Milestone (if it takes longer to load, you get the wrong handle, if you click out of focus before 1 second you get the wrong handle).

#include <MsgBoxConstants.au3>
#include <Array.au3>
#include <ComboConstants.au3>
#include <ButtonConstants.au3>
#include <EditConstants.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <GuiComboBox.au3>
#include <AutoItConstants.au3>

#Region EDIT THE BELOW INFORMATION-------------------------------------------------------------------------------------------------------------------------------------------
Local Const $milestone = "C:\Program Files\Milestone\XProtect Smart Client\Client.exe"  ;Path to software
Local $Title = "Program Title"                                                          ;Give me a name
Local $icon = "Full\Path\To\Icon.ico"
#EndRegion-------------------------------------------------------------------------------------------------------------------------------------------------------------------

#Region Script---------------------------------------------------------------------------------------------------------------------------------------------------------------
Local $iFileExists = FileExists($milestone)                                     ;Check if the file exists, returns a 1 or 0
Global $list = IniReadSection(@TempDir & "\" & $Title & "\config.ini","Server") ;Read the ini file, this builds a 2D Array [X][0] is the key [X][1] is the value
HotKeySet("{F1}", "help")

If $iFileExists Then                                                            ;If FileExists = 1 (exists) then
    If Not FileExists(@TempDir & "\" & $Title) Then                             ;If config directory does not exists then
        Do
        DirCreate(@TempDir & "\" & $Title)                                      ;Create a directory in TempDir for the config.ini file to be stored locally
        Until FileExists(@TempDir & "\" & $Title)
        IniWrite(@TempDir & "\" & $Title & "\config.ini", "Server", "", "")
    EndIf
    Local $GUI = GUICreate($Title, 267, 115)                                    ;Create the GUI
    GUISetIcon($icon, -1)                                                       ;Create icon
    $start = GUICtrlCreateButton("Start", 40, 72, 65, 25)                       ;Create a button
    $create = GUICtrlCreateButton("Add Server", 160, 72, 65, 25)
    $server = GUICtrlCreateCombo("Select Server", 8, 8, 249, 25, $CBS_DROPDOWN) ;Create the combo box
    For $i = 1 To UBound($list) - 1                                             ;Populate combo box with first value (name) from array
        If $list[$i][0] <> "" Then GUICtrlSetData($server, $list[$i][0])        ;Ensure array is not empty and fill combox with KEYS
    Next
    $servername = GUICtrlCreateInput("Server Name", 8, 40, 121, 21)
    GUICtrlSetState(-1, $GUI_HIDE)
    $serverip = GUICtrlCreateInput("IP Address", 136, 40, 121, 21)
    GUICtrlSetState(-1, $GUI_HIDE)

    GUISetState(@SW_SHOW)

    While 1                                                                     ;Enter loop until user closes or presses button
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE                                               ;Exit when closed
                Exit
            Case $start                                                         ;Store selection into variable, delete the GUI, and run the start function
                $selection = GUICtrlRead($server)
                If $selection <> "Select Server" Then
                    GUIDelete()
                    start()
                EndIf
            Case $create
                If GUICtrlRead($create) = "Add Server" Then
                    GUICtrlSetState($servername, $GUI_SHOW)
                    GUICtrlSetState($serverip, $GUI_SHOW)
                    GUICtrlSetData($create, "Create")
                Else
                    If (GUICtrlRead($servername) <> "" And GUICtrlRead($servername) <> "Server Name" And GUICtrlRead($serverip) <> "" And GUICtrlRead($serverip) <> "IP Address") Then
                        IniWrite(@TempDir & "\" & $Title & "\config.ini", "Server", GUICtrlRead($servername), GUICtrlRead($serverip))
                        GUICtrlSetState($servername, $GUI_HIDE)
                        GUICtrlSetState($serverip, $GUI_HIDE)
                        GUICtrlSetData($create, "Add Server")
                    Else
                        MsgBox($MB_ICONINFORMATION, $Title, "Invalid Server Name and IP Address.")
                    EndIf
                    For $i = UBound($list) - 1 To 0 Step -1
                        _GUICtrlComboBox_DeleteString($server, $i)
                    Next
                    Local $list = IniReadSection(@TempDir & "\" & $Title & "\config.ini","Server")
                    For $i = 1 To UBound($list) - 1
                        If $list[$i][0] <> "" Then GUICtrlSetData($server, $list[$i][0])
                    Next
                EndIf
        EndSwitch
    WEnd
Else                                                                            ;otherwise, message
    MsgBox($MB_SYSTEMMODAL, "", "Milestone XProtect wasn't found on this computer" & @CRLF)
EndIf

Func start()
    $iPID = Run($milestone)                                                     ;Runs the software and returns the Process ID
    BlockInput(1)
    Sleep(1000)                                                                 ;sleep for 1 second
    BlockInput(0)
    If @error Then MsgBox(0, $Title, "The program could not launch.") Exit      ;If Run returns a 0 or error, there was a problem
    $handle = WinGetHandle("[ACTIVE]")                                          ;Get the handle of the active window, should be Milestone software as it was just ran 1 second ago.
    If $handle = 0 Then MsgBox(0, $Title, "The handle could not be found.") Exit;If the Handle returned is 0, there was no match
    Sleep(4000)
    WinWaitActive($handle)                                                      ;Wait for the program to be activated
    Send("{TAB}")                                                               ;send keystrokes to active window
    Send("{TAB}")
    For $i = 0 to UBound($list) - 1                                             ;Find the IP address that matches the selection and send it
        If $list[$i][0] = $selection Then Send($list[$i][1])
    Next
    Send("{ENTER}")
    Exit                                                                        ;Exit for now until you can capture a succesful run
EndFunc

Func help()
    ShellExecute(@TempDir & "\" & $Title & "\config.ini")
EndFunc
#EndRegion--------------------------------------------------------------------------------------------------------------------------------------------------------------------

The added/modified lines is as follows, in order:

#include <AutoItConstants.au3>
$iPID = Run($milestone) ;Removed the #shownoactivate parameter, we want it to be the active window. Very important.
BlockInput(1) ;suggestion from comment, disables all input briefly
Sleep(1000) ;wait 1 second for program to launch
BlockInput(0) ;enables all user input again
If @error Then MsgBox(0, $Title, "The program could not launch.") Exit      
$handle = WinGetHandle("[ACTIVE]") ;Get the handle of the active window, should be Milestone software as it was just ran 1 second ago.
If $handle = 0 Then MsgBox(0, $Title, "The handle could not be found.") Exit
Sleep(4000)

I tested this with the software on my end and it worked.

Kevin P.
  • 907
  • 7
  • 18