0

I'm trying to create some automation code for Milestone XProtect, a camera surveillance software, and I need a bit of help. I was originally using Batch scripting and VBScript to attempt my goal, but it doesn't seem to work for me

    #include <MsgBoxConstants.au3>                          ;Import Message Box

Local Const $milestone = "C:\Program Files\Milestone\XProtect Smart Client\Client.exe"  ;Local Variable $milestone set to file path

Local $iFileExists = FileExists($milestone)                     ;Variable that sees if file exists($milestone)

If $iFileExists Then
    Run($milestone)
>   ;[Unknown Variables]                                ;***Figure out the "Window Title", Class, and Instance***
    Send("{TAB}")
    Send("{TAB}")
    Send("[Insert Camera IP Address Here]")                     ;Between [] different for each .exe I'll create
    Send("{ENTER}")
>   ;[Unknown Variables]                    ;***Figure out items in camera window to see when its fully loaded***
Else
    MsgBox($MB_SYSTEMMODAL, "", "Milestone XProtect wasn't found on this computer" & @CRLF)     ;Error Message "File not Found"
EndIf

As of right now, my code sets a variable of the path to Milestone on the computer, and the if statement checks if the file exists. If it exists, then it'll run the program. The next line of code is supposed to wait until the program is fully loaded before sending two tab keys, the ip address to the cameras server, then and enter key. The last line of code in the if statement is supposed to check and see if the cameras have loaded up fully before ending the program.

What I need help on are the two sections labeled [Unknown Variables] in my code:

  1. I need to know when the program is loaded up to the server selection screen
  2. I need to know when the cameras server has loaded completely before I end the program

Can anyone help?

  • No problem, there are a few things to clarify on. As it stands the script is running once and only once. Do you want it to run once? What do you want it to do if it can't detect the program running? I don't mind posting the full script with changes after some feedback. – Kevin P. Jan 17 '22 at 15:25
  • For the second variable, it can be tricky to read programs to see if the camera's are running, especially without me knowing what the program looks like. What is the reason behind loading them completely before ending? Do you want it to retry after a timeout? – Kevin P. Jan 17 '22 at 16:11
  • Thank you for the quick reply @KevinP. The script should only have to run once per server. I would like to open the program, select the camera server I want to open, and click run. As for wanting to see when the camera server is loaded completely, I would like to have it retry after a timeout. Sometimes a server won't load because of a connection issue, but they'll relaunch after a few minutes. If this isn't possible, then that's alright. The second variable was more of me being interested in the possibility than a requirement – Jamie Small Jan 17 '22 at 17:11
  • I plan on being able to run the script several times to open several instances of the Milestone XProtect software so that I'm able to see several separate camera servers. If you need any further clarification, feel free to ask for anything – Jamie Small Jan 17 '22 at 17:13
  • I also created a small sudo-program with batch programming and HTML to use for the time being, if you'd like to see what I'm doing there. It works pretty well, but it's kind of all over the place, and would love to simplify it with Python or Java programming instead - preferably Python – Jamie Small Jan 17 '22 at 17:15
  • When you say "select the camera server I want to open, and click run." is the the part of the script that is entering the IP address and pressing enter? Or you want this script to offer different IP addresses from a drop down before automation? – Kevin P. Jan 17 '22 at 17:22
  • I have posted an answer to hopefully solve your issues, along with annotations. Please let me know if you experience any issues or need further assistance. As for Python, I am unfamiliar with that language, but I can assist you with AutoIt. – Kevin P. Jan 17 '22 at 18:33
  • I was aiming to be able to have the program offer different IP addresses from a drop down, but instead of ip addresses being shown, the location of the server in place of the ip address (i.e. 192.168.0.0 = Austin, TX, 192.168.0.1 = Memphis, TN) – Jamie Small Jan 18 '22 at 15:19
  • That will take some significant changes but I will post an update shortly with an method. – Kevin P. Jan 18 '22 at 20:41
  • I have posted an update. You're getting into trickier but much more fun territory. Take a look at the annotations and see if you can understand what's happening. – Kevin P. Jan 18 '22 at 21:46

1 Answers1

0

Solution

I have put together some of the missing elements you needed in order to accomplish this task while maintaining simplicity, as well as added annotations to help describe each line.

There are several ways to continue the automation process, such as offering a drop down list of different IP Address's or an Input box instead of hardcoding the address. You will just have to evolve the script over time adding one element at a time.

AutoIt Script

#include <MsgBoxConstants.au3>

Local Const $milestone = "C:\Program Files\Milestone\XProtect Smart Client\Client.exe" ;Path to software

Local $iFileExists = FileExists($milestone)                                 ;Check if the file exists, returns a 1 or 0

If $iFileExists Then                                                        ;If FileExists = 1 (exists)
    ;$inputbox = InputBox("Title", "Enter the IP Address of the camera.", "127.0.0.1")  ;Uncomment out if you would like to prompt for input for the IP Address. Used on line 19.
    ;If @error Then Exit                                                    ;@Error can mean Cancel button was pushed, so exit if canceled.
    $iPID = Run($milestone)                                                 ;Runs the software and returns the Process ID
    Sleep(1000)                                                             ;sleep for 1 second
    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
    WinWaitActive($handle)                                                  ;Wait for the program to be activated
    Send("{TAB}")                                                           ;send keystrokes to active window
    Send("{TAB}")
    Send("[Insert Camera IP Address Here]")                                 ;Hardcoded IP Address
    ;Send($inputbox)                                                            ;Uncomment out this line and comment out the above line to utilize the input box method instead of hardcoding IP Addresses.
    Send("{ENTER}")
   ;[Unknown Variables]                                                     ;***Figure out items in camera window to see when its fully loaded***
Else
    MsgBox($MB_SYSTEMMODAL, "", "Milestone XProtect wasn't found on this computer" & @CRLF)
EndIf

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

Window Handle

Retrieving the window handle is important as it ensures the keystrokes are being sent to the last ran instance of the Milestone software, if several are running.

Thoughts On Retrying

As for the second question, there are many ways you could achieve checking for a connection with a timeout to retry. However, it mostly depends on the interaction available via AutoIt. A good place to start is the AutoIt Window Info Tool. You can drag and drop the crosshair to elements of a window to identify controls. Image below shows the tool when focusing the Windows Calculator.

Example of Calculator.exe

Example

If there is a popup window that displays when a server cannot connect you could intercept that to signal a retry. There are options to search for an image or pixels on the screen if all you have to go off of is a blank video monitor. Or perhaps a good server connection will offer some type of alert that AuoIt can capture and successfully close when satisfied, or retry in X seconds if not.

UPDATE

Here is the program with the GUI and Combo Box option, this utilizes 2D arrays.

#include <MsgBoxConstants.au3>
#include <Array.au3>
#include <ComboConstants.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>

Local Const $milestone = "C:\Program Files\Milestone\XProtect Smart Client\Client.exe" ;Path to software

Local $iFileExists = FileExists($milestone)                                     ;Check if the file exists, returns a 1 or 0
Global $list = [["Austin, TX","192.168.0.0"], ["Memphis, TN","192.168.0.1"]]    ;Enter all your selections, this builds a 2D Array [X][0] is the name [X][1] is the IP

If $iFileExists Then                                                            ;If FileExists = 1 (exists) then
    Local $GUI = GUICreate("TITLE", 267, 115)                                   ;Create the GUI
    $button = GUICtrlCreateButton("Start", 96, 56, 65, 25)                      ;Create a button
    $server = GUICtrlCreateCombo("Select Server", 8, 8, 249, 25, $CBS_DROPDOWN) ;Create the combo box
    For $i = 0 To UBound($list) - 1                                             ;Populate combo box with first value (name) from array
        If $list[$i][0] <> "" Then $var = GUICtrlSetData($server, $list[$i][0]) ;Ensure array is not empty
    Next

    GUISetState(@SW_SHOW)

    While 1                                                                     ;Enter loop until user closes or presses button
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE                                               ;Exit when closed
                Exit
            Case $button                                                        ;Store selection into variable, delete the GUI, and run the start function
                $selection = GUICtrlRead($server)
                GUIDelete()
                start()
        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
    Sleep(1000)                                                                 ;sleep for 1 second
    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
    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
   ;[Unknown Variables]                                                         ;***Figure out items in camera window to see when its fully loaded***
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

UPDATE #2

  1. I have added a way to add new servers to the list and store them locally.
  2. I made a hidden hotkey to open up that file if changes need to be made or servers deleted, you can enter that file by pressing the F1 key on your keyboard. I will let you figure out how to further customize this.
  3. If no server is selected nothing happens, otherwise start the program.
  4. Currently this will launch the program out of focus and wait until it has focus again to input the keystrokes (in case someone wants to wait 1 second or 100 seconds before clicking back in the program).
  5. If this still does not suffice or the window takes control after the splash screen by default then change the sleep timer from 1000 (1 second) to whatever you want.
  6. Pay attention to the first region in the script, it requires some customizing. Set the path to the program, the title of THIS program, and an icon file.
  7. To compile it just use the AutoIt compiler, this will allow the icon to work properly.

#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 = "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,"", @SW_SHOWNOACTIVATE)                              ;Runs the software and returns the Process ID
    Sleep(1000)                                                                 ;sleep for 1 second
    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--------------------------------------------------------------------------------------------------------------------------------------------------------------------
Kevin P.
  • 907
  • 7
  • 18
  • I tried out this updated code you posted, but it still isn't able to detect when the Milestone program has loaded fully before entering the ip address @Kevin P. – Jamie Small Jan 18 '22 at 16:19
  • That is probably due to a splash screen displaying or the program needing loading time. What you can do is change the sleep timer to something much longer, unfortunately this method is hardcoded and can still fail or take uncomfortably long. Your other option is to find some type of notification that the program is up and running and ready to accept the input. Is there anything you can think of, such as a running process? – Kevin P. Jan 18 '22 at 17:43
  • The closest I found in terms of telling when the splash screen has finished loading is that the CPU usage reaches below 5%. I've looked everywhere for python code, autoIT/autoHotkey code that would read that properly, but idk – Jamie Small Jan 21 '22 at 12:51
  • One other thing: Is it possible to have an option in the GUI to add a new server so that users can easily add location names and ip addresses? – Jamie Small Jan 21 '22 at 12:54
  • Also, if we're unable to detect when the program loads, would it be possible to just have the program wait for around 4-5 seconds between selecting the server and sending the text to Milestone, so that Milestone has some time to load? – Jamie Small Jan 21 '22 at 12:55
  • And one last thing, sorry: If the selection in the combobox is still "select server", could it show an error message saying something along the lines of "please select a server"? – Jamie Small Jan 21 '22 at 13:01
  • I'm sorry if I'm asking too much of you, I'm not well versed in AutoIT, and you're the first person to respond to my post – Jamie Small Jan 21 '22 at 13:02
  • No problem, I don't mind helping. The issue you get into with creating a custom button to add locations and names is this requires a file to be created such as a config.ini file and stored locally. I can show you how to set one up so you can play with it. For the meantime I suggest increases the `sleep(1000)` to `sleep(5000)` to achieve waiting 5 seconds before sending commands. That being said, use the AutoIt Window Info tool to capture the elements of the splash screen and the main program to see if you can spot differences, such as CLASS or TITLE, or if the handle changes. – Kevin P. Jan 21 '22 at 15:49
  • I would love to go in that direction with my program. If I'm able to have a file stored locally that AutoIT can read and write to to store locations and site IP Addresses, that would be fantastic. Also, I've looked at Milestone using the Window Info tool, and stupidly, according to the Window Info tool, there is no discernible difference between the splash screen and the screen that has the login characteristics. So i think for the time being, I'm good with just keeping a timeout option – Jamie Small Jan 21 '22 at 16:21
  • However, is there a way to make sure that the Milestone window that was just opened is brought to the top after the 5 second time out? I would love for my users to be able to continue doing work elsewhere on the computer, and then have the window pop back up after the timeout to enter in the information – Jamie Small Jan 21 '22 at 16:22
  • This is also one last dumb question that doesn't NEED to be answered, but is there a way to assign an icon to the program? I'm going to be running the code through an online compiler to a .exe program so that my coworkers can use it on their desktops without having to install AutoIT with admin privilages – Jamie Small Jan 21 '22 at 16:27
  • See my update please. – Kevin P. Jan 21 '22 at 22:00
  • Thank you so much for the help thus far!! The program looks great, and I'm sure my coworkers will be able to easily use it to help them do their jobs faster. So far, everything is working up until the program is supposed to tab down twice and type the data in. I'm not sure why it's not getting to that stage, and I've tried to make sense of it myself looking through the code, but it's not making much sense to me. TLDR: It'll save ip addresses, and it'll open Milestone, but it won't enter the ip addresses into Milestone – Jamie Small Jan 22 '22 at 08:36
  • I'm going to be at work for 11 1/2 more hours, so I'll be online to provide details etc throughout the day – Jamie Small Jan 22 '22 at 08:36
  • Are you still able to help? If not, all good I understand – Jamie Small Jan 24 '22 at 12:55
  • What that is telling me is the main program after the load screen, has a different handle. So the program stalls at the line `WinWaitActive($handle)` because by the time that function is called your extended sleep time has already passed and the load screen is no longer up and AutoIt never completes that step, it just waits. I will have to attempt some troubleshooting techniques. Otherwise for the time being if you want to remove that line it should work but keep in mind if the program is not in focus the keys will still be sent, but not to the right window. – Kevin P. Jan 24 '22 at 15:04
  • Alrighty! Again, I want to thank you for your help thus far. I eagerly await whatever solution you end up finding for focusing the window. If you would like to test it out yourself, Milestone is a free program you can download. Milestone Xprotect Smart Client 2019 Professional is the version we use at work – Jamie Small Jan 24 '22 at 15:42
  • That would be great! Having the program and seeing what you're seeing would make things much easier haha. I will take a look at it later today, and have an update for you! – Kevin P. Jan 24 '22 at 16:04
  • Amazing, you're the best man! :) – Jamie Small Jan 24 '22 at 16:17
  • And don't worry about accidentally connecting into any camera servers when you test out the program. It'll try to load if you enter an ip address, but if your computer isn't part of the domain associated with the camera server, it'll not work. As long as it ends up entering in the data and clicking enter, I'm sure it'll work for my purposes – Jamie Small Jan 24 '22 at 16:18
  • I just had an idea regarding setting the focus to the newest Milestone instance: instead of using the handle of the window, what if we used the window PID instead to set the focus? I read this on an AutoIT forum, so maybe it'll work? – Jamie Small Jan 24 '22 at 19:39
  • 'MyID = 3572 WinActivate ahk_pid %MyID%' – Jamie Small Jan 24 '22 at 19:40
  • Just checking in, how are things going on your end? @Kevin P. – Jamie Small Jan 25 '22 at 20:37
  • Unfortunately I did not see a way to obtain the version of the software you have. I found a free version but that required granted access through a small survey but having the same version is critical. – Kevin P. Jan 26 '22 at 16:10
  • Sorry for the late response. The link below should take you to the screen to download the correct version of Milestone. Scroll down a bit and click on XProtect Smart Client 64-bit to download [link](https://www.milestonesys.com/support/resources/download-software/?prod=1425&type=11&ver=1585&lang=27) – Jamie Small Jan 28 '22 at 12:09
  • Hey there. Just messaging to check in and see how things are going? Were you able to download the software? @Kevin P. – Jamie Small Jan 31 '22 at 12:55
  • You still there? – Jamie Small Feb 05 '22 at 08:09