Closing OneNote Application from excel VBA - vba

I'm trying to Close the OneNote Application from VBA excel with this piece of code:
Sub closeOneNote()
Dim oneNoteApp As Object
On Error Resume Next
Set oneNoteApp = GetObject(, "OneNote.Application")
If Err.Number = 0 Then
oneNoteApp.Quit
Else
Err.Clear
End If
End Sub
When I try with Outlook instead of OneNote, it works fine and Outlook closes. I was wondering if it was because OneNote is not an application that supports automation through VBA. As shown in the below link, the table at the bottom of the page lists all the top-level Office objects Ican reference and their class names and OneNote is not among them:
Creation of Object Variables to Automate Another Office Application
Any ideas, suggestions on how to close the application (not the notebooks themselves, only the application running.. )
Thanks

Here is a good resource for handling NoteNote:
http://msdn.microsoft.com/en-us/library/hh377180(v=office.14)
and in general:
http://msdn.microsoft.com/en-us/library/hh377180(v=office.14)

OneNote Application interface (documented here) does not have a Quit method. That is why it won't work. What you can do instead is to close the OneNote window, which is somewhat tricky. Here are some instructions and sample code:
Get the OneNote window handle: The application object has a CurrentWindow member which then has a WindowHandle member that is a HWND to the current OneNote window.
Get the top level window: This handle is typically a child window of the OneNote window so you need to call GetAncestor with GA_ROOT to get the top level window
Close the window: You can send WM_CLOSE to the top level window to close it. Of course if it is displaying a dialog box or busy in another way, it may not respond to this.
Option Explicit
Private Declare PtrSafe Function PostMessage Lib "user32" Alias "PostMessageA" _
(ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare PtrSafe Function GetAncestor Lib "user32.dll" _
(ByVal hwnd As Long, ByVal gaFlags As Long) As Long
Private Const GA_ROOT As Long = 2
Private Const WM_CLOSE = &H10
Sub test()
' Create Application Object
Dim app As Object
Set app = CreateObject("OneNote.Application")
' Get the window handle
Dim hwnd As Long
hwnd = app.Windows.CurrentWindow.WindowHandle
' Get the top level window
Dim hwndRoot As Long
hwndRoot = GetAncestor(hwnd, GA_ROOT)
' Close it
PostMessage hwndRoot, WM_CLOSE, 0&, 0&
End Sub
This won't be enough if there are multiple OneNote windows around. For that case you can enumerate the Windows collection and do this for each Window object in it.

Related

IN VBA is there a good way to receive WM_HOTKEY messages or any other way to simulate Excel's Application.OnKey

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

Detect Other Instance of Open Workbooks

I'm trying to have the user select an instance or open Workbook of Excel. The idea is to have a window that will display all open Instances of Excel and then display the Workbooks within these instances. I've done some self research and what I've found below...
Public Declare Function GetDesktopWindow Lib "user32" () As Long
Public Declare Function FindWindowEx Lib "user32" Alias _
"FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Function ExcelInstances() As Long
Dim hWndDesk As Long
Dim hWndXL As Long
'Get a handle to the desktop
hWndDesk = GetDesktopWindow
Do
'Get the next Excel window
hWndXL = FindWindowEx(GetDesktopWindow, hWndXL, _
"XLMAIN", vbNullString)
'If we got one, increment the count
If hWndXL > 0 Then
ExcelInstances = ExcelInstances + 1
End If
'Loop until we've found them all
Loop Until hWndXL = 0
End Function
Problem:
When I ran the code, I am getting the error message:
Compile Error:
Only comments may appear after End Sub, End Function or End Property
It's highlighting the first line in the code, and I believe it has something to do with the "user32" string?
Question:
This code will only give me a COUNT of how many instances of Excel are currently open. Is there any way to return the names of the instances and then another sub routine that would return the Workbooks within the instances as well? I've seen a solution making use of VB.Net; however I'd like to avoid this so that I can try to keep everything consolidated into a single Excel Spreadsheet (if possible).

How do I bypass outlook alerts when trying to get user infromation?

I am trying to get username, user email, user department, and user location from outlook to populate an excel user form.
That code works without issue. The problem I am having is this: I get the popup from Excel saying "A program is trying to access e-mail address information stored in Outlook. If this is unexpected, click Deny and verify your antivirus software is up to date."
I found some code that is supposed to suppress this popup, but it doesn't appear to be working. Below is the procedure that is calling using the procedure. I have put the call to the "Turn_Auto_Yes_On" procedure multiple times to see if it works with any of the calls. Turn_Auto_yes_On is successfully executed, but seems to have no effect as I still get the outlook message after execution.
I am stuck here and have gone to multiple sites for answers, and I have found none. Any help would be much appreciated.
Function fill_Outlook_Info() As Boolean
Application.DisplayAlerts = False
Call Turn_Auto_Yes_On
Set OL = CreateObject("outlook.application")
Call Turn_Auto_Yes_On
Set olAllUsers = OL.Session.AddressLists.Item("All Users").AddressEntries
Call Turn_Auto_Yes_On
s_OutlookUser = OL.Session.CurrentUser.Name
Call Turn_Auto_Yes_On
Set oentry = olAllUsers.Item(s_OutlookUser)
Call Turn_Auto_Yes_On
Set oExchUser = oentry.GetExchangeUser()
v_department = oExchUser.DEPARTMENT
v_Email = oExchUser.PrimarySmtpAddress
s_OutlookCity = oExchUser.city
End Function
and this is the code that is supposed to bypass outlook's alerts
Option Explicit
'these declarations are to access the RegisterWindowsMessage which is used to send "yes" to outlook
Public OL, olAllUsers, oExchUser, oentry As Object
Public v_Email As Variant 'v_department
'Public s_OutlookUser As String
Public Declare Function RegisterWindowMessage Lib "User32" Alias "RegisterWindowMessageA" (ByVal lpString As String) As Long
Public Declare Function FindWindow Lib "User32" _
Alias "FindWindowA" (ByVal lpClassName As Any, _
ByVal lpWindowName As Any) As Long
Public Declare Function SendMessage Lib "User32" Alias "SendMessageA" (ByVal hwnd As Long, _
ByVal wMsg As Long, ByVal wParam As Long, _
lParam As Any) As Long
Public Sub Turn_Auto_Yes_On()
Dim wnd As Long
Dim uClickYes As Long
Dim Res As Long
uClickYes = RegisterWindowMessage("CLICKYES_SUSPEND_RESUME")
wnd = FindWindow("EXCLICKYES_WND", 0&)
Res = SendMessage(wnd, uClickYes, 1, 0)
End Sub
Public Sub Turn_Off_Auto_Yes()
Dim wnd As Long
Dim uClickYes As Long
Dim Res As Long
uClickYes = RegisterWindowMessage("CLICKYES_SUSPEND_RESUME")
wnd = FindWindow("EXCLICKYES_WND", 0&)
Res = SendMessage(wnd, uClickYes, 0, 0)
End Sub
While not specific to this exact use case, I have gotten around similar outlook "manual confirmation" and "Outlook security" issues by using Outlook Redemption.
Outlook Redemption works around limitations imposed by the Outlook Security Patch plus provides a number of objects and functions to work with properties and functionality not exposed through the Outlook object model.
With Outlook Redemption you can [...] Make your code runs unaffected by the Security Patch.
My particular use case was automatically sending outlook e-mails from python. I had the same issue with the user pop-up and this did the trick.

VBA to Prevent Keyboard Input While a Package Object (XML) is Read into ADODB Stream?

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

How do I save a handle of ActiveDocument to a variable?

How do I get the Windows Handle of an ActiveDocument in Microsoft Word?
I wish to save the handle value to a variable.
You can use the FindWindow function to get the handle of the window in a currently active word application:
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
The class name for MS Word is "OpusApp". If you have only the one Word app open then the following would return the handle to that window:
Public Function Test1()
Dim lhWnd As Long
lhWnd = FindWindow("OpusApp", vbNullString)
End Function
Note when you have multiple active Word applications, you can add the name of the window caption. For example if its saved as "Stanigator.doc", then the caption would read as "Stanigator - Microsoft Word". So:
Public Function Test2()
Dim lhWnd As Long
lhWnd = FindWindow(vbNullString, "Stanigator - Microsoft Word")
End Function
The other API that may come in handy is:
Private Declare Function GetActiveWindow Lib "user32" () As Long
Edit:
I was going to recommend the VSTO approach but based on this link that doesn't appear to be wise:
Connect Issue: VSTO API support for MS Word Main Window Handle
As Tim Williams pointed out, it's Set myDoc = ActiveDocument.