Icon.ExtractAssociatedIcon for things that are not files? - vb.net

Is this possible? It gives me an error, and I had previously thought it could work for folders and drives and stuff like that as well.
Icon.ExtractAssociatedIcon("C:\") did not work when I tried it, and threw an error.
How can I get the associated icon from EVERYTHING? This is vb.net

The SHGetFileInfo() shell function can provide you with the icon you are looking for. This code worked well, it generated appropriate icons for drives, folders and files:
Imports System.Drawing
Imports System.Reflection
Imports System.Runtime.InteropServices
Public Module NativeMethods
Public Function GetShellIcon(ByVal path As String) As Icon
Dim info As SHFILEINFO = New SHFILEINFO()
Dim retval as IntPtr = SHGetFileInfo(path, 0, info, Marshal.SizeOf(info), &H100)
If retval = IntPtr.Zero Then Throw New ApplicationException("Could not retrieve icon")
'' Invoke private Icon constructor so we do not have to copy the icon
Dim cargt() As Type = { GetType(IntPtr) }
Dim ci As ConstructorInfo = GetType(Icon).GetConstructor(BindingFlags.NonPublic Or BindingFlags.Instance, Nothing, cargt, Nothing)
Dim cargs() As Object = { info.IconHandle }
Dim icon As Icon = CType(ci.Invoke(cargs), Icon)
Return icon
End Function
'' P/Invoke declaration
Private Structure SHFILEINFO
Public IconHandle As IntPtr
Public IconIndex As Integer
Public Attributes As Integer
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)> _
Public DisplayString As String
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=80)> _
Public TypeName As String
End Structure
Private Declare Auto Function SHGetFileInfo lib "Shell32.dll" (path As String, _
attributes As Integer, byref info As SHFILEINFO, infoSize As Integer, flags As Integer) As IntPtr
End Module

It is not possible to use Icon.ExtractAssociatedIcon on anything other than files. This API is a thin wrapper on top of the Win32 call ExtractAssociatedIcon. While the documentation for the managed code is a bit ambiguous, the native documentation is much clearer that the target must be a file. It goes further to say that it must be an executable file.
Unfortunately I'm not sure if there is an equivalent function for Directories or not.

Related

How to copy multiple files/folders using single windows copy GUI in VB.Net

I am making an app in VB.Net that copies many files and folders to the same directory and I wish to use windows explorer for that (so the user has GUI and I do not have to worry about showing any errors or compare files).
So, if I do this for each file/folder:
My.Computer.FileSystem.CopyDirectory(source_path, target_path, FileIO.UIOption.AllDialogs)
My.Computer.FileSystem.CopyFile(source_path, target_path, FileIO.UIOption.AllDialogs)
It works correctly and shows this window:
Which is fine, however, if I have many files and/or folders and I loop through them and call commands above, they launch a new copy window for each file/folder, instead of launching a single GUI that combines them all, like so:
Is it possible to combine multiple files/folders copy process into a single windows explorer copy window GUI?
Thanks to #Jimi, I got pointed in the direction of SHFileOperations, so i figured out how to do this. I made a small class to do this:
Imports System.Runtime.InteropServices
Public Class NativeCopy
Private Enum FO_Func As Short
FO_COPY = &H2
FO_DELETE = &H3
FO_MOVE = &H1
FO_RENAME = &H4
End Enum
Private Structure SHFILEOPSTRUCT
Public hwnd As IntPtr
Public wFunc As FO_Func
<MarshalAs(UnmanagedType.LPWStr)>
Public pFrom As String
<MarshalAs(UnmanagedType.LPWStr)>
Public pTo As String
Public fFlags As UShort
Public fAnyOperationsAborted As Boolean
Public hNameMappings As IntPtr
<MarshalAs(UnmanagedType.LPWStr)>
Public lpszProgressTitle As String
End Structure
<DllImport("shell32.dll", CharSet:=CharSet.Unicode)>
Private Shared Function SHFileOperation(
<[In]> ByRef lpFileOp As SHFILEOPSTRUCT) As Integer
End Function
Private Shared _ShFile As SHFILEOPSTRUCT
Public Shared Sub Copy(ByVal sSource As List(Of String), ByVal sTarget As String)
_ShFile.wFunc = FO_Func.FO_COPY
_ShFile.pFrom = String.Join(vbNullChar, sSource) + vbNullChar
_ShFile.pTo = sTarget
SHFileOperation(_ShFile)
End Sub
End Class
To copy files and/or folders is as simple as this:
Dim copy_items_paths As List(Of String)
Dim target_path As String
NativeCopy.Copy(copy_items_paths, target_path)

Window explorer is freezing when open

I need to open the specific folder for a file and I am doing it with:
file = Directory.GetFiles(filepath,Filename,
SearchOption.AllDirectories).FirstOrDefault()
Process.Start("explorer.exe", "/select," & file.ToString)
This code is immediately opening the folder which is already fully loaded, but it doesnt seem enabled, endeed I cant do any action in it. The form is not freezing.
Thanks
I'll give you an answer in two parts...
Firstly, if the GetFiles() call takes to long and freezes the form (which doesn't seem to be the current problem), you should do the following:
Use EnumerateFiles() instead because in this case, FirstOrDefault() will return immediately after finding a matching file, unlike GetFiles() which will get all the files first before calling FirstOrDefault().
Wrap the call to EnumerateFiles() in a Task.Run() to execute it on a worker thread in case the search takes a little too long:
' Or:
' Private Async Sub SomeEventHandler()
Private Async Function ParentMethod() As Task
Dim filePath As String = Await Task.Run(
Function()
Return Directory.EnumerateFiles(dirPath, FileName, SearchOption.AllDirectories).
FirstOrDefault()
End Function)
' TODO: Use `filePath` to open the folder and select the file.
End Function
Secondly, do not use Process.Start("explorer.exe", "/select") because a) it starts a new Explorer.exe process rather than opening the directory in the current one, b) it seems to be causing you some issues, and c) it has some limitations.
Instead, use the approach demonstrated in the answer linked in point (c) above. The code is in C# but it can be easily converted to VB. Here's the VB version of the code (with an additional overload).
Add the following class to your project:
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, you can easily call it like this:
NativeMethods.OpenFolderAndSelectFile(filePath)
Some additional notes:
You should choose meaningful variable names. filePath should refer to the path of a file. If you want to refer to a folder/directory path, use something like dirPath or folderPath instead.
You don't need to call .ToString() on a variable that's already of a String type.
I would change the variable name file to something else. Maybe foundFile. After all File is the name of a class in System.IO and vb.net is not case sensitive. Your code works fine for me with the variable name change. Also got rid of the .ToString. I used .EnumerateFiles as commented by #jmcilhinney in the question you deleted. I purposely chose a path with all sorts of strange characters and it still worked.
Private Sub OPCode()
Dim filepath = "C:\Users\xxxx\Documents\TextNotes\Dogs & Cats (Pets)"
Dim Filename = "Specialty Vets.txt"
Dim foundFile = Directory.EnumerateFiles(filepath, Filename,
IO.SearchOption.AllDirectories).FirstOrDefault()
Process.Start("explorer.exe", "/select," & foundFile)
End Sub

Diagnosing AccessViolationException in optimized compiled code

I have a large vb.net x86 project that is running in VS2015 and .Net 4.5.2
When it is compiled and run in debug without optimization then it works ok. However if I compile and run it in Release mode with optimization turned on then I get a variety of exceptions at the same innocuous line of code. I have tried debugging it in release mode but the breakpoints are unreliable. Also the very act of debugging it seems to modify the exception. Also if I change the code (for example putting a MsgBox in to display information) then the problem can go away. For example I changed an Arraylist to a List(Of Control) and the problem no longer occurred where it did before but now moved elsewhere.
I have received all of the following at different times:
AccessViolationException,
NullReferenceException (somewhere deep within .Net classes)
and FatalExecutionEngineError
The exception detail in the AccessViolationException tells nothing except that "this is often an indication that other memory is corrupt". The stack trace is meaningless and there is no description of what it thought was at the invalid memory address.
I also cannot find any meaningful detail about what Optimization in the compiler actually does - one solution might be to turn Optimization off but I don't understand what the benefit / negative effect is of doing this.
Is the Optimization unreliable? How can one possibly try and ascertain what is happening?
The only unmanaged code we use is some calls to get Icons related to file extensions - which are then cloned into managed objects and the unmanaged memory destroyed. This is pretty standard and the same API has been used since 1.1 and through 4.5.2 for 10 years without this occurring before.
I cannot create a small project that reproduces the issue
Here's the code we use for extracting icons as it's the only potential cause I have right now. It was borrowed from elsewhere and I can't really tell whether it's as good as it should be:
Public Class IconExtractor
<Flags()> Private Enum SHGFI
SmallIcon = &H1
LargeIcon = &H0
Icon = &H100
DisplayName = &H200
Typename = &H400
SysIconIndex = &H4000
UseFileAttributes = &H10
End Enum
<StructLayout(LayoutKind.Sequential)>
Private Structure SHFILEINFO
Public hIcon As IntPtr
Public iIcon As Integer
Public dwAttributes As Integer
<MarshalAs(UnmanagedType.LPStr, SizeConst:=260)> Public szDisplayName As String
<MarshalAs(UnmanagedType.LPStr, SizeConst:=80)> Public szTypeName As String
Public Sub New(ByVal B As Boolean)
hIcon = IntPtr.Zero
iIcon = 0
dwAttributes = 0
szDisplayName = vbNullString
szTypeName = vbNullString
End Sub
End Structure
Private Declare Auto Function SHGetFileInfo Lib "shell32" (
ByVal pszPath As String, ByVal dwFileAttributes As Integer,
ByRef psfi As SHFILEINFO, ByVal cbFileInfo As Integer, ByVal uFlags As SHGFI) As Integer
<DllImport("user32.dll", SetLastError:=True)>
Private Shared Function DestroyIcon(ByVal hIcon As IntPtr) As Boolean
End Function
Public Shared Sub GetIconsForFile(ByVal rstrFileName As String, ByRef rzSmallIcon As Icon, ByRef rzLargeIcon As Icon)
Dim zFileInfo As New SHFILEINFO(True)
Dim cbSizeInfo As Integer = Marshal.SizeOf(zFileInfo)
Dim flags As SHGFI = SHGFI.Icon Or SHGFI.UseFileAttributes Or SHGFI.SmallIcon
SHGetFileInfo(rstrFileName, 256, zFileInfo, cbSizeInfo, flags)
' Use clone so we can destroy immediately
rzSmallIcon = DirectCast(Icon.FromHandle(zFileInfo.hIcon).Clone, Icon)
DestroyIcon(zFileInfo.hIcon)
zFileInfo = New SHFILEINFO(True)
cbSizeInfo = Marshal.SizeOf(zFileInfo)
flags = SHGFI.Icon Or SHGFI.UseFileAttributes Or SHGFI.LargeIcon
SHGetFileInfo(rstrFileName, 256, zFileInfo, cbSizeInfo, flags)
' Use clone so we can destroy immediately
rzLargeIcon = DirectCast(Icon.FromHandle(zFileInfo.hIcon).Clone, Icon)
DestroyIcon(zFileInfo.hIcon)
End Sub
End Class
I stumbled across the solution to this by chance.
I was reading this documentation of SHGETFILEINFO
https://msdn.microsoft.com/en-us/library/windows/desktop/bb762179(v=vs.85).aspx
and found that it said in the remarks: You should call this function from a background thread. Failure to do so could cause the UI to stop responding
It isn't clear why you should call it from a background thread nor is it clear what "stop responding" might actually manifest itself as.
However it seemed this was fairly likely what was causing the problem and so I refactored to execute the api call under a separate thread. This has certainly seemed to work. Many of the examples on the internet of SHGETFILEINFO do not seem to consider the separate thread requirement.
I reproduce the whole refactored code here:
Imports System.Drawing
Imports System.Runtime.InteropServices
Imports System.Threading
''' <summary>
''' Retrieves the small and large icons registered for a filename based on the file's extension
''' </summary>
Public Class FileIcons
Private mFileName As String
Private mSmallIconHandle As IntPtr
Private mSmallIcon As Icon
Private mLargeIconHandle As IntPtr
Private mLargeIcon As Icon
Public Sub New(ByVal rFileName As String)
mFileName = rFileName
Dim t As New Thread(AddressOf GetIconsForFile)
t.SetApartmentState(ApartmentState.STA)
t.Start()
t.Join()
' Use clone so we can destroy immediately
mSmallIcon = DirectCast(Icon.FromHandle(mSmallIconHandle).Clone, Icon)
DestroyIcon(mSmallIconHandle)
' Use clone so we can destroy immediately
mLargeIcon = DirectCast(Icon.FromHandle(mLargeIconHandle).Clone, Icon)
DestroyIcon(mLargeIconHandle)
End Sub
Public ReadOnly Property SmallIcon As Icon
Get
Return mSmallIcon
End Get
End Property
Public ReadOnly Property LargeIcon As Icon
Get
Return mLargeIcon
End Get
End Property
Private Sub GetIconsForFile()
' Go and extract the small and large icons
' Full filename must be < MAX_PATH - which is 260 chars in .Net (apparently) though a file path/length of 256 also causes an error.
' Otherwise SHGetFileInfo gets nothing, Icon.FromHandle then gives "System.ArgumentException: The Win32 handle you passed to Icon is invalid or of the wrong type."
' This needs to be stopped in the calling code, or the resulting error trapped
Dim zFileInfo As New SHFILEINFO(True)
Dim cbSizeInfo As Integer = Marshal.SizeOf(zFileInfo)
Dim flags As SHGFI = SHGFI.Icon Or SHGFI.UseFileAttributes Or SHGFI.SmallIcon
SHGetFileInfo(mFileName, 256, zFileInfo, cbSizeInfo, flags)
mSmallIconHandle = zFileInfo.hIcon
zFileInfo = New SHFILEINFO(True)
cbSizeInfo = Marshal.SizeOf(zFileInfo)
flags = SHGFI.Icon Or SHGFI.UseFileAttributes Or SHGFI.LargeIcon
SHGetFileInfo(mFileName, 256, zFileInfo, cbSizeInfo, flags)
mLargeIconHandle = zFileInfo.hIcon
End Sub
#Region "WinAPI"
<Flags()> Private Enum SHGFI
SmallIcon = &H1
LargeIcon = &H0
Icon = &H100
DisplayName = &H200
Typename = &H400
SysIconIndex = &H4000
UseFileAttributes = &H10
End Enum
<StructLayout(LayoutKind.Sequential)>
Private Structure SHFILEINFO
Public hIcon As IntPtr
Public iIcon As Integer
Public dwAttributes As Integer
<MarshalAs(UnmanagedType.LPStr, SizeConst:=260)> Public szDisplayName As String
<MarshalAs(UnmanagedType.LPStr, SizeConst:=80)> Public szTypeName As String
Public Sub New(ByVal B As Boolean)
hIcon = IntPtr.Zero
iIcon = 0
dwAttributes = 0
szDisplayName = vbNullString
szTypeName = vbNullString
End Sub
End Structure
Private Declare Auto Function SHGetFileInfo Lib "shell32" (
ByVal pszPath As String,
ByVal dwFileAttributes As Integer,
ByRef psfi As SHFILEINFO,
ByVal cbFileInfo As Integer,
ByVal uFlags As SHGFI) As Integer
<DllImport("user32.dll", SetLastError:=True)>
Private Shared Function DestroyIcon(ByVal hIcon As IntPtr) As Boolean
End Function
#End Region
End Class

Bring specific Word instance to front VB

I am using the following Namespace: Imports Microsoft.Office.Interop
I need a function, that brings a specific Word-Instance to front.
Optimal solution would be
Public Sub toFront(ByVal wdObj as Word.Application, ByVal filePath as String)
'sends by filePath specified Word-Instance to Front
End Sub
I know there is Word.Application.Activate, but that does not seem to be working all the time and also it only activates the ActiveDocument.
I already tried the following functions where wdObj is a Word.Application
wdObj.Activate()
wdObj.Application.Documents(My.Settings.DocPath).Activate()
There is no such a function in Interop. Try to use WinApi instead:
void BringWindowToTop (Microsoft.Office.Interop.Word.Document doc) {
// To make it active document
doc.Activate();
// I'm not 100% sure, but I think MainWindowHandle gives
// a handle of currently active window of word
IntPtr hwnd = Process.GetCurrentProcess().MainWindowHandle;
BringWindowToTop(hwnd);
}
[DllImport("user32.dll", SetLastError = true)]
static extern bool BringWindowToTop(IntPtr hWnd);

vb.net dynamic plugin loading question

I'm using the following code in a vb.net 2008 project to dynamically load dll's (with forms) from a folder, all works great however I can't figure out for the life of me how to call a function or get a public variable from the plugins.
Can anyone answer this issue for me?
Dim PluginList As String() = Directory.GetFiles(appDir, "*.dll")
For Each Plugin As String In PluginList
Dim Asm As Assembly
Dim SysTypes As System.Type
Asm = Assembly.LoadFrom(Plugin)
SysTypes = Asm.GetType(Asm.GetName.Name + ".frmMain")
Dim IsForm As Boolean = GetType(Form).IsAssignableFrom(SysTypes)
If IsForm Then
Dim tmpForm As Form = CType(Activator.CreateInstance(SysTypes), Form)
You should probably create an interface in a common assembly and have your form implement it, this way you can cast you dynamically loaded object as your interface type.
Imports System.Reflection
Imports Plugin.Interfaces
Sub Main()
Dim assembly As Assembly
assembly = assembly.LoadFrom("Plugin.X.dll")
Dim type As Type
Dim found As Boolean = False
For Each type In assembly.GetTypes()
If GetType(IForm).IsAssignableFrom(type) Then
found = True
Exit For
End If
Next
If found Then
Dim instance As IForm
instance = CType(Activator.CreateInstance(type), IForm)
Console.WriteLine(instance.Add(20, 20))
End If
End Sub
Interface Assembly
Public Interface IForm
Function Add(ByVal x As Integer, ByVal y As Integer) As Integer
End Interface
Plugin Assembly
Imports Plugin.Interfaces
Public Class Form
Implements IForm
Public Function Add(ByVal x As Integer, ByVal y As Integer) As Integer Implements IForm.Add
Return x + y
End Function
End Class