Open Folder Path if not already open, if open show window - vb.net

This seems like a simple task but I cannot seem to produce the results looking for.
currently I have this code
Dim folderpath As String
Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
folderpath = "C:\epds\WIP"
Process.Start("explorer.exe", folderpath)
End Sub
That's fine and it opens my folder path as indicated, however, if the instance of that folder is already open in the explorer how do I just make that window current instead of opening a new window explorer?
EDIT: This seemed to do the trick, thanks for pointing me in the right direction #Okuma.Scott
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function FindWindow( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As IntPtr
End Function
<DllImport("user32.dll", EntryPoint:="FindWindow", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function FindWindowByClass( _
ByVal lpClassName As String, _
ByVal zero As IntPtr) As IntPtr
End Function
<DllImport("user32.dll", EntryPoint:="FindWindow", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function FindWindowByCaption( _
ByVal zero As IntPtr, _
ByVal lpWindowName As String) As IntPtr
End Function
Dim folderpath As String
Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
folderpath = "C:\epds\WIP"
'Process.Start("explorer.exe", folderpath)
Dim nWnd As IntPtr
Dim ceroIntPtr As New IntPtr(0)
Dim Wnd_name As String
Wnd_name = "WIP"
nWnd = FindWindow(Nothing, Wnd_name)
'show the info
If nWnd.Equals(ceroIntPtr) Then
Process.Start("explorer.exe", folderpath)
Else
AppActivate(Wnd_name)
SendKeys.SendWait("~")
End If
End Sub

I was trying to solve this same issue and discovered that it seems to work by just calling Process.Start with the desired path:
Process.Start("C:\Temp")
If the folder is already open in an Explorer window it opens the existing window otherwise it opens a new window.

you need to import Imports System.Runtime.InteropServices
then you can use the function Findwindow
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function FindWindow(ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
End Function
Then make 2 dims 1. folderpath and 2 is foldername
Then in your click event use "System.IO.Path.GetFileName(folderpath)" to get the name of the window you are looking for." for you WIP"
Then check with a if statement if FindWindow(vbNullString, foldername) = 0 "not open"
The vbNullString Represents a zero-length string for print and display functions, and for calling external procedures."msdn"
so if findwindow is 0 open the folder and else focus the folder
Dim folderpath As String
Dim foldername As String
Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
folderpath = "C:\epds\WIP"
foldername = System.IO.Path.GetFileName(folderpath)
If FindWindow(vbNullString, foldername) = 0 Then
Process.Start("explorer.exe", folderpath)
Else
AppActivate(foldername)
SendKeys.SendWait("~")
End If
End Sub

This works and will not open multiple windows:
Process.Start(new ProcessStartInfo() { FileName = "C:\\", UseShellExecute = true });
The only downside is that it does not bring the opened folder to the foreground (which depending on your use case may or may not be a bad thing!

Related

How to set the Explorer window of a specific file as a child window of TopMost form?

I made some research, but I can't find something really "interesting". I tried my best to find any kind of documentation or questions that are closest to my case as following:
How to find main window title name of application
how to get the window title of a process
How to get the Title Bar Text by its Process Id
getting the name of a process
How do I get list of Process Names running
Check to see if process is running
How To Get Process Owner ID
How to get the title/name of the last active window?
Get Process ID from Window Title
and also
Process.GetProcessesByName Method
The code I am using to open the process window
Private Async Function ParentMethod() As Task
Dim filePath As String = Await Task.Run(
Function()
Return Directory.EnumerateFiles(My.Settings.Cartellasalvataggio, titolo & ".mp3",
SearchOption.AllDirectories).FirstOrDefault()
End Function)
If Not String.IsNullOrEmpty(filePath) Then
LinkLabel1.Text = "File exist already"
LinkLabel1.Visible = True
PictureBox7.Visible = True
Else
MsgBox("it doesn't exist")
End If
End Function
and the helper class
Imports System.IO
Imports System.Runtime.InteropServices
Public Class NativeMethods
<DllImport("shell32.dll", SetLastError:=True)>
Private Shared Function SHOpenFolderAndSelectItems(
pidlFolder As IntPtr, cidl As UInteger,
<[In], MarshalAs(UnmanagedType.LPArray)> apidl As IntPtr(),
dwFlags As UInteger) As Integer
End Function
<DllImport("shell32.dll", SetLastError:=True)>
Private Shared Sub SHParseDisplayName(
<MarshalAs(UnmanagedType.LPWStr)> name As String,
bindingContext As IntPtr, <Out> ByRef pidl As IntPtr,
sfgaoIn As UInteger, <Out> ByRef psfgaoOut As UInteger)
End Sub
Public Shared Sub OpenFolderAndSelectFile(filePath As String)
Dim dirPath As String = Path.GetDirectoryName(filePath)
Dim fileName As String = Path.GetFileName(filePath)
OpenFolderAndSelectFile(dirPath, fileName)
End Sub
Public Shared Sub OpenFolderAndSelectFile(dirPath As String, fileName As String)
Dim nativeFolder As IntPtr
Dim psfgaoOut As UInteger
SHParseDisplayName(dirPath, IntPtr.Zero, nativeFolder, 0, psfgaoOut)
If nativeFolder = IntPtr.Zero Then
' Log error, can't find folder
Return
End If
Dim nativeFile As IntPtr
SHParseDisplayName(Path.Combine(dirPath, fileName),
IntPtr.Zero, nativeFile, 0, psfgaoOut)
Dim fileArray As IntPtr()
If nativeFile = IntPtr.Zero Then
' Open the folder without the file selected if we can't find the file
fileArray = New IntPtr(-1) {}
Else
fileArray = New IntPtr() {nativeFile}
End If
SHOpenFolderAndSelectItems(nativeFolder, CUInt(fileArray.Length), fileArray, 0)
Marshal.FreeCoTaskMem(nativeFolder)
If nativeFile <> IntPtr.Zero Then
Marshal.FreeCoTaskMem(nativeFile)
End If
End Sub
End Class
then calling it with
NativeMethods.OpenFolderAndSelectFile(filepath,filename & "extension"))
Since I am opening the process this way and NOT with Process class, almost all of them are not suitable to be considered for my case as many of them refer to notepad, while I think the explorer window title and ID changes for every file ( obviously), while "notepad" process, stay "notepad".
I also tried BringToFront, but this latter moves a control in front of other controls, but in this case Explorer is not a control, right?
The least I want to do is to
Get a list of active windows & their process names
as It will waste memory and time usage for no reason as I will need to "filter" process to find my process.
Hope we can find a solution to this, Thanks in advance.
Mattia
This is the solution to it using FindWindowW e SetWindowPos Api.
It is showing Explorer folder on top of top most form.
<DllImport("user32.dll", EntryPoint:="FindWindowW")>
Public Shared Function FindWindowW(<MarshalAs(UnmanagedType.LPTStr)> ByVal lpClassName As String, <MarshalAs(UnmanagedType.LPTStr)> ByVal lpWindowName As String) As IntPtr
End Function
<DllImport("user32.dll")>
Shared Function SetWindowPos(ByVal hWnd As IntPtr, ByVal hWndInsertAfter As IntPtr, ByVal X As Integer, ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal uFlags As UInteger) As Boolean
End Function
Shared ReadOnly HWND_TOPMOST As IntPtr = New IntPtr(-1)
Const SWP_NOSIZE As UInt32 = &H1
Const SWP_NOMOVE As UInt32 = &H2
Const SWP_SHOWWINDOW As UInt32 = &H40
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim inptr = FindWindowW("CabinetWClass", Nothing)
SetWindowPos(inptr, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE Or SWP_NOSIZE Or SWP_SHOWWINDOW)
End Sub

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.

Copy file to remote computer

I have created a program which will copy from my computer to other remote computers a specific file. I have successfully did this, but only in the driver C:\ . My question is how can I copy the file to the D:\ drive?
I have tried adding \157.60.113.28\D:\testnew\right.bmp but with no luck. Let me know!
Imports System
Imports System.Runtime.InteropServices
Imports System.Security.Principal
Imports System.Security.Permissions
Public Class Form1
<DllImport("advapi32.DLL", SetLastError:=True)> _
Public Shared Function LogonUser(ByVal lpszUsername As String, ByVal lpszDomain As String, _
ByVal lpszPassword As String, ByVal dwLogonType As Integer, ByVal dwLogonProvider As Integer, _
ByRef phToken As IntPtr) As Integer
End Function
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim admin_token As IntPtr
Dim wid_current As WindowsIdentity = WindowsIdentity.GetCurrent()
Dim wid_admin As WindowsIdentity = Nothing
Dim wic As WindowsImpersonationContext = Nothing
Try
MessageBox.Show("Copying file...")
If LogonUser("Local Admin name", "Local computer name", "pwd", 9, 0, admin_token) <> 0 Then
wid_admin = New WindowsIdentity(admin_token)
wic = wid_admin.Impersonate()
System.IO.File.Copy("C:\right.bmp", "\\157.60.113.28\testnew\right.bmp", True)
MessageBox.Show("Copy succeeded")
Else
MessageBox.Show("Copy Failed")
End If
Catch se As System.Exception
Dim ret As Integer = Marshal.GetLastWin32Error()
MessageBox.Show(ret.ToString(), "Error code: " + ret.ToString())
MessageBox.Show(se.Message)
Finally
If wic IsNot Nothing Then
wic.Undo()
End If
End Try
End Sub
End Class
the path you're looking for is:
\\157.60.113.28\D$\testnew\right.bmp
if you are required to interact with the remote machine regularly it would be wise to map the network path as a network drive in your machine.

Read INI in Visual Basic vb.NET

I'm trying to read a INI file then dump the values into the registry for a setup application to function.
The INI file looks like this
[Setup Components]
Sybase Adaptive Server Anywhere Client=Yes
Borland Database Engine=Yes
BDERoot=c:\Program Files\Borland\BDE
Program Shortcuts=Yes
ODBC Data Service Name=Yes
Local Client = Yes
Sybase Admin = Yes
Sybase Directory= C:\Program Files\SQL Anywhere 16
DBRoot=c:\Database
Word Link=Yes
Installation Root Folder=c:\program
FireDAC=DB=asa16;eng=ENGINE;dbn=DBNAME;links=TCPIP{Host=127.0.0.1;Port=2638}
[Program Shortcuts]
Desktop=Yes
Start Menu=Yes
Program Title=PROGRAM
EXE Pathname=c:\program.exe
Parameters=DBNAME
Starting Directory=c:\
Icon=icon.ICO
[ODBC Data Service Name]
Data Service Name=DBNAME
Database File Name=c:\database\database.db
Database Name=DBNAME
Server Name=ENGINE
Comm Links=TCPIP{}
PrefetchBuffer=6000
PrefetchRows=50
CommBufferSize=1460
Compress=no
[Service]
Service Name=SQLANYs_DBNAME
Display Name=SQL Anywhere - DBNAME
Service Group=SQLANYServer
Params=-n DBNAME -x TCPIP{} "C:\database\database.db" -n DBNAME
So I need all those items to be dumped into the registry easily.
I'm using Visual Studio 2015, I did attempt to use Ludvik Jerabek's INI reader (http://www.codeproject.com/Articles/21896/INI-Reader-Writer-Class-for-C-VB-NET-and-VBScript) but had no luck getting that to function!
The code i did use for the above was the following:
Dim ini = New IniFile()
ini.Load("setup.ini")
Dim readValue = ini.Sections("Service").Keys("Service Name")
MessageBox.Show(readValue.ToString)
When running this code i got the following error : "Conversion from string "Service" to type "Integer" is not valid. -Also this method means naming each and every key in the INI file which would be quite some task!
I then went down another method after reading some help questions on here and i used the following:
Private Declare Auto Function GetPrivateProfileString Lib "kernel32" (ByVal lpAppName As String,
ByVal lpKeyName As String,
ByVal lpDefault As String,
ByVal lpReturnedString As StringBuilder,
ByVal nSize As Integer,
ByVal lpFileName As String) As Integer
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim sb As StringBuilder
sb = New StringBuilder(500)
Dim readVal = GetPrivateProfileString("Service", "Service Name", "", sb, sb.Capacity, "setup.ini")
MessageBox.Show(readVal.ToString)
End Sub
However this just returns "0"
Any help would be grateful to find a way to get this reading from the INI and dumping to the registry
Using the given IniFile class, this will get you to the config setting values:
Private Sub INIButtonTest_Click(sender As Object, e As EventArgs) Handles INIButtonTest.Click
Try
Dim iniFilePath As String = "H:\Dev\StackOverflow\StackOverflowTest\StackOverflowApp\bin\Debug\test.ini"
Dim myIniFile As New IniFile
myIniFile.Load(iniFilePath)
Dim myValue As String = getIniValue(myIniFile, "Service", "Service Name")
If Not String.IsNullOrEmpty(myValue) Then
MessageBox.Show(String.Format("Found value: [{0}]", myValue))
End If
Catch ex As Exception
MessageBox.Show(String.Concat("Something went wrong:", ex.Message))
End Try
End Sub
Private Function getIniValue(iniFileInstance As IniFile, sectionName As String, sectionKey As String) As String
Dim myValue As String = String.Empty
For Each sect As IniFile.IniSection In iniFileInstance.Sections
If sect.Name = sectionName Then
For Each key As IniFile.IniSection.IniKey In sect.Keys
If key.Name = sectionKey Then
myValue = key.GetValue
Exit For
End If
Next
Exit For
End If
Next
Return myValue
End Function
As an alternate, the code for the original approach is very close to correct, but GetPrivateProfileString doesn't actually exist in Kernel32.dll. You need to add a W to the name, which makes the corrected code:
' With these imports
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Imports System.Text
' Note the W on the function name
Private Declare Auto Function GetPrivateProfileStringW Lib "kernel32" (ByVal lpAppName As String,
ByVal lpKeyName As String,
ByVal lpDefault As String,
ByVal lpReturnedString As StringBuilder,
ByVal nSize As Integer,
ByVal lpFileName As String) As Integer
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim sb As New StringBuilder(500)
Dim result As Integer = GetPrivateProfileStringW("Service", "Service Name", "", sb, sb.Capacity, "setup.ini")
If result > 0 Then
MessageBox.Show(sb.ToString())
Else
Dim ex As New Win32Exception(Marshal.GetLastWin32Error())
MessageBox.Show(ex.Message)
End If
End Sub
If you prefer not to use the W, or you want to call your function something different, you can also use the DllImportAttribute and change the function declaration to:
<DllImport("Kernel32.dll", CharSet:=CharSet.Auto,
SetLastError:=True, EntryPoint:="GetPrivateProfileStringW")>
Private Shared Function GetPrivateProfileString(ByVal lpAppName As String,
ByVal lpKeyName As String,
ByVal lpDefault As String,
ByVal lpReturnedString As StringBuilder,
ByVal nSize As Integer,
ByVal lpFileName As String) As Integer
End Function
This is what I have used since VB 3.0 and it has worked with all versions of windows since 3.1. There have been a few modifications to make it fit into the conventions of the newer dev tools but essentially they are the same routines.
These must be in a public class or in the public area of a form:
Public Declare Function OSGetPrivateProfileString% Lib "kernel32" Alias "GetPrivateProfileStringA"(ByVal AppName As String, ByVal KeyName As String, ByVal keydefault As String, ByVal ReturnString As String, ByVal NumBytes As Integer, ByVal FileName As String)
Public Declare Function OSWritePrivateProfileString Lib "kernel32" Alias "WritePrivateProfileStringA"(ByVal AppName As String, ByVal KeyName As String, ByVal keydefault As String, ByVal FileName As String) As Integer
Then, somewhere in your form's code area or if you have a "functions.vb" class (where the above would be declared) place these two:
Public Shared Function GetINIString(ByVal strItem As String, ByVal strDefault As String, ByVal strSection As String, ByVal filename As String) As String
Const BUFFERSIZE As Integer = 16768
Dim strTemp As New String(Chr(0), BUFFERSIZE)
Dim stringSize As Long = 0
stringSize = OSGetPrivateProfileString%(strSection, strItem, strDefault, strTemp, BUFFERSIZE, filename)
Return Strings.Left(strTemp, stringSize)
End Function
AND
Public Shared Sub PutINIString(strItem As String, strValue As String, strSection As String, iniFilename As String)
Dim i As Integer = 0
i = OSWritePrivateProfileString(strSection, strItem, strValue, iniFilename)
End Sub
This continues to work in VB 2010

Can VB.net take control of a VBA form?

I have an Excel VBA macro containing a form. I would like for VB.net to either hide the form or take control of it and click the "X" button or the custom "Exit" button.
Does anybody have experience with this?
Sendkeys are highly unreliable.
If you can automate the Excel from VB.Net the I would recommend that. If you are interested in automating Excel from VB.Net then see this.
If you don't want to automate but want to directly handle it then I would recommend using the FindWindow and Sendmessage API to get control of the form. See this Example.
Let's say that there is a userform in Excel which looks like this
Use this code form VB.Net
CODE:
Imports System.Runtime.InteropServices
Imports System.Text
Public Class Form1
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function FindWindow( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As IntPtr
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function SendMessage(ByVal hWnd As IntPtr, _
ByVal Msg As UInteger, _
ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Boolean
End Function
Private Const WM_CLOSE = &H10
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim Ret As Integer
'~~> Get the handle of the Excel Userform named "Example"
Ret = FindWindow(vbNullString, "Example")
'~~> If Found
If Ret <> 0 Then
'~~> Close the Window
SendMessage(Ret, WM_CLOSE, CLng(0), CLng(0))
End If
End Sub
End Class