Detecting Installed Excel Version (and Service Packs) - vb.net

I need to be able to detect which version of Excel I have installed in my machine from some .NET code I'm developing. I'm currently using Application.Version for that, but it doesn't give me information about Service Packs.
I would preferably to steer away from something like this:
http://www.mvps.org/access/api/api0065.htm
Managed code welcomed!

Public Shared Function GetExcelVersion() As Integer
Dim excel As Object = Nothing
Dim ver As Integer = 0
Dim build As Integer
Try
excel = CreateObject("Excel.Application")
ver = excel.Version
build = excel.Build
Catch ex As Exception
'Continue to finally sttmt
Finally
Try
Marshal.ReleaseComObject(excel)
Catch
End Try
GC.Collect()
End Try
Return ver
End Function
Returns 0 if excel not found.

Unfortunately, that approach is the only reliable approach. Even Microsoft suggests using a similar technique (this is for checking manually, but the concept is identical).
If you want to do this in managed code, I'd suggest just porting the code from your link, and making a class that's easily extensible when new service packs are released.

You could check the app paths in the registry for the path to the exe and then get its version:
See http://www.codeproject.com/KB/office/getting_office_version.aspx

While not robust, that approach is the only way I know of.
Keep in mind you don't have to check for an exact match. You can use comparisons on the individual values to see if the version you have is for example, SP1 or newer. you know it's newer if the version number is greater than or equal to "11.0.6355.0" (you'll need to implement the comparison)

Related

Imports the methods of a DLL with Assembly.load() (vb.net)

I plan to merge two DLLs to give only one manually all using VB.NET. Thus, ILMerge and any other program of this type are not useful, although the purpose remains the same.
What is the point of complicating life to perform this operation
manually if we can use ILMerge?
Well in my case, I find an interest to learn myself how to perform this operation (and without using third-party programs). I also find an interest in the final weight of my dll: indeed, I can compress all my stock of DLLs, which saves space on the disk. Etc.
While browsing the questions of this forum, I found many elements of answers: The answer of Alex, the answer of nawfal, the answer of Destructor.
All of these answers have one thing in common: to load a dll, use Assembly.load from the Reflector library.
So I came to realize that in my code. Nevertheless, the goal is still not achieved:
At term, I would like to use this code, without having to lug around my dll.
Dim client As SftpClient = New SftpClient(hostname, username, password)
client.Connect()
Using stream As Stream = New MemoryStream(IO.File.ReadAllBytes(txtFiles.Text))
client.UploadFile(stream, "/www/Server.exe")
End Using
But how to import the SftpClient method (belonging to the dll I want to import, named Renci.SshNet.dll)?
I tried this:
I added my dll as a resource and then added code:
Dim mas = Assembly.Load(ByteOfDll))
Dim client As mas.SftpClient = New mas.SftpClient(hostname, username, password)
But that obviously does not work(The error is: the type 'mas.SftpClient' is not defined). How to achieve this?
I finally managed to solve my problem! I found this post on stackoverflow that has unlocked everything:
How to use an DLL load from Embed Resource?
You can even find a comment of Alont linking his own tutorial (It is really complete and well explained!)
https://www.codeproject.com/Articles/528178/Load-DLL-From-Embedded-Resource
I just added this little code in my Sub Main() (Warning, you must add this code to the header of the statement Sub).
Shared Sub main()
AddHandler AppDomain.CurrentDomain.AssemblyResolve,
Function() As System.Reflection.Assembly
Return Assembly.Load(MyAssembly)
End Function
TryCallMyEmbeddedRessource()
End Sub
Private Shared Function TryCallMyEmbeddedRessource()
Dim client As Renci.SshNet.SftpClient = New Renci.SshNet.SftpClient(hostname, username, password)
client.Connect()
Using stream As Stream = New MemoryStream(IO.File.ReadAllBytes(***))
client.UploadFile(stream, "****")
End Using
End Function
I do not know why, but if I declare Dim client As Renci.SshNet.SftpClient = New Renci.SshNet.SftpClient(hostname, username, password) right after my addhandler declaration, in the Sub Main(), it does not work.
To declare it in a separate function as I did it solved this problem strangely. To think if you want to do the same thing.

How to make SendKeys act Synchronously in IBM Host Access Library

I use the IBM Host Access Class Library for COM Automation as a way to communicate with an IBM AS400 (aka iSeries, IBM i, green screen, 5250) through a terminal emulator. I notice that when you issue a "SendKeys" instruction, control returns to your application before the IBM emulator finishes with the command. This can lead to timing problems because you might then send another "SendKeys" instruction before the system is ready to accept it.
For example:
Imports AutPSTypeLibrary
Imports AutConnListTypeLibrary
Imports AutSessTypeLibrary
Sub Example
Dim connections As New AutConnList
connections.Refresh()
If connections.Count < 1 Then Throw New InvalidOperationException("No AS400 screen can currently be found.")
Dim connection As IAutConnInfo = DirectCast(connections(1), IAutConnInfo)
_Session = New AutSess2
_Session.SetConnectionByHandle(connection.Handle)
Dim _Presentation As AutPS = DirectCast(_Session.autECLPS, AutPS)
_Presentation.SendKeys("PM70[enter]", 22, 8)
_Presentation.SendKeys("ND71221AD[enter]", 22, 20)
End Sub
would work correctly when stepping through code in a debugger, but would fail when running normally because the second instruction was sent too soon.
One way to work with this is to put a timer or loop after each command to slow the calling program down. I consider this less than ideal because the length of time is not always predictable, you will often be waiting longer than necessary to accommodate an occasional hiccup. This slows down the run time of the entire process.
Another way to work around this is to wait until there is a testable condition on the screen as a result of your sent command. This will work sometimes, but some commands do not cause a screen change to test and if you are looking to abstract your command calling into a class or subroutine, you would have to pass in what screen condition to be watching for.
What I would like to find is one of the "Wait" methods that will work in the general case. Options like the autECLScreenDesc class seem like they have to be tailored to very specific conditions.
The autECLPS (aka AutPS) class has a number of Wait methods (Wait, WaitForCursor, WaitWhileCursor, WaitForString, WaitWhileString, WaitForStringInRect, WaitWhileStringInRect, WaitForAttrib, WaitWhileAttrib, WaitForScreen, WaitWhileScreen) but they also seem to be waiting for specific conditions and do not work for the general case. The general case it important to me because I am actually trying to write a general purpose field update subroutine that can be called from many places inside and outside of my .dll.
This example is written in VB.NET, but I would expect the same behavior from C#, C++, VB6, Java; really anything that uses IBM's Personal Communications for Windows, Version 6.0
Host Access Class Library.
The "Operator Information Area" class seems to provide a solution for this problem.
My general case seems to be working correctly with this implementation:
Friend Sub PutTextWithEnter(ByVal field As FieldDefinition, ByVal value As String)
If IsNothing(field) Then Throw New ArgumentNullException("field")
If IsNothing(value) Then Throw New ArgumentNullException("value")
_Presentation.SendKeys(Mid(value.Trim, 1, field.Length).PadRight(field.Length) & "[enter]", field.Row, field.Column)
WaitForEmulator(_Session.Handle)
End Sub
Private Sub WaitForEmulator(ByVal EmulatorHandle As Integer)
Dim Oia As New AutOIATypeLibrary.AutOIA
Oia.SetConnectionByHandle(EmulatorHandle)
Oia.WaitForInputReady()
Oia.WaitForAppAvailable()
End Sub
I give thanks to a user named "khieyzer" on this message board for pointing our this clean and general-purpose solution.
Edit:
After a few weeks debugging and working through timing and resource release issues, this method now reads like:
Private Sub WaitForEmulator(ByRef NeededReset As Boolean)
Dim Oia As New AutOIA
Oia.SetConnectionByHandle(_Presentation.Handle)
Dim inhibit As InhibitReason = Oia.InputInhibited
If inhibit = InhibitReason.pcOtherInhibit Then
_Presentation.SendKeys("[reset]")
NeededReset = True
WaitForEmulator(NeededReset)
Marshal.ReleaseComObject(Oia)
Exit Sub
End If
If Not Oia.WaitForInputReady(6000) Then
If Oia.InputInhibited = InhibitReason.pcOtherInhibit Then
_Presentation.SendKeys("[reset]")
NeededReset = True
WaitForEmulator(NeededReset)
Marshal.ReleaseComObject(Oia)
Exit Sub
Else
Marshal.ReleaseComObject(Oia)
Throw New InvalidOperationException("The system has stopped responding.")
End If
End If
Oia.WaitForInputReady()
Oia.WaitForAppAvailable()
Marshal.ReleaseComObject(Oia)
End Sub

"Target Framework" compatibility

I am having a problem. The following code should output a single line describing my local instance of SQL. After lots of poking and prodding I have found that the code succeeds when it is compiled with the "Target Framework" set to v3.5, but it fails to return any instances when the "Target Framework" is set to anything higher. There is no error, exception, warning or other explanation. I know that it is not simply taking longer to find the instance because it reaches the final "Console.Readkey()" within .04 seconds when the target is v3.5.
I suppose what I really want to know is: How can I make this work without changing the Target Framework? I would rather not if I don't have to since I have written the rest of my project under the default (v4.5.2) and don't know what consequences might arise from doing so.
P.S. Bonus points if you can tell me why this doesn't work after v3.5.
Module Module1
Sub Main()
Dim datatable As DataTable = System.Data.Sql.SqlDataSourceEnumerator.Instance.GetDataSources()
For Each row As DataRow In datatable.Rows
For i As Integer = 0 To (datatable.Columns.Count - 1)
Console.Write(row.Item(i) & vbTab)
Next
Console.WriteLine()
Next
Console.ReadKey()
End Sub
End Module
Unfortunately, you appear to be experiencing this reported bug.
If it looks like the bug report matches your problem, consider voting to have it fixed.

String Addition / Subtraction Function

This was a possibility in VB Script by using a script control with the eval function. For example
ScriptControl1.Eval("(10+1.5)") 'Returns 11.5
Is there a way to do this in Vb.Net? The alternative would be to simply split up the string and verify if it is an addition or a subtracting and work from there. I was just wondering if there's already a built in function that I'm not yet aware of.
Thank you
I figured it out. I had to add a COM reference to Microsoft Script control like so:
Then I just declare my new variable as a Script Control.
Dim ScriptControl1 As New MSScriptControl.ScriptControl()
ScriptControl1.Eval("(10+1.5)") ' Returns 11.5
Note*
Make sure your Target CPU is at x86! I would receive a COMException if i had it set to Any CPU.

Need to call com function from VB.NET

If anyone wants to take a crack at this I'd really appreciate it. I'm writing a VB.NET app that will control a commercial backup product. One of the things I need to do is loop through all existing jobs and look at the source drive. I am able to do this in VBScript very simply like this:
Dim SP, BackupJob, volumes
Set SP = CreateObject("ShadowStor.ShadowProtect")
For Each Job In SP.Jobs
Set BackupJob = SP.Jobs.GetBackupJob(Job.Description)
BackupJob.GetVolumes volumes
For Each Volume in volumes
WScript.Echo volume
Next
Next
Set SP = Nothing
However nothing I try in VB.NET works. I'm pretty sure it has to do with the fact that the com functions are returning variant data types and arrays (specifically GetVolumes). I have tried using string arrays, object arrays, and even wrapping the return value in a VariantWrapper and I always get errors such as "not implemented" or "the parameter is incorrect." If anyone is bored and wants to write some code I'll gladly give it a shot and report back.
UPDATE:
This is odd. Look at this code:
Dim SP As Object = CreateObject("ShadowStor.ShadowProtect")
Dim gotJob As Object
Dim volumes() As Object
Try
For Each Job As Object In SP.Jobs
gotJob = SP.Jobs.GetBackupJob(Job.Description.ToString())
gotJob.GetVolumes(volumes)
For Each volume As Object In volumes
MsgBox(volume.ToString())
Next
Next
Catch ex As Exception
MsgBox(ex.Message)
End Try
This will display the volume from ONE job, then it crashes if there is more than one jobwith the error "invalid callee."
Locate ShadowStor.ShadowProtect in your registry in HKCR. It will have a CLSID which is a GUID. Search for that GUID, also in HKCR. You should find it in the CLSID section. Under that key you should find the actual dll path under InprocServer32.
Now if that component has an embedded TypeLib you should be able to add a reference to it in Visual Studio. (If you have OLE View installed you can inspect the type lib easily as well).
And if you cannot add a reference to the dll, there might be a seperate .tlb file, and you can find that by searching on the GUID present in the TypeLib value.
For anyone interested, the solution was to Dim volumes() As Object inside the loop and then set volumes = Nothing at the end of the loop so that it was re-created each time. If anyone can explain why this is so I would love to understand it.