Quantcast
Channel: Delphi – The Wiert Corner – irregular stream of stuff
Viewing all articles
Browse latest Browse all 1440

Writing a tool that restarts the Google Chat desktop app Window (and hopefully the Google Duo desktop app Window too)

$
0
0

In the past, the Google Hangouts desktop app on Windows would integrate with the system “tray” (actually the notification area) and show you missed chats and calls.

The [Wayback/Archive] Google Chat desktop app does not. It shows missed messages only as a number on the taskbar icon. Even worse: when you close the Window, the taskbar application icon does not show that number any more.

The odd thing is that the Google Duo desktop app does stay active and shows a notification popup on incoming calls. The Google Chat desktop app does not.

So I wanted to restart the Google Chat desktop app automatically when the Window was closed. But there is a catch:

The Google Chat desktop app is not a process by itself: it is ran by the main chrome.exe process at the root of the Chrome process tree (which also hosts all other Chrome processes that render the various browser windows and tabs). Which means you cannot detect the Google Chat desktop app window being closed by the main chrome.exe process disappearing.

This renders traditional “restart after failure” programs useless.

Not wanting to re-invent wheels, I asked around if anyone had a ready to run script on Twitter, got one response, which meant I had to at least partially re-invent some portions of the wheels.

Let’s first get to the tweets:

  1. [Wayback/Archive] Jeroen Wiert Pluimers on Twitter: “Who of the fellow tweeps has a script or tool that automatically restarts an application that has stopped? It is about Google Chat: "C:Program FilesGoogleChromeApplicationchrome_proxy.exe" --profile-directory=Default --app-id=mdpkiolbdkhdjpekfbkbmhigcaggjagi
  2. [Wayback/Archive] Niel 🇺🇦 on Twitter: “@jpluimers for example this w-shadow.com/blog/2009/03/04/restart-on-crash
  3. [Wayback/Archive] Jeroen Wiert Pluimers on Twitter: “@nieldk Thanks. Fails because it monitors the process which is chrome.exe. So I need to find a solution that can monitor a certain Window. I might need to build a custom thing like I did here for @ScanSnap_US But thanks for the link, as it likely will be useful another day.”
  4. [Wayback/Archive] Jeroen Wiert Pluimers on Twitter: “@nieldk @ScanSnap_US That was this little Delphi tool. … Hopefully I can get something like this running from PowerShell.”
  5. [Wayback/Archive] Jeroen Wiert Pluimers on Twitter: “@nieldk How cool, I just found out how you can add a bit of C# as a Type in PowerShell: …”

    Later I found out it was not their code, but from (a now defunct and non-archived site powershell-knowhow.de) by [Wayback/Archive] Peter Monadjemi (@pemo09) who published [Wayback/Archive] PowerShell für die Windows-Administration: … – Peter Monadjemi – Google Books. but I could not find any current version of it via [Wayback/Archive] In Windows, how can I view a list of all window titles ? – Super User (thanks [Wayback/Archive] barlop and [Wayback/Archive] Oliver Salzburg)

    Not even the homepage was successfully archived in the Wayback Machine, just a different domain on which the site apparently moved to in 2015: [Wayback/Archive] Posh-Admin | Per PowerShelll Abläufe in der IT automatisieren – das PowerShell-Blog von Peter Monadjemi.

The cool thing about their code is that it shows how to integrate a C# class into a PowerShell script. I didn’t know that is possible, but it basically boils down to Add-Type -TypeDefinition 'your code here' -Language CSharp.

And it finds Windows too; sometimes too many, so I have to refine the search regular? expression:

PS C:\Temp> [Api.Apidef]::GetWindows() | Where-Object { $_.WinTitle -like "*Chat*" } | Sort-Object -Property WinTitle | Select-Object WinTitle,@{Name="Handle"; Expression={"{0:X0}" -f $_.WinHwnd}}

WinTitle
--------
Edit Post “Writing a tool that restarts the Google Chat desktop app Window (and hopefully the Google Duo desktop app...
Google Chat - Chat


PS C:\Temp> [Api.Apidef]::GetWindows() | Where-Object { $_.WinTitle -like "*Chat*" } | Sort-Object -Property WinTitle | Select-Object WinTitle,@{Name="Handle"; Expression={"{0:X0}" -f $_.WinHwnd}}

WinTitle
--------
Edit Post “Writing a tool that restarts the Google Chat desktop app Window (and hopefully the Google Duo desktop app...
Google Chat - My Brother - Chat

Their code is so similar to my Delphi code (just compare the gists with code below the signature), so that in a future blog post (or series) I will rewrite my code to C#, integrate it in PowerShell and make the query argument more flexible.

Then I need to find out how to run the PowerShell script hidden and start it every minute or so. The latter should be easy through the Windows Task Scheduler (which in the past was called “Scheduled Tasks”) because the at command got deprecated:

The AT command has been deprecated. Please use schtasks.exe instead.

Before the tweets: searching

Before starting the first Tweet I already had searched for these app-id values:

The first ID of course is from the Google Chat shortcut on my system mentioned in the first tweet above.

The second ID is which is from the Google Duo shortcut on my system pointing to "C:Program FilesGoogleChromeApplicationchrome_proxy.exe" --profile-directory=Default --app-id=imgohncinckhbblnlmaedahepnnpmdma.

Both queries didn’t return many useful results, so I did more digging:

Back to the code

Integrating C# into PowerShell. Given the Add-Type -TypeDefinition 'your code here' -Language CSharp above, I did some searches to see if there were some immediate caveats:

A few notes here:

  1. I have yet to see a code editor that syntax-highlights both the PowerShell and the C# code.
  2. You cannot add the same type name twice in the same App Domain (Application Domain or for that matter: PowerShell session as there is one App Domain per PowerShell session), so be aware of any errors or try to prevent them (see the StackOverflow and Random IT examples above).
    • Note I have seen in PowerShell 5.1 on Windows 10 that this only holds when the type names are identical but the types themselves are not. In this example if you run the code multiple types it shill executes fine (as the type name is still the same, but also the type itself is identical). So probably some optimisation took place:
      $code = @"
      using System;
      namespace HelloWorld
      {
       public class Program
        {
        public static void Main(){
          Console.WriteLine("Hello world!");
          }
        }
      }
      "@
      
      Add-Type -TypeDefinition $code -Language CSharp
      Invoke-Expression "[HelloWorld.Program]::Main()"
  3. There is both a speed penalty on using PowerShell and one for including C# in that so be aware if you need performant code.
  4. I’m not sure if the Add-Type really needs to be at the beginning of the script; my impression is that you need to include it before using it (just like any other PowerShell code).

---jeroen


The PowerShell/C# code for finding Word windows



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


<#
.Synopsis
Enumerieren der vorhandenen Fenster
#>
$TypeDef = @"
using System;
using System.Text;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Api
{
public class WinStruct
{
public string WinTitle {get; set; }
public int WinHwnd { get; set; }
}
public class ApiDef
{
private delegate bool CallBackPtr(int hwnd, int lParam);
private static CallBackPtr callBackPtr = Callback;
private static List<WinStruct> _WinStructList = new List<WinStruct>();
[DllImport("User32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumWindows(CallBackPtr lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
private static bool Callback(int hWnd, int lparam)
{
StringBuilder sb = new StringBuilder(256);
int res = GetWindowText((IntPtr)hWnd, sb, 256);
_WinStructList.Add(new WinStruct { WinHwnd = hWnd, WinTitle = sb.ToString() });
return true;
}
public static List<WinStruct> GetWindows()
{
_WinStructList = new List<WinStruct>();
EnumWindows(callBackPtr, IntPtr.Zero);
return _WinStructList;
}
}
}
"@
Add-Type -TypeDefinition $TypeDef -Language CSharpVersion3
[Api.Apidef]::GetWindows() | Where-Object { $_.WinTitle -like "*Word" } | Sort-Object -Property WinTitle | Select-Object WinTitle,@{Name="Handle"; Expression={"{0:X0}" -f $_.WinHwnd}}

My Delphi code for ScanAnap



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


program AbbyyFineReader_CreativeCloud_AutoCloseConsoleProject;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Winapi.Messages,
Winapi.Windows,
System.SysUtils;
const
CreativeCloud = 'Creative Cloud';
AbbyyFineReaderForScanSnap50 = 'ABBYY FineReader for ScanSnap 5.0';
type
TWindowDump = record
public
HWnd: HWnd;
IsAppWindow: Boolean;
IsOwned: Boolean;
IsVisible: Boolean;
WindowText: string;
WindowTextLength: Integer;
ParentHWnd: lParam;
Prefix: string;
constructor Create(const APrefix: string; const AHWnd: HWnd; const AParentHWnd: lParam);
function ToString: string;
end;
constructor TWindowDump.Create(const APrefix: string; const AHWnd: HWnd; const AParentHWnd: lParam);
begin
Prefix := APrefix;
HWnd := AHWnd;
ParentHWnd := AParentHWnd;
IsAppWindow := GetWindowLongPtr(HWnd, GWL_STYLE) and WS_EX_APPWINDOW <> 0;
IsOwned := GetWindow(HWnd, GW_OWNER) <> 0;
IsVisible := IsWindowVisible(HWnd);
WindowTextLength := GetWindowTextLength(HWnd);
if WindowTextLength <> 0 then
begin // without Length check you can get an access violation
SetLength(WindowText, WindowTextLength);
GetWindowText(HWnd, PChar(WindowText), Length(WindowText) + 1);
end;
end;
function TWindowDump.ToString: string;
begin
Result := Format('%sParentHWnd=$%8.8x;HWnd=$%8.8x;IsVisible=%s;IsOwned=%s;IsAppWindow=%s;WindowTextLength=%d;WindowText="%s"', [ //
Prefix, ParentHWnd, HWnd, BoolToStr(IsVisible), BoolToStr(IsOwned), BoolToStr(IsAppWindow), WindowTextLength, WindowText]);
end;
function DumpWindow(const Prefix: string; const HWnd: HWnd; const ParentHWnd: lParam): TWindowDump;
begin
Result := TWindowDump.Create(Prefix, HWnd, ParentHWnd);
Writeln(Result.ToString());
end;
procedure DumpWindowStack(const Prefix: string; const HWnd: HWnd; const ParentHWnd: lParam);
var
OwnerHWnd: lParam;
begin
if ParentHWnd <> 0 then
begin
OwnerHWnd := GetWindow(ParentHWnd, GW_OWNER);
DumpWindow(Prefix, ParentHWnd, OwnerHWnd);
DumpWindowStack(' ' + Prefix, ParentHWnd, OwnerHWnd);
end;
end;
function EnumChildWindowsProc(HWnd: HWnd; ParentHWnd: lParam): Bool; stdcall;
var
IsVisible: Boolean;
ParentWindowText: string;
ParentWindowTextLength: Integer;
WindowDump: TWindowDump;
begin
Result := True; // carry on enumerating
IsVisible := IsWindowVisible(HWnd);
if IsVisible then
begin
WindowDump := DumpWindow(' ', HWnd, ParentHWnd);
ParentWindowTextLength := GetWindowTextLength(ParentHWnd);
if ParentWindowTextLength <> 0 then
begin
SetLength(ParentWindowText, ParentWindowTextLength);
GetWindowText(ParentHWnd, PChar(ParentWindowText), Length(ParentWindowText) + 1);
end;
if ParentWindowText = AbbyyFineReaderForScanSnap50 then
begin
if WindowDump.WindowText = '&Close' then
begin
Writeln(' > Child is Close button: clicking.');
DumpWindowStack(' < ', HWnd, ParentHWnd);
SendMessage(HWnd, BM_CLICK, 0, 0);
end;
end;
if ParentWindowText = CreativeCloud then
begin
if WindowDump.WindowText = 'Sign in – Adobe ID' then
begin
Writeln(' > Child is Signin button: closing parent.');
DumpWindowStack(' < ', HWnd, ParentHWnd);
SendMessage(ParentHWnd, WM_CLOSE, 0, 0);
end;
end;
end;
end;
function EnumWindowsProc(HWnd: HWnd; lParam: lParam): Bool; stdcall;
var
WindowDump: TWindowDump;
begin
Result := True; // carry on enumerating
WindowDump := TWindowDump.Create('', HWnd, lParam);
if WindowDump.IsVisible then
begin
Writeln(WindowDump.ToString());
(*
ParentHWnd=$00000000;HWnd=$00030602;IsVisible=-1;IsOwned=0;IsAppWindow=-1;WindowTextLength=33;WindowText="ABBYY FineReader for ScanSnap 5.0"
> Recursive child windows for ABBYY
ParentHWnd=$00030602;HWnd=$000205E2;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=0;WindowText=""
ParentHWnd=$00030602;HWnd=$000205E0;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=0;WindowText=""
ParentHWnd=$00030602;HWnd=$000205EC;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=0;WindowText=""
ParentHWnd=$00030602;HWnd=$000205EA;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=74;WindowText="Register your copy of ABBYY FineReader and receive the following benefits:"
ParentHWnd=$00030602;HWnd=$000205E8;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=25;WindowText="- Free technical support;"
ParentHWnd=$00030602;HWnd=$000205E6;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=51;WindowText="- Information about new versions of ABBYY products."
ParentHWnd=$00030602;HWnd=$000205E4;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=12;WindowText="Registration"
ParentHWnd=$00030602;HWnd=$000205FC;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=0;WindowText=""
ParentHWnd=$00030602;HWnd=$000205FA;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=6;WindowText="&Close"
> Child is Close button: clicking.
< ParentHWnd=$00000000;HWnd=$00030602;IsVisible=-1;IsOwned=0;IsAppWindow=-1;WindowTextLength=33;WindowText="ABBYY FineReader for ScanSnap 5.0"
ParentHWnd=$00030602;HWnd=$000205F6;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=34;WindowText="Processing finished (warnings: 1)."
ParentHWnd=$00030602;HWnd=$000205F4;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=31;WindowText="Converting to searchable PDF…"
ParentHWnd=$00030602;HWnd=$000205F0;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=0;WindowText=""
ParentHWnd=$00030602;HWnd=$000205EE;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=0;WindowText=""
ParentHWnd=$00030602;HWnd=$000205D2;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=63;WindowText="Page 1. Make sure the correct recognition language is selected."
ParentHWnd=$00000000;HWnd=$00010248;IsVisible=-1;IsOwned=-1;IsAppWindow=0;WindowTextLength=14;WindowText="Creative Cloud"
> Recursive child windows for Creative Cloud
ParentHWnd=$00010248;HWnd=$0001024A;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=28;WindowText="Main Container Client Dialog"
ParentHWnd=$00010248;HWnd=$0002034A;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=3;WindowText="IMS"
ParentHWnd=$00010248;HWnd=$0001035A;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=0;WindowText=""
ParentHWnd=$00010248;HWnd=$00020350;IsVisible=-1;IsOwned=0;IsAppWindow=0;WindowTextLength=18;WindowText="Sign in – Adobe ID"
> Child is Signin button: closing parent.
< ParentHWnd=$0003011A;HWnd=$00010248;IsVisible=-1;IsOwned=-1;IsAppWindow=0;WindowTextLength=14;WindowText="Creative Cloud"
< ParentHWnd=$00000000;HWnd=$0003011A;IsVisible=0;IsOwned=0;IsAppWindow=0;WindowTextLength=4;WindowText="Core"
*)
if WindowDump.IsVisible and WindowDump.IsAppWindow then
begin
if WindowDump.WindowText = AbbyyFineReaderForScanSnap50 then
begin
Writeln('> Recursive child windows for ABBYY');
EnumChildWindows(HWnd, @EnumChildWindowsProc, HWnd);
end;
end;
if WindowDump.IsVisible and WindowDump.IsOwned then
if WindowDump.WindowText = CreativeCloud then
begin
Writeln('> Recursive child windows for Creative Cloud');
EnumChildWindows(HWnd, @EnumChildWindowsProc, HWnd);
end;
end;
end;
begin
try
repeat
EnumWindows(@EnumWindowsProc, 0);
Writeln('.');
Sleep(10 * 1000);
until False;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.


Viewing all articles
Browse latest Browse all 1440

Trending Articles