0

I'm searching for a way to execute Steam (Steam.exe) and wait for it to be loaded (Not to exited).

I was think a good idea is to list all the child handles and search for a string in the title handles because when steam loads exist a window handle named "Friends", but this is hard to do it for me (APIs) so maybe exist a easy way to wait for a program to load...

Example of what I will do:

' First process
Process.start("Process 1.exe")

' Here will goes an efficient method to wait for application is loaded,
' not sleeping X seconds measuring the loading time or any of that.

' Second process
' Depends on first process fully loaded.
' If first process is not fully loaded (libraries and that) then this process can't run.
Process.start("Processs 2.exe")

UPDATE:

The Main question is how to wait for X process to be loaded, but about the Steam.exe I've noticed too when Steam is loaded tries to read/open some regkeys:

http://img267.imageshack.us/img267/6599/prtscrcapture4u.jpg

Maybe I can do something with code to monitor if one of that RegKeys is accesed?

Something like this?:

Dim RegKey = "HKLM\...blablabla"
Process.start("steam.exe")

While not Regkey.IsAccessed 
  ' Do nothing and wait
Loop

Process.start("Second process.exe")

UPDATE 2:

I'm trying to make a function taking by reference the solution of Amegon, I don't finished the code because I'm getting an error in the line:

Memory = hprocess.PrivateMemorySize64

enter image description here

But I only get the error when I try to launch a "big" application (application that needs much time to load like Photoshop), with "little" apps works good.

Please if someone can help me to simplify/improve the Amegon's solution to make a Generic function and to correct the memory overflow error... Thank you!

This is my code:

' This is the sub that launchs the unfinished function:
Private Sub Launch_Game()
    'Wait_For_Application_To_Load("C:\Games\Steam.exe", "-applaunch 0")
    Wait_For_Application_To_Load("C:\Program Files\Adobe Photoshop CS6 (64 Bit)\Photoshop.exe")
End Sub

Private Function Wait_For_Application_To_Load(ByVal APP_Path As String, Optional ByVal APP_Arguments As String = Nothing)
    Dim File = My.Computer.FileSystem.GetFileInfo(APP_Path)
    Process.Start(APP_Path, APP_Arguments)
    Timer_CheckCPU.Tag = File.Name.Substring(0, File.Name.Length - 4) ' Photoshop
    Timer_CheckCPU.Enabled = True
End Function

Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Integer, ByVal lpBaseAddress As Integer, ByVal lpBuffer As Integer, ByVal nSize As Integer, ByRef lpNumberOfBytesWritten As Integer) As Integer
Private WithEvents Timer_CheckCPU As New Timer

Dim Memory_Value_Changed As Boolean
Dim CPU_Changed As Boolean
Dim CPU_Time As Boolean
Dim Running_Time As Boolean
Private _desiredTime_ms As Integer = 1500

Private Sub Timer_CheckCPU_Tick(sender As Object, ev As EventArgs) Handles Timer_CheckCPU.Tick
    Timer_CheckCPU.Enabled = False
    Dim pProcess() As Process = System.Diagnostics.Process.GetProcessesByName(Timer_CheckCPU.Tag)
    Dim hprocess As Process = pProcess(0)
    If hprocess Is Nothing Then
        Running = False
        Timer_CheckCPU.Enabled = True
        Return
    End If
    Running = True


    ' Here is the error:
    Memory = hprocess.PrivateMemorySize64
    ' MsgBox(hprocess.PrivateMemorySize64.ToString)


    CPUTotal = hprocess.TotalProcessorTime.TotalMilliseconds

    If AllConditionsGood() Then
        If Not (_countdown.IsRunning) Then
            _countdown.Reset()
            _countdown.Start()
        End If
        Dim _elapsed As Integer = _countdown.ElapsedMilliseconds
        If _elapsed >= _desiredTime_ms Then
            Me.TopMost = True
            MsgBox("process loaded")
            Return
        End If
    Else
        _countdown.Reset()
    End If
    Timer_CheckCPU.Enabled = True
End Sub

Private Function AllConditionsGood() As Boolean
    If CPU_Time Then Return False
    If Memory_Value_Changed Then Return False
    If Running_Time Then Return False
    Return True
End Function

Private _countdown As New Stopwatch

Private _Running As Boolean = False
Public WriteOnly Property Running() As Boolean
    Set(ByVal value As Boolean)
        _Running = value
        If value Then
            Running_Time = False
        Else
            Running_Time = True
        End If
    End Set
End Property

Private _CPUTotal As Integer
Public WriteOnly Property CPUTotal() As Integer
    Set(ByVal value As Integer)
        CPU = value - _CPUTotal 'used cputime since last check
        _CPUTotal = value
    End Set
End Property


Private _CPU As Integer
Public WriteOnly Property CPU() As Integer
    Set(ByVal value As Integer)
        If value = 0 Then
            CPU_Time = False
        Else
            CPU_Time = True
        End If
        _CPU = value
    End Set
End Property

Private _Memory As Integer
Public WriteOnly Property Memory() As Integer
    Set(ByVal value As Integer)
        MemoryDiff = Math.Abs(value - _Memory)
        _Memory = value
    End Set
End Property

Private _MemoryDiff As Integer
Public WriteOnly Property MemoryDiff() As Integer
    Set(ByVal value As Integer)
        If value = _MemoryDiff Then
            Memory_Value_Changed = False
        Else
            Memory_Value_Changed = True
        End If
        _MemoryDiff = value
    End Set
End Property
Raidri
  • 17,258
  • 9
  • 62
  • 65
ElektroStudios
  • 19,105
  • 33
  • 200
  • 417
  • have you checked the [GetProcessesByName](http://msdn.microsoft.com/en-us/library/z3w4xdc9.aspx) method of the Process class? Just try with a tool like ProcessExplorer to identify the process name..... – Steve Apr 09 '13 at 16:15
  • And what do you see if you click on the Process button? You are searching the process not the windows – Steve Apr 09 '13 at 16:26
  • Sorry about that, is my first time using spy++, I've edited my question! – ElektroStudios Apr 09 '13 at 16:30
  • I am not sure about monitoring other programs reg key accesses with vb.net, but it is not that difficult to check running windows and subprocesses of those windows. But I guess anything may work, as long as it gives a good hint about loading is done. Since friends list auto-login can be deactivated, it would not be a general solution if you scan for an open friends list – Amegon Apr 09 '13 at 16:33
  • nope, my first idea was to scan for the handle titled "Friends" when Steam is loaded but that handle don't refers to the friends list window auto-login (I don't know exactly which is the refering) because the handle exist if I use any auto-log option (Servers, friends, comunity, shop, etc...). – ElektroStudios Apr 09 '13 at 16:37

2 Answers2

2

The WaitForInputIdle method waits until the application has started and is waiting for input

Dim psi As New ProcessStartInfo("fileName", "arguments")
Dim p As New Process
p.StartInfo = psi
p.Start()
p.WaitForInputIdle()

Note: This will not work for service applications.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • waiting for input will mean waiting for user-input? is a solution only for CLI apps? – ElektroStudios Apr 09 '13 at 16:58
  • +1 for the way of starting the process (and immediately having a handle for it, didn't know that way). Also, if you keep the process handle p and WaitForInputIdle is not successfully (e.g. steam allows user input while it is still loading some components like logging in to friends network), then you can use .Refresh to be able to read the latest values from the process handle p. – Amegon Apr 09 '13 at 17:48
  • @Amegon: Yes, you can call `WaitForInputIdle` only once! Later it will not have any effect any more. I don't know how this method works exactly, but I think that it is wating for the message loop to be started, i.e. you could send it something with `SendKeys` for instance, even if it is still working. – Olivier Jacot-Descombes Apr 09 '13 at 18:10
  • @ElektroHacker: This should work fine for WinForms applications. – Olivier Jacot-Descombes Apr 09 '13 at 18:11
  • @OlivierJacot-Descombes yes, msdn help says that it freezes until the process has the state 'Idle' in the message loop. and also thats why it only works for applications with a message queue, but that should work for any gui application here. However, it may still trigger when not completely loaded, if gui accepts user inputs while another window is still loading/connecting. You can click in steam library while the friends list window is working on connecting to the friends network. You can also use WaitForInputIdle(timeout_in_ms) and put it in a loop to give some feedback while waiting – Amegon Apr 09 '13 at 18:17
  • Tested with some executables but I don't get the desired, perhaps I am asking a lot but you can give me a example or information about that of "you can click on steam library while the friends list window is working"? I don't know how to do that :( Thankyou for your answer – ElektroStudios Apr 09 '13 at 18:41
1

Update I have build an example program to show this memory and cpu observing

To run it create a new project, windows form application, then open the code part and insert the following code. -no need to add any controls, I added them in code manually. I as able to successfully run the code with VS 2012 ultimate on win8 prof x64- :

Option Strict On
Imports System.Runtime.InteropServices

Public Class Form1
    Private WithEvents btnStart As New Button
    Private WithEvents tmrCheckCPU As New Timer

    Private tbRunning As New TextBox
    Private tbCPU As New TextBox
    Private tbMemory As New TextBox
    Private tbTime As New TextBox

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Me.Size = New Size(400, 400)
        With btnStart
            '.Text = "Start DxDiag"
            .Text = "Start Photoshop"
            Me.Controls.Add(btnStart)
            .Location = New Point(50, 50)
            .Size = New Size(200, 50)
        End With
        With tmrCheckCPU
            .Interval = 100
            .Enabled = False
        End With
        With tbRunning
            Me.Controls.Add(tbRunning)
            .Location = New Point(50, 110)
            .Size = New Size(100, 40)
        End With
        With tbCPU
            Me.Controls.Add(tbCPU)
            .Location = New Point(50, 150)
            .Size = New Size(100, 40)
        End With
        With tbMemory
            Me.Controls.Add(tbMemory)
            .Location = New Point(50, 200)
            .Size = New Size(100, 40)
        End With
        With tbTime
            Me.Controls.Add(tbTime)
            .Location = New Point(50, 250)
            .Size = New Size(100, 40)
        End With
    End Sub

    Private Sub btnStart_Clicked(sender As Object, ev As EventArgs) Handles btnStart.Click
        'start Process
        'Process.Start("dxdiag.exe", "/whql:on")
        Process.Start("C:\Program Files\Adobe\Adobe Photoshop CS6 (64 Bit)\Photoshop.exe")
        'start watching Timer
        tmrCheckCPU.Enabled = True
    End Sub

    Private Function FindProcess(MainWindowTitle As String) As System.Diagnostics.Process
        For Each ele As System.Diagnostics.Process In Process.GetProcesses
            If ele.MainWindowTitle = MainWindowTitle Then Return ele
        Next
        Return Nothing
    End Function

    Private Sub tmrCheckCPU_Tick(sender As Object, ev As EventArgs) Handles tmrCheckCPU.Tick
        tmrCheckCPU.Enabled = False
        'Dim name As String = "DirectX Diagnostic Tool"
        Dim name As String = "Adobe Photoshop CS6 Extended"
        Dim hprocess As Process = FindProcess(name)
        If hprocess Is Nothing Then
            Running = False
            tmrCheckCPU.Enabled = True
            Return
        End If
        Running = True
        Memory = hprocess.PrivateMemorySize64
        CPUTotal = hprocess.TotalProcessorTime.TotalMilliseconds

        If AllConditionsGood() Then
            If Not (_countdown.IsRunning) Then
                _countdown.Reset()
                _countdown.Start()
            End If
            Dim _elapsed As Long = _countdown.ElapsedMilliseconds
            If _elapsed >= _desiredTime_ms Then
                tbTime.Text = _desiredTime_ms.ToString
                tbTime.BackColor = Color.LightGreen
                Me.TopMost = True
                Me.Focus()
                MsgBox("process loaded")
                Return
            Else
                tbTime.Text = _elapsed.ToString
                tbTime.BackColor = Color.LightGreen
            End If
        Else
            _countdown.Reset()
        End If
        tmrCheckCPU.Enabled = True
    End Sub

    Private _desiredTime_ms As Integer = 1500

    Private Function AllConditionsGood() As Boolean
        If tbCPU.BackColor <> Color.LightGreen Then Return False
        If tbMemory.BackColor <> Color.LightGreen Then Return False
        If tbRunning.BackColor <> Color.LightGreen Then Return False
        Return True
    End Function

    Private _countdown As New Stopwatch

    Private _Running As Boolean = False
    Public WriteOnly Property Running() As Boolean
        Set(ByVal value As Boolean)
            _Running = value
            If value Then
                tbRunning.Text = "Running"
                tbRunning.BackColor = Color.LightGreen
            Else
                tbRunning.Text = "Not Running"
                tbRunning.BackColor = Color.LightPink
            End If
        End Set
    End Property

    Private _CPUTotal As Double
    Public WriteOnly Property CPUTotal() As Double
        Set(ByVal value As Double)
            CPU = value - _CPUTotal 'used cputime since last check
            _CPUTotal = value
        End Set
    End Property


    Private _CPU As Double
    Public WriteOnly Property CPU() As Double
        Set(ByVal value As Double)
            If value = 0 Then
                tbCPU.BackColor = Color.LightGreen
            Else
                tbCPU.BackColor = Color.LightPink
            End If
            _CPU = value
            tbCPU.Text = value.ToString
        End Set
    End Property

    Private _Memory As Long
    Public WriteOnly Property Memory() As Long
        Set(ByVal value As Long)
            MemoryDiff = Math.Abs(value - _Memory)
            _Memory = value
        End Set
    End Property

    Private _MemoryDiff As Long
    Public WriteOnly Property MemoryDiff() As Long
        Set(ByVal value As Long)
            If value = _MemoryDiff Then
                tbMemory.BackColor = Color.LightGreen
            Else
                tbMemory.BackColor = Color.LightPink
            End If
            _MemoryDiff = value
            tbMemory.Text = value.ToString
        End Set
    End Property

End Class

If you click on the button, it will start dxdiag and shows the change of memory and the difference of total cpu time (compared with last time) in textboxes. If all conditions are met (running, no cpu total time change, no memory change) then a stopwatch will count to 1500 and then a messagebox will say 'finished'

It is dirty written, but I guess you understand the main core (how to find the correct process without any dll declare method), how to read some information of that process after finding it, and in what way to apply the conditions.

The waiting time is meant to assure that a second of pure hard drive loading time will not create the wrong impression that loading is done (no memory or cpu change maybe).

The risk is that.. if a page will automatically appear and show content/advertisement, then maybe the cpu is constantly used. so you may only check for memory change or any other value tht you can access from the Process.

Update corrected code (fixed the wrong data types). Now Option strict is not complaining about my code :)

Amegon
  • 629
  • 6
  • 15
  • Thanks but the memory consumition can variate a lot in the loading time of the first process (steam.exe), I think that is a risk like the time asumming. Really I don't understand what you will mean exactly with "subprocesses" (a sub-thread of the steam.exe, or an standalone executable) but I've monitored the executable with "Process Monitor" of "sysinternals", and I've used now "Spy++" for first time and I don't noticed any "subprocess" but maybe i'm wrong cause i'm not an expert. I will update my question in a few seconds uploading a screenshot. – ElektroStudios Apr 09 '13 at 16:11
  • About the vanishing, I launch steam with an argument to disable the update, that problem is solved. – ElektroStudios Apr 09 '13 at 16:26
  • I am also a very beginner with Spy++, but when working with sendkeys (trying to build a simple bot) I learned that sometimes a specific target must be accessed for sending commands. Therefore with spy++ it is useful to check what else exists as one running program. I am not sure if it is defined as subprocess or subthread. Since steam will probably not send a message "I am finished with loading",maybe you can try to find another sub'object' under steam that only exists if loaded, e.g. showing friends. In that case you can constantly scan for that object and know that steam is loaded when found – Amegon Apr 09 '13 at 16:27
  • Oh your update of the memory seems to be good, but with Steam minimized and in background I can see how the memory values still changing between every 2-30 seconds, the best solution for this application seems to be checking for the handle titles "Friends" but I tried to use the WinAPI for that and I can't, anyway I will try the memory way like a generic solution for other apps, thanks. – ElektroStudios Apr 09 '13 at 16:49
  • Depending on what memory information you read, you may only see a change if the max-memory-border is changed. I think usually it is doubled for some fields, so it does not change so often. According to the help http://msdn.microsoft.com/en-us/library/ccf1tfx0%28v=vs.110%29.aspx look for memory properties that do not return a **max** memory size. This should help to see more changes. – Amegon Apr 09 '13 at 17:42
  • tested your form and it works fantastic with steam, I'll accept your answer but I have a little question. I needed to do a little modification of the name var: Dim name As String = "Directx diagnostic tool" to the equivalent spanish name, then... the code can be improved to not depend on window title names? maybe better with app executable names like "dxdiag.exe"? – ElektroStudios Apr 09 '13 at 18:33
  • Tested for curiosity with Photoshop (30 seconds-40 seconds to be loaded) and i'm getting an error in this line of your form: "Memory = hprocess.PrivateMemorySize64" in english it says something like: "aritmethic operation overflow", i will update my question in few minutes please help me a little more! – ElektroStudios Apr 09 '13 at 20:41
  • I tested it with Photoshop cs6 x64. For me it works, but I added Option Strict, and.. shame on me, some types were wrong. I will update the answer to the correct code. (Just change variables for memory to long, and variables for totalMilliseconds to Double) – Amegon Apr 09 '13 at 20:50
  • I'm using Win7 x64, trying to launch Photoshop x64, and running on VS2012. changed all integers to int64 and now works, thankyou again you helped me a lot! – ElektroStudios Apr 09 '13 at 20:54
  • sorry, all int64 is also wrong, add **option strict on** at very top, and then correct the local variables with wrong conversions. I forgot to add option strict to the code before. It was not very clean, and a bit unpredictable. memory and memorydiff to long, cpu and cputotal to double, in code _elapsed to long, then all is good – Amegon Apr 09 '13 at 21:00
  • Can you create a function based in your code? like in the example of my last update in my question?, I can do it by myself but maybe my result is a poor code, nothing is better than the owner version because you are much more experienced! PS: I have a headache with the shortly var names that you've used xD – ElektroStudios Apr 09 '13 at 21:42