There is an application that runs pretty much 24/7 on this computer. It is run inside a command prompt Window. I would like to be able to capture all of the text currently displayed in the window.
The application is already running (and for unrelated reasons, can't be launched from within VB), so I can't just redirect the output of the process to save the text.
My current method for capturing the text is with the following code:
SendKeys.SendWait("^(a)")
SendKeys.SendWait("^(a)")
SendKeys.SendWait("{enter}")
Dim CmdText As String = Clipboard.GetText
Clipboard.Clear()
The above code sends a select all command to the window (it sends it twice, otherwise the entire windows text isn't captured). It then hits the enter key to load it into the clipboard. I then save the clipboard contents to a variable. It works well, but the major problem is that it requires the window to be in focus.
Is there anyway to capture the text from the CMD window if it is currently out of focus?
Edit: I think I'm getting close to finding a workaround using sendmessage/postmessage. Here is the current code:
Private Declare Function PostMessage Lib "user32.dll" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As String) As Long
Private Const WM_CHAR As Long = &H102
Private Const VK_CONTROL = &H11
Private Const VK_RETURN = &HD
Public Function GetWindowHandle(ByVal processName As String) As IntPtr
processName = System.IO.Path.GetFileNameWithoutExtension(processName)
Dim p() = Process.GetProcessesByName(processName)
Return If(p.Length > 0, p(0).MainWindowHandle, IntPtr.Zero)
End Function
Private Sub GetText()
Dim h As Long = GetWindowHandle("programname.exe")
SendMessage(h, WM_CHAR, 1, 0) 'suppose to simulate Ctrl + A
SendMessage(h, WM_CHAR, 1, 0) 'suppose to simulate Ctrl + A
PostMessage(h, WM_KEYDOWN, VK_RETURN, 0) 'sends enter key to load text into clipboard
End Sub
The problem is that instead of sending Ctrl + A to the command window, it just sends the text ^A. Any ideas?
I've written a library called InputHelper which could come in handy here. It includes different methods of performing input simulation, one being sending it to a specific window.
Download from GitHub:
https://github.com/Visual-Vincent/InputHelper/releases
Its wiki is sadly far from complete, but the library itself includes XML documentation describing every method inside it (which is automatically shown by Visual Studio's IntelliSense when you select a method or member in the members list that pops up while typing).
The library currently consists of four main categories:
InputHelper.Hooks: Classes for capturing system-wide (some times referred to as global) mouse and keyboard input. Can be used to make for instance hot keys.
InputHelper.Keyboard: Methods for simulating real keyboard input/key strokes.
InputHelper.Mouse: Methods for simulating real mouse input.
InputHelper.WindowMessages: Methods for simulating mouse and keyboard input at a more virtual level, for instance targeting specific windows. This utilizes window messages (thus SendMessage() and PostMessage()).
The last one mentioned would be the one of your interest. Using InputHelper.WindowMessages.SendKeyPress() you can send a specific key stroke to a window of your choice or, if omitted, the currently active window.
Something like this should work:
Dim hWnd As IntPtr = GetWindowHandle("programname.exe")
'Send CTRL + A twice.
InputHelper.WindowMessages.SendKeyPress(hWnd, Keys.Control Or Keys.A)
InputHelper.WindowMessages.SendKeyPress(hWnd, Keys.Control Or Keys.A)
'Send ENTER.
InputHelper.WindowMessages.SendKeyPress(hWnd, Keys.Enter)
Note that doing Keys.Control Or Keys.A sends the combination CTRL + A, however this works only when using either Keys.Control, Keys.Shift or Keys.Alt (or a combination of them). Using any other keys (for instance Keys.A Or Keys.B or Keys.ControlKey Or Keys.A) won't work.
Related
I have an application which is designed to run full screen constantly. This works fine normally, however, when things run in the background, for example, an antivirus update this can bring that window above my app. Thats fine because I can use things like:
SetForegroundWindow
ShowWindow
SwitchToThisWindow
All of which allow me to bring my application back to the front. However, inside the application is a hidden text box which when the application loads is focussed. When I use one of the pInvoke calls above whilst the application is brought back to front, the focus is still on the existing application.
I am currently struggling with the best way of giving focus back to the control.
I could use Control.FromHandle but seems fairly complicated to get the controls I need and offer focus if a specific tab page is at the front. Is there a better way, any thoughts / ideas welcome.
I was running this on a Windows 10 LTSB unit and as previously mentioned SetForegroundWindow and Show functions were not working along with many other functions I found on pInvoke. I managed to select the correct process and bring it to the forefront if something else takes its place to the top. The issue was that it would never activate no matter what I tried.
In the end, I implemented the following code which checks every 5 seconds, if my app is not the foremost and not maximised then minimise / maximise the window and this reactivates and refocuses the app:
Public Declare Function FindWindow Lib "user32.dll" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
Public Declare Function ShowWindowAsync Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal nCmdShow As Integer) As IntPtr
Private Declare Function GetWindowPlacement Lib "user32.dll" (ByVal hWnd As IntPtr, ByRef lpwndpl As WINDOWPLACEMENT) As Boolean
Private Declare Function GetForegroundWindow Lib "user32.dll" () As IntPtr
<Serializable>
Friend Structure WINDOWPLACEMENT
Public length As Integer
Public flags As Integer
Public showCmd As ShowWindowCommands
Public ptMinPosition As System.Drawing.Point
Public ptMaxPosition As System.Drawing.Point
Public rcNormalPosition As System.Drawing.Rectangle
End Structure
Friend Enum ShowWindowCommands
Hide = 0
Normal = 1
Minimized = 2
Maximized = 3
End Enum
Private Async Function CheckCurrentApp() As Task
Try
' Try and locate the core process
Dim coreHandle = FindWindow(Nothing, "Name of window")
If coreHandle = IntPtr.Zero Then
' Can't find the core. Exit here.
Exit Try
End If
' Get information about the Core window
Dim currentWindowInfo As WINDOWPLACEMENT
GetWindowPlacement(coreHandle, currentWindowInfo)
' If the core is not the foreground window or isn't maximised then send a minimise (6) and maximise (3) request.
' Activate functions in user32 don't work - I spent a day trying to make it so. I could get the foreground window as the core but the input would
' remain in a different application.
If coreHandle <> GetForegroundWindow() OrElse currentWindowInfo.showCmd <> ShowWindowCommands.Maximized Then
ShowWindowAsync(coreHandle, 6)
ShowWindowAsync(coreHandle, 3)
End If
Catch ex As Exception
' DO SOMETHING WITH THE EXCEPTION.
End Try
Await Task.Delay(TimeSpan.FromSeconds(5))
Await CheckCurrentApp()
End Function
quite a long post so tl;dr first:
Is there a proper way to receive WM_HOTKEY messages in VBA or any other proper way to act on individual keyboard shortcuts to call your own subroutines?
I am currently writing a little Add-In for Powerpoint* , that has it's own Ribbon but I would also like to be able to call subroutines by keyboard shortcuts. In an Excel Add-In I am using Application.OnKey, which sadly is not available in Powerpoint. (* personal use at work to enhance productivity; might want to share it with my team but no professional/commercial development)
After quite a bit of research into the Windows API, keyboard hooks and so on I ran into a whole lot of problems with every approach I tried.
1. Keyboard Hook
I first tried a keyboard hook that analyzes keyboard input and does something if a specific combination of buttons is pressed (like CTRL+L or something). Unfortunately, I ran into the same problem that is described here Windows keyboard hook API in VBA causes infinite loop in PowerPoint.
Basically the problem is that using a WH_KEYBOARD hook causes Powerpoint to crash if you input text in a dialog window of Powerpoint (e.g. when you try to name a new design of yours like described in the linked post). Maybe there is a referencing issue (window instance, hook callback etc.)?
This problem does not occur when I try a WH_CBT hook but that causes different problems. For example I could not switch through running applications while the hook was running (i.e. a click on the icons in the taskbar had no effect).
Since these were my first encounters with the Windows API's SetWindowsHookExA and all that comes with it, I ran into an impasse at that point.
2. Register Hotkey
The first approach also showed that it's already quite a task to correctly register the key combinations you want to use as shortcuts. That's what let me into looking into RegisterHotKey which is easy to setup. After quite some research into Windows's message queue I understood that I have to implement a mechanism to listen to the message queue and register WM_HOTKEY messages.
I came up with something that basically works (see code below) but unfortunately slows Powerpoint down quite a bit, which seems to be caused by DoEvents and the never-ending loop I used for PeekMessage.
Option Explicit
Private Const MOD_ALT = &H1
Private Const MOD_CONTROL = &H2
Private Const MOD_SHIFT = &H4
Private Const PM_REMOVE = &H1
Private Const WM_HOTKEY = &H312
Private Const KC = 104
Private Const KC_ALT = 105
Private Const KC_CTRL = 106
Private Const VK_SNAPSHOT = &H2C
' Needed for MSG Structure
Private Type POINTAPI
x As Long
y As Long
End Type
Private Type Msg
hwnd As Long
message As Long
wParam As Long
lParam As Long
time As Long
pt As POINTAPI
End Type
Private Declare Function RegisterHotKey Lib "user32" ( _
ByVal hwnd As Long, _
ByVal id As Long, _
ByVal fsModifiers As Long, _
ByVal vk As Long) As Long
Private Declare Function UnregisterHotKey Lib "user32" (ByVal hwnd As Long, ByVal id As Long) As Long
Private Declare Function PeekMessage Lib "user32" Alias "PeekMessageA" ( _
lpMsg As Msg, _
ByVal hwnd As Long, _
ByVal wMsgFilterMin As Long, _
ByVal wMsgFilterMax As Long, _
ByVal wRemoveMsg As Long) As Long
Private Declare Function WaitMessage Lib "user32" () As Long
Private bCancel As Boolean
Private Sub SetHotKey()
Call RegisterHotKey(0, KC_ALT, MOD_ALT, VK_SNAPSHOT)
End Sub
Private Sub UnSetHotKey()
Call UnregisterHotKey(0, KC_ALT)
End Sub
Private Sub ProcessMessages()
Dim message As Msg
Dim ModHwnd As Long
ModHwnd = GetModuleHandle(vbNullString)
'loop until bCancel is set to True
Do While Not bCancel
'wait for a message
WaitMessage
'check if it's a HOTKEY-message
If PeekMessage(message, 0, WM_HOTKEY, WM_HOTKEY, PM_REMOVE) Then
'here I would check which hotkey was triggered and execute a macro depending on which
Call TestSub
Debug.Print time(), "called"
End If
'let the operating system process other events
DoEvents
Loop
End Sub
Is there a proper way to receive WM_HOTKEY messages in VBA or any other proper way to act on individual keyboard shortcuts to call your own subroutines?
I know there is a program called Shortcut Manager for PowerPoint which also has an SDK to implement it into your own Add-In so that might be what I end up with but would be more flexible to have your own code and not having to rely on 3rd-party software (also harder sell to the IT department).
Any help would be much appreciated. Please let me know if you need more information or code examples (for the keyboard hook approach or anything else).
Best regards
Philipp
I have a bunch of related VB.NET console applications I've written for admin/management functions.
I now want to bring them together under a single Windows Forms application that works as a launcher, but instead of launching the apps as seperate programs, I want to embed the apps in the windows forms app, e.g. have a scrollable list of icons down the left hand side - clicking on one will launch the associated .exe in the right hand pane of the windows forms app.
For now I have a simple single form application with one panel, so I can test out if what I want to do is possible.
Having read around MSDN and SO, I've found other posts about this but none that seem to do what I want.
I don't want to intercept stin/stdout/stderr streams and attempt to emulate the console application, I want to actually run the compiled exe (which I can achieve) but have it run inside the windows form (which I can't achieve, it launches as a seperate process).
The main code for Form1 that I have right now is:
Public Class Form1
Declare Auto Function SetParent Lib "user32.dll" (ByVal hWndChild As IntPtr, ByVal hWndNewParent As IntPtr) As Integer
Declare Auto Function SendMessage Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
Private Const WM_SYSCOMMAND As Integer = 274
Private Const SC_MAXIMIZE As Integer = 61488
Dim proc As Process
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
proc = Process.Start("\\fileserver\datashare\path\WIN_MGR.exe")
proc.WaitForInputIdle()
SetParent(proc.MainWindowHandle, Me.Panel1.Handle)
SendMessage(proc.MainWindowHandle, WM_SYSCOMMAND, SC_MAXIMIZE, 0)
End Sub
End Class
Anyone able to put me in the right direction to actually run the .exe application windowed within the windows form?
Based on the comments, change your Form_Load as follows:
proc = Process.Start("\\fileserver\datashare\path\WIN_MGR.exe")
Thread.Sleep(1000) 'Possibly replace this with polling MainWindowHandle
SetParent(proc.MainWindowHandle, Me.Panel1.Handle)
SendMessage(proc.MainWindowHandle, WM_SYSCOMMAND, SC_MAXIMIZE, 0)
WaitForInputIdle fails because the console application does not have a graphical interface. For a more accurate delay, Idle_Mind suggests polling MainWindowHandle in a while-loop until it returns a value other than IntPtr.Zero.
I have a web app that successfully gets to a page and clicks an "Upload File" button.
My app also successfully handles pop-up windows by monitoring and hooking them. For the most part, it's just to click "OK" or "Cancel" buttons. Buttons are easy.
What I need help with is the Choose-File dialog. I'm hooking it fine, but there are a lot of controls on it and I need some direction.
These are the child controls on it:
DUIViewWndClassName,DirectUIHWND,CtrlNotifySink,NamespaceTreeControl,Static,SysTreeView32,CtrlNotifySink,Shell Preview Extension Host,CtrlNotifySink,SHELLDLL_DefView,DirectUIHWND,CtrlNotifySink,ScrollBar,CtrlNotifySink,ScrollBar,Static,Static,Static,ListBox,Static,Static,ComboBoxEx32,ComboBox,Edit,Static,ComboBox,Button,Button,Button,ScrollBar,WorkerW,ReBarWindow32,TravelBand,ToolbarWindow32,Address Band Root,msctls_progress32,Breadcrumb Parent,ToolbarWindow32,ToolbarWindow32,UniversalSearchBand,Search Box,SearchEditBoxWrapperClass,DirectUIHWND
I would be happy with sticking an exact path/file into the File-Name textbox/combobox and clicking "Open". The button part is easy, but I don't know either how to select files in the window, and/or how to put my path into the File-Name entry field.
Right now I have something like this:
<DllImport("user32.dll")> _
Private Shared Function GetClassName(ByVal hWnd As IntPtr, ByVal lpClassName As StringBuilder, ByVal nMaxCount As Int32) As Int32
End Function
<DllImport("user32.dll")> _
Private Shared Function GetWindowText(ByVal hWnd As IntPtr, ByVal text As StringBuilder, ByVal maxLength As Int32) As Int32
End Function
<DllImport("user32.dll")> _
Private Shared Function GetDlgCtrlID(ByVal hwndCtl As IntPtr) As Integer
End Function
....
Private Shared Function hwndHandler() As Int32
Dim ptrButtonhwnd As IntPtr
For Each pChild As IntPtr In Interop.ChildWindows(pPopup.hwnd)
Dim sbControl As New StringBuilder(255)
GetClassName(pChild, sbControl, sbControl.Capacity)
If "Button".Equals(sbControl.ToString()) Then
Dim sbText As New StringBuilder(255)
GetWindowText(pChildOfDialog, sbText, sbText.Capacity)
If "&Open".Equals(sbText.ToString()) Then
ptrButtonHwnd = pChild
End If
End If
Next
If ptrButtonHwnd <> IntPtr.Zero Then
Dim ctrlId As Int32 = GetDlgCtrlID(ptrButtonHwnd)
SendMessage(pPopup.hwnd, WM_COMMAND, New IntPtr(ctrlId), ptrButtonHwnd)
Return 1
End If
Return 0
End Function
This works fine, but I need to add something to select a file to open either by inputting it into the text/combo field, or by selecting it in the window.
I found the answer was to look for a control with the text of "Edit", which was one of the controls listed in my original list.
So according to my code posted above, I made a new pointer ptrEdit, and assigned it the control where "Edit".Equals(sbControl.ToString()).
Then to manipulate it, I used one of the DLLs:
If ptrEdit <> IntPtr.Zero Then
SetWindowText(pEditHwnd, strFilePath)
If ptrButtonHwnd <> IntPtr.Zero Then
Dim ctrlId As Int32 = GetDlgCtrlID(ptrButtonHwnd)
SendMessage(cwp.hwnd, WM_COMMAND, New IntPtr(ctrlId), ptrButtonHwnd)
Return 1
End If
End If
And so I was able to control the "Choose File To Upload" Dialog Box.
I am developing an application which opens and reads an XML document previously embedded in a PowerPoint presentation, or a Word document. In order to read this object (xmlFile as Object) I have to do:
xmlFile.OLEFormat.DoVerb 1
This opens the package object, and I have another subroutine that gets the open instance of Notepad.exe, and reads its contents in to ADODB stream.
An example of this procedure is available on Google Docs:
XML_Test.pptm.
During this process there is a few seconds window where the Notepad.exe gains focus, and an inadvertent keystroke may cause undesired results or error reading the XML data.
I am looking for one of two things:
Either a method to prevent the user from inadvertently inputting (via keyboard/mouse/etc) while this operation is being performed. Preferably something that does not take control of the user's machine like MouseKeyboardTest subroutine, below. Or,
A better method of extracting the XML data into a string variable.
For #1: this is the function that I found, which I am leery of using. I am wary of taking this sort of control of the users system. ##Are there any other methods that I might use?##
Private Declare Function BlockInput Lib "USER32.dll" (ByVal fBlockIt As Long) As Long
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Sub MouseKeyboardTest() 'both keyboard and mouse blocked
BlockInput True ' Turns off Keyboard and Mouse
' Routine goes here
Sleep 5000 ' Optional coding
BlockInput False ' Turns on Keyboard and Mouse
End Sub
For #2: Some background, but the issue seems to be the inability to extract the embedded object reliably using any method other than DoVerb 1. Since I am dealing with an unsaved document in an application (Notepad) that is immune to my VBA skillz, this seems to be the only way to do this. Full background on that, here:
Extracting an OLEObject (XML Document) from PowerPoint VBA
As you correctly guessed in the comment above that taking the focus away from notepad will solve your problem. The below code does exactly that.
LOGIC:
A. Loop through the shape and get it's name. In your scenario it would be something like Chart Meta XML_fbc9775a-19ea-.txt
B. Use APIs like FindWindow, GetWindowTextLength, GetWindow etc to get the handle of the notepad window using partial caption.
C. Use the ShowWindow API to minimize the window
Code (tested in VBA-Powerpoint)
Paste this code in a module in the above PPTM
Private Declare Function FindWindow Lib "User32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function GetWindowText Lib "User32" Alias "GetWindowTextA" _
(ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare Function GetWindowTextLength Lib "User32" Alias _
"GetWindowTextLengthA" (ByVal hWnd As Long) As Long
Private Declare Function GetWindow Lib "User32" (ByVal hWnd As Long, _
ByVal wCmd As Long) As Long
Private Declare Function ShowWindow Lib "User32" (ByVal hWnd As Long, _
ByVal nCmdShow As Long) As Long
Private Const GW_HWNDNEXT = 2
Private Const SW_SHOWMINIMIZED = 2
Sub Sample()
Dim shp As Shape
Dim winName As String
Dim Ret As Long
For Each shp In ActivePresentation.Slides(1).Shapes
If shp.Type = msoEmbeddedOLEObject Then
winName = shp.Name
shp.OLEFormat.Activate
Exit For
End If
Next
If winName <> "" Then
Wait 1
If GetHwndFromCaption(Ret, Replace(winName, ".txt", "")) = True Then
Call ShowWindow(Ret, SW_SHOWMINIMIZED)
Else
MsgBox "Window not found!", vbOKOnly + vbExclamation
End If
End If
End Sub
Private Function GetHwndFromCaption(ByRef lWnd As Long, ByVal sCaption As String) As Boolean
Dim Ret As Long
Dim sStr As String
GetHwndFromCaption = False
Ret = FindWindow(vbNullString, vbNullString)
Do While Ret <> 0
sStr = String(GetWindowTextLength(Ret) + 1, Chr$(0))
GetWindowText Ret, sStr, Len(sStr)
sStr = Left$(sStr, Len(sStr) - 1)
If InStr(1, sStr, sCaption) > 0 Then
GetHwndFromCaption = True
lWnd = Ret
Exit Do
End If
Ret = GetWindow(Ret, GW_HWNDNEXT)
Loop
End Function
Private Sub Wait(ByVal nSec As Long)
nSec = nSec + Timer
While nSec > Timer
DoEvents
Wend
End Sub
My understanding is that you have control over how XML file gets embedded into PowerPoint presentation in the first place. Here I do not quite understand why you chose to keep the data you need as contents of an embedded object.
To be sure, the task of getting those contents back is not a piece of cake. Actually, as long as there is no (simple or even moderately difficult) way to call QueryInterface and use IPersist* interfaces from VBA, there is just one way to get to contents of embedded object. The way involves following steps:
Activate an embedded object. You used OLEFormat.DoVerb 1 for that. A better way would be to call OLEFormat.Activate, but this is irrelevant for your particular problem.
Use embedded object's programming model to perform useful operations like getting contents, saving or whatever is exposed. Notepad.exe exposes no such programming model, and you resorted to WinAPI which is the best choice available.
Unfortunately, your current approach has at least 2 flaws:
The one you identified in the question (activation of notepad.exe leading to possibility of user's interference).
If a user has default program for opening .txt files other than notepad.exe, your approach is doomed.
If you do have control over how embedded object is created then better approach would be to store your XML data in some property of Shape object. I would use Shape.AlternativeText (very straightforward to use; shouldn't be used if you export your .pptm to HTML or have some different scenario where AlternativeText matters) or Shape.Tags (this one is probably the most semantically correct for the task) for that.
I don't think that blocking the user is the right approach,
If you must use a content of a notepad window, I would suggest using the SendKeys method, in order to send this combination:
SendKeys("^A^C")
Which is the equivalent of "Select All" and "Copy",
And then you could continue working "offline" on the clipboard, without fear of interference by keystrokes.
My approach, per Sid's suggestion, was to find a way to minimize the Notepad.exe. Since I already found way to get that object and close it, I figured this should not be as hard.
I add these:
Public Declare Function _
ShowWindow& Lib "user32" (ByVal hwnd As Long, _
ByVal ncmdshow As Long)
Public Const SW_MINIMIZE = 6
And then, in the FindNotepad function, right before Exit Function (so, after the Notepad has been found) I minimize the window with:
ShowWindow TopWnd, SW_MINIMIZE