Passing value to excel inputbox from VB.NET - vb.net

I am trying to automate data population on some excel sheets that have some macros. Now the excel is protected and I cannot get the secret key. Now I am able to run the macros but when I try to pass arguments I get arguments mismatch.
If I just run the macro with the name, I get an inputbox which takes an extra argument as input and auto generates some of the values for the columns. I have to manually enter this value into the inputbox as of now. Is there any way that I could automate that process, i.e capture the inputbox thrown by the macro in the vb.net script and enter the values from there? i.e., I would like to run the macro and after I get the popup asking me to enter some value, use the vb.net code to enter the value to that popup.
Here is what I have till now
Public Class Form1
Dim excelApp As New Excel.Application
Dim excelWorkbook As Excel.Workbook
Dim excelWorkSheet As Excel.Worksheet
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
excelWorkbook = excelApp.Workbooks.Open("D:/excelSheets/plan_management_data_templates_network.xls")
excelApp.Visible = True
excelWorkSheet = excelWorkbook.Sheets("Networks")
With excelWorkSheet
.Range("B7").Value = "AR"
End With
excelApp.Run("createNetworks")
// now here I would like to enter the value into the createNetworks Popup box
excelApp.Quit()
releaseObject(excelApp)
releaseObject(excelWorkbook)
End Sub
Macro definition
createNetworks()
//does so basic comparisons on existing populated fields
//if true prompts an inputbox and waits for user input.
This stall my vb.net script too from moving to the next line.

Like you and me, we both have names, similarly windows have handles(hWnd), Class etc. Once you know what that hWnd is, it is easier to interact with that window.
This is the screenshot of the InputBox
Logic:
Find the Handle of the InputBox using FindWindow and the caption of the Input Box which is Create Network IDs
Once that is found, find the handle of the Edit Box in that window using FindWindowEx
Once the handle of the Edit Box is found, simply use SendMessage to write to it.
In the below example we would be writing It is possible to Interact with InputBox from VB.Net to the Excel Inputbox.
Code:
Create a Form and add a button to it.
Paste this code
Imports System.Runtime.InteropServices
Imports System.Text
Public Class Form1
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Integer
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As Integer, ByVal hWnd2 As Integer, ByVal lpsz1 As String, _
ByVal lpsz2 As String) As Integer
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, _
ByVal lParam As String) As Integer
Const WM_SETTEXT = &HC
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Ret As Integer, ChildRet As Integer
'~~> String we want to write to Input Box
Dim sMsg As String = "It is possible to Interact with InputBox from VB.Net"
'~~> Get the handle of the "Input Box" Window
Ret = FindWindow(vbNullString, "Create Network IDs")
If Ret <> 0 Then
'MessageBox.Show("Input Box Window Found")
'~~> Get the handle of the Text Area "Window"
ChildRet = FindWindowEx(Ret, 0, "EDTBX", vbNullString)
'~~> Check if we found it or not
If ChildRet <> 0 Then
'MessageBox.Show("Text Area Window Found")
SendMess(sMsg, ChildRet)
End If
End If
End Sub
Sub SendMess(ByVal Message As String, ByVal hwnd As Long)
Call SendMessage(hwnd, WM_SETTEXT, False, Message)
End Sub
End Class
ScreenShot
When you run the code this is what you get
EDIT (Based on further request of automating the OK/Cancel in Chat)
AUTOMATING THE OK/CANCEL BUTTONS OF INPUTBOX
Ok here is an interesting fact.
You can call the InputBox function two ways in Excel
Sub Sample1()
Dim Ret
Ret = Application.InputBox("Called Via Application.InputBox", "Sample Title")
End Sub
and
Sub Sample2()
Dim Ret
Ret = InputBox("Called Via InputBox", "Sample Title")
End Sub
In your case the first way is used and unfortunately, The OK and CANCEL buttons do not have a handle so unfortunately, you will have to use SendKeys (Ouch!!!) to interact with it. Had you Inbutbox been generated via the second method then we could have automated the OK and CANCEL buttons easily :)
Additional Info:
Tested on Visual Studio 2010 Ultimate (64 bit) / Excel 2010 (32 bit)
Inspired by your question, I actually wrote a blog Article on how to interact with the OK button on InputBox.

Currently, I employ a method where I run a thread before the macro is called by the script. The thread checks if the inputbox has been called. If it is, it picks up the value from the location and using sendkeys, submits the box.
This is a rudimentary solution but I was hoping for a more elegant solution to this problem.
My solution Code:
Public Class Form1
Dim excelApp As New Excel.Application
Dim excelWorkbook As Excel.Workbook
Dim excelWorkSheet As Excel.Worksheet
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
excelWorkbook = excelApp.Workbooks.Open("D:/excelSheets/some_excel.xls")
excelApp.Visible = True
excelWorkSheet = excelWorkbook.Sheets("SheetName")
With excelWorkSheet
.Range("B7").Value = "Value"
End With
Dim trd = New Thread(Sub() Me.SendInputs("ValueForInputBox"))
trd.IsBackground = True
trd.Start()
excelApp.Run("macroName")
trd.Join()
releaseObject(trd)
excelApp.Quit()
releaseObject(excelApp)
releaseObject(excelWorkbook)
End Sub
Private Sub releaseObject(ByVal obj As Object)
Try
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)
obj = Nothing
Catch ex As Exception
obj = Nothing
Finally
GC.Collect()
End Try
End Sub
Private Sub SendInputs(ByVal noOfIds As String)
Thread.Sleep(100)
SendKeys.SendWait(noOfIds)
SendKeys.SendWait("{ENTER}")
SendKeys.SendWait("{ENTER}")
End Sub

Related

VB .NET Outlook 2016 Add-in Subject Line

I am writing an add-in for Outlook 2016 using Visual Studio 2015. I added a button to the built-in New Mail tab. When clicked it adds the word "unencrypt" to the end of the subject line and then sends the email.
This works fine as long as the user has tabbed out of the subject line field after entering the subject. But if you type in the subject and then immediately click the button it wipes out the subject line and replaces it with "unencrypt".
However, when I step through in debug it works fine - it keeps the existing text even if I haven't tabbed out of the subject line. I figured there was some sort of delay in updating the Subject property of the mail item, but I manually put in a delay of 20 seconds and it still wiped out the subject line if I wasn't stepping through in debug.
I'm at a loss here. Is there a way to check the subject line textbox itself? or some other way to grab the text even if the user hasn't tabbed out?
Any help would be appreciated!
Private Sub Button1_Click(sender As Object, e As RibbonControlEventArgs) Handles Button1.Click
' Get the Application object
Dim application As Outlook.Application = Globals.ThisAddIn.Application
' Get the active Inspector object and check if is type of MailItem
Dim inspector As Outlook.Inspector = application.ActiveInspector()
Dim mailItem As Outlook.MailItem = TryCast(inspector.CurrentItem, Outlook.MailItem)
If mailItem IsNot Nothing Then
If mailItem.EntryID Is Nothing Then
If Not IsNothing(mailItem.Subject) AndAlso ((mailItem.Subject.Contains(" unencrypt")) OrElse (mailItem.Subject.Contains("unencrypt "))) Then
mailItem.Subject = mailItem.Subject
'ElseIf IsNothing(mailItem.Subject) Then
'System.Threading.Thread.Sleep(20000)
'mailItem.Subject = mailItem.Subject + " unencrypt"
Else
mailItem.Subject = mailItem.Subject + " unencrypt"
End If
If Not IsNothing(mailItem.To) AndAlso mailItem.To.ToString().Trim <> "" Then
mailItem.Send()
Else
MessageBox.Show("We need to know who to send this to. Make sure you enter at least one name.", "Microsoft Outlook", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1)
End If
End If
End If
End Sub
Edit:
Dmitry's answer got me where I needed, but for anyone else not familiar with the Windows API I added the code below and then simply called the GetSubject function from my original code instead of using the mailItem.Subject property.
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function FindWindowEx(ByVal parentHandle As IntPtr, _
ByVal childAfter As IntPtr, _
ByVal lclassName As String, _
ByVal windowTitle As String) As IntPtr
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function FindWindow(ByVal lclassName As String, _
ByVal lWindowName As String) As IntPtr
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function GetWindowText(ByVal hWnd As IntPtr, _
ByVal lpString As StringBuilder, _
ByVal nMaxCount As Integer) As Integer
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function GetWindowTextLength(ByVal hwnd As IntPtr) As Integer
End Function
Private Function GetSubject(inspector As Outlook.Inspector) As String
Try
Dim inspectorHandle As IntPtr = FindWindow("rctrl_renwnd32", inspector.Caption)
Dim windowLevel2Handle As IntPtr = FindWindowEx(inspectorHandle, IntPtr.Zero, "AfxWndW", "")
Dim windowLevel3Handle As IntPtr = FindWindowEx(windowLevel2Handle, IntPtr.Zero, "AfxWndW", "")
Dim windowLevel4Handle As IntPtr = FindWindowEx(windowLevel3Handle, IntPtr.Zero, "#32770", "")
Dim SubjectHandle As IntPtr = FindWindowEx(windowLevel4Handle, IntPtr.Zero, "Static", "S&ubject")
Dim SubjectTextBoxHandle As IntPtr = FindWindowEx(windowLevel4Handle, SubjectHandle, "RichEdit20WPT", "")
Dim length As Integer = GetWindowTextLength(SubjectTextBoxHandle)
Dim sb As New StringBuilder(length + 1)
GetWindowText(SubjectTextBoxHandle, sb, sb.Capacity)
Return sb.ToString()
Catch
Return ""
End Try
End Function
The important part is that the subject edit box needs to lose focus for the OOM to become aware of the change.
You can use accessibility API or raw Windows API to access the contents of the edit box or you can try to focus some other inspector control, such as the message body editor.

Display Excel sheet with macro inside form

I got application in .net and I would like to display excel sheet with macro inside my form. I found code that display excel file inside panel and everything works ok but once I run macro its frozen. If I will not run macro Its work fine.
Imports excel = Microsoft.office.interop.excel
Imports office = Microsoft.office.core
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
Private Sub btnShowExcel_Click(sender As Object, e As EventArgs) Handles btnShowExcel.Click
Dim sExcelFileName = "D:\123.xlsm"
Dim oExcel As New Excel.Application
oExcel.DisplayAlerts = False
oExcel.Workbooks.Open(sExcelFileName)
oExcel.Application.WindowState = excel.XlWindowState.xlNormal
oExcel.Visible = True
SetParent(oExcel.Hwnd, pnlExcel.Handle)
SendMessage(oExcel.Hwnd, WM_SYSCOMMAND, SC_MAXIMIZE, 0)
End Sub
End Class
Could anyone help me to change this code or find different one so I could display excel file with macro and macro will work without any problem.
Thanks
Regards
Mark

Is there a way to display user form in IDE instead of host app?

I created a userform in an *.xlam add-in and created a new commandbar and button in the IDE, but when I click the button, the user form is opened in Excel, and focus is forced away from the IDE. Is there a way to open the user form in the IDE instead of the host application without resorting to a .Net COM Add-in?
Here is the code that creates the commandbar and button and handles the button click event.
Option Explicit
Public WithEvents cmdBarEvents As VBIDE.CommandBarEvents
Private Sub Class_Initialize()
CreateCommandBar
End Sub
Private Sub Class_Terminate()
Application.VBE.CommandBars("VBIDE").Delete
End Sub
Private Sub CreateCommandBar()
Dim bar As CommandBar
Set bar = Application.VBE.CommandBars.Add("VBIDE", MsoBarPosition.msoBarFloating, False, True)
bar.Visible = True
Dim btn As CommandBarButton
Set btn = bar.Controls.Add(msoControlButton, , , , True)
btn.Caption = "Show Form"
btn.OnAction = "ShowForm"
btn.FaceId = 59
Set cmdBarEvents = Application.VBE.Events.CommandBarEvents(btn)
End Sub
Private Sub cmdBarEvents_Click(ByVal CommandBarControl As Object, handled As Boolean, CancelDefault As Boolean)
CallByName Me, CommandBarControl.OnAction, VbMethod
End Sub
Public Sub ShowForm()
Dim frm As New UserForm1
frm.Show
End Sub
P.S. You may need this line of code to remove the commandbar...
Application.VBE.CommandBars("VBIDE").Delete
Here is an alternative.
Put a button on your user form. For demonstration purpose, I am using this
Next put this code in the userform
Private Sub CommandButton1_Click()
Unload Me
Application.Visible = True
End Sub
Next paste this on top of your class module
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Dim Ret As Long, ChildRet As Long
Private Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, _
ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, _
ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
Private Const HWND_TOPMOST = -1
Private Const SWP_NOACTIVATE = &H10
Private Const SWP_SHOWWINDOW = &H40
Finally change your Sub ShowForm() to this
Public Sub ShowForm()
Dim frm As New UserForm1
Dim Ret As Long
frm.Show vbModeless
Application.Visible = False
Ret = FindWindow("ThunderDFrame", frm.Caption)
SetWindowPos Ret, HWND_TOPMOST, 100, 100, 250, 200, _
SWP_NOACTIVATE Or SWP_SHOWWINDOW
End Sub
This is what you get
EDIT
More thoughts. To prevent the user from creating more userforms when the user clicks on smiley, change the Sub ShowForm() to the below. (Alternative would be to disable the smiley and re enable it when the form unload?)
Public Sub ShowForm()
Dim frm As New UserForm1
Dim Ret As Long
Dim formCaption As String
'~~> Set Userform Caption
formCaption = "Blah Blah"
On Error Resume Next
Ret = FindWindow("ThunderDFrame", formCaption)
On Error GoTo 0
'~~> If already there in an instance then exit sub
If Ret <> 0 Then Exit Sub
frm.Show vbModeless
frm.Caption = formCaption
Application.Visible = False
Ret = FindWindow("ThunderDFrame", frm.Caption)
SetWindowPos Ret, HWND_TOPMOST, 100, 100, 250, 200, _
SWP_NOACTIVATE Or SWP_SHOWWINDOW
End Sub

Copying text to clipboard using VBA

In MS Excel 2010 I am trying to copy some text to the clipboard using SendKeys. However, it does not work.
Is this some kind of security measure that Microsoft took in order to prevent people from creating fraudulent macros? Here's some code that shows what I'm trying to do (assume, that you're in the vba window and have some text selected):
Public Sub CopyToClipboardAndPrint()
Call SendKeys("^(C)", True)
Dim Clip As MSForms.DataObject
Set Clip = New MSForms.DataObject
Clip.GetFromClipboard
Debug.Print Clip.GetText
End Sub
Note that in order to use the MSForms.DataObject you'll have to reference %windir%\system32\FM20.DLL (i.e. Microsoft Forms 2.0 Object Library).
Edit:
The text I'm trying to copy is not in the document window, but in the immediate window of the vba project window! So Selection.Copy won't work here.
The following code uses the SendInput function from the Windows API to simulate the Control-C key combination, in order to copy the current text selection to the Clipboard.
The copy/print subroutine (the very last procedure in the code) calls two utility functions to trigger the necessary key presses and then uses the code you prepared to retrieve the text from the Clipboard.
I've tested the code in the Immediate window, the code editor pane, and the worksheet.
Option Explicit
'adapted from:
' http://www.mrexcel.com/forum/excel-questions/411552-sendinput-visual-basic-applications.html
Const VK_CONTROL = 17 'keycode for Control key
Const VK_C = 67 'keycode for "C"
Const KEYEVENTF_KEYUP = &H2
Const INPUT_KEYBOARD = 1
Private Type KEYBDINPUT
wVK As Integer
wScan As Integer
dwFlags As Long
time As Long
dwExtraInfo As Long
End Type
Private Type GENERALINPUT
dwType As Long
xi(0 To 23) As Byte
End Type
Private Declare Function SendInput Lib "user32.dll" _
(ByVal nInputs As Long, _
pInputs As GENERALINPUT, _
ByVal cbSize As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" _
Alias "RtlMoveMemory" _
(pDst As Any, _
pSrc As Any, _
ByVal ByteLen As Long)
Private Sub KeyDown(bKey As Byte)
Dim GInput(0 To 1) As GENERALINPUT
Dim KInput As KEYBDINPUT
KInput.wVK = bKey
KInput.dwFlags = 0
GInput(0).dwType = INPUT_KEYBOARD
CopyMemory GInput(0).xi(0), KInput, Len(KInput)
Call SendInput(1, GInput(0), Len(GInput(0)))
End Sub
Private Sub KeyUp(bKey As Byte)
Dim GInput(0 To 1) As GENERALINPUT
Dim KInput As KEYBDINPUT
KInput.wVK = bKey
KInput.dwFlags = KEYEVENTF_KEYUP
GInput(0).dwType = INPUT_KEYBOARD
CopyMemory GInput(0).xi(0), KInput, Len(KInput)
Call SendInput(1, GInput(0), Len(GInput(0)))
End Sub
Sub CopyToClipboardAndPrint()
Dim str As String
'Simulate control-C to copy selection to clipboard
KeyDown VK_CONTROL
KeyDown VK_C
KeyUp VK_C
KeyUp VK_CONTROL
DoEvents
Dim Clip As MSForms.DataObject
Set Clip = New MSForms.DataObject
Clip.GetFromClipboard
Debug.Print Clip.GetText
End Sub

VB.Net Close window by title

I'm searching a method to close a specific window by the title.
I tried with Process.GetProcessesByName; but not is working by this case particulary.
I'm searching a method with APIs or similar (Not in C#, I see several code but not work fine in vb.net)
Thanks!
UPDATE
Thanks for the reply. But I'm still have a problem with the solution that you describe me below.
I'm have an only process that's control two windows. Then, if I close (or kill) the Window #2, instantly close the first one (See the image).
By this reason I think in using an API method from the begging.
I'm only want close the second window.
Try using something like this. using Process.MainWindowTitle to get the Title Text and Process.CloseMainWindow to close down the UI, its a little more graceful than killing the Process.
Note: Contains does a case-sensitive search
Imports System.Diagnostics
Module Module1
Sub Main()
Dim myProcesses() As Process = Process.GetProcesses
For Each p As Process In myProcesses
If p.MainWindowTitle.Contains("Notepad") Then
p.CloseMainWindow()
End If
Next
End Sub
End Module
As far as Win API functions try something like this. Be aware if you close the parent window you will close the children also.
Module Module1
Private Declare Auto Function FindWindowEx Lib "user32" (ByVal parentHandle As Integer, _
ByVal childAfter As Integer, _
ByVal lclassName As String, _
ByVal windowTitle As String) As Integer
Private Declare Auto Function PostMessage Lib "user32" (ByVal hwnd As Integer, _
ByVal message As UInteger, _
ByVal wParam As Integer, _
ByVal lParam As Integer) As Boolean
Dim WM_QUIT As UInteger = &H12
Dim WM_CLOSE As UInteger = &H10
Sub Main()
Dim handle As Integer = FindWindowEx(0, 0, Nothing, "YourFormsTitle")
PostMessage(handle, WM_CLOSE, 0, 0)
End Sub
End Module
You haven't showed us your code snippet. Perhaps you can try this one.
Dim processList() As Process
processList = Process.GetProcessesByName(ListBox1.Items(ListBox1.SelectedIndex).ToString)
For Each proc As Process In processList
If MsgBox("Terminate " & proc.ProcessName & "?", MsgBoxStyle.YesNo, "Terminate?") = MsgBoxResult.Yes Then
Try
proc.Kill()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End If
Next
In the snippet above, i have a list of window title on the listBox. The snippet will iterate the listbox for window titles, and if the title has been found, it asks a message to terminate the process or not.