Jump to content

How to activate window by PID?


vanowm
 Share

Recommended Posts

Hello!

I'm trying create a launcher that would launch an application only once from the same folder (lets call it app.exe). It will allow launch another instance of "app.exe" only if already launched "app.exe" located in different folder.

I'm not sure if I can use WinActivate() function because all windows "app.exe" are the same (titles, class, etc), the only difference between them is the path of the executable and PID.

So I'm using different method to get list of full paths of all processes with name "app.exe" and their PID

This is what I got so far:

$path = "app.exe"

If $CmdLine[0] > 0 Then;filename submitted via command line
  If (StringInStr($CmdLine[1], "\") OR StringInStr($CmdLine[1], "/")) Then;assuming if submitted string conteins / or \ its a full path otherwise its only a filename
    $path = $CmdLine[1]
  Else
    $path = @WorkingDir & '\' & $CmdLine[1]
  EndIf
Else
    $path = @WorkingDir & '\' & $path
EndIf
If (NOT FileExists($path)) Then
    Exit
EndIf

$name = StringReplace($path, "/", "\");lets work only on one type of slashes
$name = StringRight($name, StringLen($name) - StringInStr($name, "\", 0, -1))

$objWMIService = ObjGet("winmgmts:{authenticationLevel=pktPrivacy, (Debug)}\\.\root\CIMV2")
$colItems = $objWMIService.ExecQuery("SELECT * FROM Win32_Process WHERE Caption = '" & $name & "'", "WQL", 0x10 + 0x20)

For $objItem In $colItems
  If $objItem.ExecutablePath == $path Then
    $PID = $objItem.ProcessId  
  ; app.exe from this folder already started, we need restore it's window and activate it.
  ; WinActivate($PID); obviosly it doesn't work
    MsgBox(64, "PID", $PID)
    Exit;
  EndIf
Next

; app.exe not started, so lets launch it
Run($path)

Now if someone could help me out by point out to the right direction how to activate already started "app.exe" window by PID (or other methods) my task would be complete.

Thank you.

Edited by vanowm
Link to comment
Share on other sites

While Smoke's function sure will do the job, I'd rather use my version, as not only does it allow you to specify a classname, but you can also specify title, text, and as well a flag which you can set to true to only return the first matching window.

Func _ProcessGetWinEx($ivPid, $svClass = "", $svTitle = "", $svText = "", $ivReturnOnlyFirstMatch = False)
   
    $ivPid = ProcessExists($ivPid)
    If Not $ivPid Then Return(SetError(1, 0, 0))
    
    Local $avwArray = WinList()
    Local $avRet[1] = [0]
   
    For $i = 1 To $avwArray[0][0]
        $avClass = DllCall("User32.dll", "int", "GetClassName", "hwnd", $avwArray[$i][1], "str", "", "int", 4096)
        If WinGetProcess($avwArray[$i][1]) = $ivPid Then
            If $svClass = "" Or (IsArray($avClass) And $avClass[2] = $svClass) Then
                If ($svTitle = "" Or StringInStr($avwArray[$i][0], $svTitle)) And ($svText = "" Or StringInStr(WinGetText($avwArray[$i][1]), $svText)) Then
                    $avRet[0] += 1
                    ReDim $avRet[$avRet[0]+1]
                    $avRet[$avRet[0]] = $avwArray[$i][1]
                    If $ivReturnOnlyFirstMatch Then
                        $avRet = $avret[1]
                        ExitLoop
                    EndIf
                EndIf
            EndIf
        EndIf
    Next
   
    Return $avRet
   
EndFunc

:)

Link to comment
Share on other sites

  • Moderators

I noticed I was doing a call tonight to GetClassName API and I was doing it with returning [2] as my value. Although this works... I'm not entirely sure it's the correct method.

This seemed to me to be correct:

Local $t_class = DllStructCreate("char[260]")
DllCall("User32.dll", "int", "GetClassName", "hwnd", $h_wnd, "ptr", DllStructGetPtr($t_class), "int", 260)
Local $s_class = DllStructGetData($t_class, 1)

Common sense plays a role in the basics of understanding AutoIt... If you're lacking in that, do us all a favor, and step away from the computer.

Link to comment
Share on other sites

Thank you for you replies, guys.

But I think you misunderstand my question (if I correctly understand your function's purpose).

I've already got list of running processes, I got their PID's, all I need now is activate one specific process' window using PID

Correct me if I'm wrong, but if you run the same program twice that doesn't change it's title text, you can't search and do actions by title nor by CLASS because CLASS would be the same for both instances? So PID is the only way identify the exact process?

WinList same as ProcessList don't return process' location full path, therefore these two functions are useless without additional "tricks"...

P.S.

_WinGetByPID() worked only for one process instance and only if process has a window (aka not minimized to tray, which I didn't mention earlier :) )...

There are so many functions in AU3 that return PID, but so few where you can use it to...weird.

Link to comment
Share on other sites

  • Moderators

Thank you for you replies, guys.

But I think you misunderstand my question (if I correctly understand your function's purpose).

I've already got list of running processes, I got their PID's, all I need now is activate one specific process' window using PID

Correct me if I'm wrong, but if you run the same program twice that doesn't change it's title text, you can't search and do actions by title nor by CLASS because CLASS would be the same for both instances? So PID is the only way identify the exact process?

WinList same as ProcessList don't return process' location full path, therefore these two functions are useless without additional "tricks"...

P.S.

_WinGetByPID() worked only for one process instance and only if process has a window (aka not minimized to tray, which I didn't mention earlier :) )...

There are so many functions in AU3 that return PID, but so few where you can use it to...weird.

A window isn't "minimized to tray". It's either destroyed or hidden. You kind of need to know what you really need/want and what windows do/don't do in order to ask a proper question I suppose.

You have a foundation.

Winlist()

ProcessList()

A structured way to give you an idea of how to incorporate the idea.

Keep in mind that WinGetState() will also come in hand it seems.

Common sense plays a role in the basics of understanding AutoIt... If you're lacking in that, do us all a favor, and step away from the computer.

Link to comment
Share on other sites

Thank you for you replies, guys.

But I think you misunderstand my question (if I correctly understand your function's purpose).

I've already got list of running processes, I got their PID's, all I need now is activate one specific process' window using PID

Correct me if I'm wrong, but if you run the same program twice that doesn't change it's title text, you can't search and do actions by title nor by CLASS because CLASS would be the same for both instances? So PID is the only way identify the exact process?

Correcting you because you're wrong: Every window has a unique handle. The most common method used to uniquely address a window is by handle.

Also, there is no one-to-one relationship between PID and window, because a PID may have no window at all, or may have many windows.

WinList same as ProcessList don't return process' location full path, therefore these two functions are useless without additional "tricks"...

P.S.

_WinGetByPID() worked only for one process instance and only if process has a window (aka not minimized to tray, which I didn't mention earlier >_< )...

There are so many functions in AU3 that return PID, but so few where you can use it to...weird.

Use WinList() to get an array of all matching windows. Loop through the list with WinGetProcess(), using the handle to ID the window, to find which window(s) match(es) your PID (there may be more than one). As for execution path of the PID, which is completely irrelevant to finding its window(s), look at _ProcessListProperties().

:)

Edited by PsaltyDS
Valuater's AutoIt 1-2-3, Class... Is now in Session!For those who want somebody to write the script for them: RentACoder"Any technology distinguishable from magic is insufficiently advanced." -- Geek's corollary to Clarke's law
Link to comment
Share on other sites

Thank you for you replies, guys.

But I think you misunderstand my question (if I correctly understand your function's purpose).

I've already got list of running processes, I got their PID's, all I need now is activate one specific process' window using PID

Correct me if I'm wrong, but if you run the same program twice that doesn't change it's title text, you can't search and do actions by title nor by CLASS because CLASS would be the same for both instances? So PID is the only way identify the exact process?

WinList same as ProcessList don't return process' location full path, therefore these two functions are useless without additional "tricks"...

P.S.

_WinGetByPID() worked only for one process instance and only if process has a window (aka not minimized to tray, which I didn't mention earlier :) )...

There are so many functions in AU3 that return PID, but so few where you can use it to...weird.

Try doing something like this(using my function):

; If you run the processes yourself(ie. through the script, skip this part)
Dim $aProcesses = ProcessList("explorer.exe"); This will get all the pids of all processes that has a matching processname to explorer.exe

; Now, as an example, lets loop through all the matching processes, and display their windows. ^^
For $i = 1 To $aProcesses[0][0]         
    $aWindows = _ProcessGetWinEx($aProcesses[$i][1]); Retrieve all the windows by each matching processes, note that the _ProcessGetWinEx function returns the handle of the windows.
    ConsoleWrite("Process: " & $aProcesses[$i][0] & " with pid: " & $aProcesses[$i][1] & " has #" & $aWindows[0] & " windows:" & @LF)
    For $i = 1 To UBound($aWindows)-1; Now let's loop through the windows, outputting their window title
        ConsoleWrite(@TAB & '"' & WinGetTitle($aWindows[$i]) & '"' & @LF & @TAB & @TAB & 'hWnd: ' & $aWindows[$i] & @LF)
    Next
Next

Func _ProcessGetWinEx($ivPid, $svClass = "", $svTitle = "", $svText = "", $ivReturnOnlyFirstMatch = False)
    $ivPid = ProcessExists($ivPid)
    If Not $ivPid Then Return(SetError(1, 0, 0))
    Local $avwArray = WinList()
    Local $avRet[1] = [0]
    For $i = 1 To $avwArray[0][0]
        $avClass = DllCall("User32.dll", "int", "GetClassName", "hwnd", $avwArray[$i][1], "str", "", "int", 4096)
        If WinGetProcess($avwArray[$i][1]) = $ivPid Then
            If $svClass = "" Or (IsArray($avClass) And $avClass[2] = $svClass) Then
                If ($svTitle = "" Or StringInStr($avwArray[$i][0], $svTitle)) And ($svText = "" Or StringInStr(WinGetText($avwArray[$i][1]), $svText)) Then
                    $avRet[0] += 1
                    ReDim $avRet[$avRet[0]+1]
                    $avRet[$avRet[0]] = $avwArray[$i][1]
                    If $ivReturnOnlyFirstMatch Then
                        $avRet = $avret[1]
                        ExitLoop
                    EndIf
                EndIf
            EndIf
        EndIf
    Next
    Return $avRet
EndFunc

Does what PsaltyDS explained. >_<

Edited by FreeFry
Link to comment
Share on other sites

@FreeFry:

If WinGetProcess($avwArray[$i][1]) = $ivPid Then

Is exactly what I've been missing.

Thank you all for the help.

P.S.

Just in case someone looking for the same solution here is my final code that works perfectly in my tests:

#NoTrayIcon
$path = 'app.exe'

If $CmdLine[0] > 0 Then;filename submitted via command line
  If (StringInStr($CmdLine[1], "\") OR StringInStr($CmdLine[1], "/")) Then;assuming if submitted string conteins / or \ its a full path otherwise its only a filename
    $path = $CmdLine[1]
  Else
    $path = @WorkingDir & '\' & $CmdLine[1]
  EndIf
Else
    $path = @WorkingDir & '\' & $path
EndIf
If (NOT FileExists($path)) Then
    Exit
EndIf

$name = StringReplace($path, "/", "\");lets work only on one type of slashes
$name = StringRight($name, StringLen($name) - StringInStr($name, "\", 0, -1))

$objWMIService = ObjGet("winmgmts:{authenticationLevel=pktPrivacy, (Debug)}\\.\root\CIMV2")
$colItems = $objWMIService.ExecQuery("SELECT * FROM Win32_Process WHERE Caption = '" & $name & "'", "WQL", 0x10 + 0x20)

For $objItem In $colItems
  If $objItem.ExecutablePath = $path Then
 ; app.exe from this folder already started, we need restore it's window and activate it.
    $PID = $objItem.ProcessId
    Local $list = WinList()
    For $i = 1 To $list[0][0]
      If WinGetProcess($list[$i][1]) = $PID AND $list[$i][0] Then;we need activate only window that matches our PID and has a title
        WinSetState($list[$i][1], "", @SW_SHOW)
        WinActivate($list[$i][1])
        ExitLoop
      EndIf
    Next
    Exit
  EndIf
Next

; app.exe not started, so lets launch it
Run($path)
Link to comment
Share on other sites

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...