How to programmatically check that SAP Business One client is installed - sapb1

I'm developing a WinForm application that connect to SAP Business One using the SAP Business One SDK.
Is there a recommended method for check if the SAP Business One client is installed?
Any help greatly appreciated.
James O'Doherty

The SAP Business One client can be detected using the following code.
Public Function isSapBusinesOneClientInstalled() As Boolean
Try
'SAP Business One Application
Dim type As Type = Type.GetTypeFromCLSID(New Guid("632F4591-AA62-4219-8FB6-22BCF5F60088"))
Dim obj As Object = Activator.CreateInstance(type)
Marshal.ReleaseComObject(obj)
Return True
Catch ex As COMException
Return False
End Try
End Function

Related

How to recover from loss of connection to a (locally hosted) DCOM object?

(I'm not sure if this is would be better suited for code review)
I'm using a (closed source) library that maintains a DCOM connection internally, this DCOM connection goes to a service running on the local machine. This service is stopped and started quite frequently by the user (not through our application).
Unfortunately it looks like DCOM related exceptions bubble up to our application.
There doesn't seem to be any way given in the library to determine if the connection to the service is still valid, right now the only way to know we've lost connection to the service to try to do something and have it throw an exception (the type of which varies significantly).
My best attempt to try to make this a little more manageable is to separate out exceptions that are usually caused by DCOM from exceptions that are caused by configuration problems....
Here's a sample of what the code ended up looking like:
Private Sub Initialize()
Dim ParametersToDComObject As Integer
Try
Dim HRESULT = ClassThatUsesDCOMInstance.InitDCOMObject(ParametersToDComObject)
Console.WriteLine("Connected to service through DCOM.")
Catch Ex As Exception
If (IsLikelyCausedByConnectionLoss(Ex)) Then
'in this case, throw to make sure that we don't try to continue assuming that the connection went through
'I'm not sure what type of exception would be best to throw here,
'this would occur if the service is not running; the calling function
'would notify the user that there's a problem
Throw New InvalidOperationException(Ex.Message, Ex)
Else
Throw
End If
End Try
End Sub
Private Sub ReadDataFromClass(EntryName As String)
Try
Dim HRESULT As Integer = ClassThatUsesDCOMInstance.ReadEntry(EntryName)
Catch Ex As Exception
If (IsLikelyCausedByConnectionLoss(Ex)) Then
'If the service is stopped, from the application's perspective every method in
'this class suddenly starts failing, and it won't start working again until a new instance
'of the class that uses DCOM is made.
'
'So the sub below attempts to make sure that our application knows that the methods in this class
'won't work anymore... (I usually set ClassThatUsesDCOMInstance to Nothing after cleaning up)
'
'Sometimes I just have to "reconnect", but the tricky thing is, the only recovery option may be to
'(re)start the service that's providing the DCOM object; it seems best to notify the
'user in this case since in most cases the user knowlingly stopped the service, it's
'normal to stop the service to configure it
CleanupAfterConnectionLoss()
Throw New InvalidOperationException(Ex.Message, Ex)
End If
If (TypeOf (Ex) Is TheLibraryDefinedThisException) Then
'let's say that the library throws this if we try to read an entry that doesn't exist (e.g., if the configuration has changed)
'we can't fix the problem automatically, but we can notify the user that something has changed external to this program, so they can
'either fix the service or edit their configuration for our application
Console.WriteLine("Entry " & EntryName & " could not be read " & Ex.Message & " check that the entry is defined on the service")
Else
Throw
End If
End Try
End Sub
Private Function IsLikelyCausedByConnectionLoss(ThrownException As Exception) As Boolean
'I'm thinking it's best to put this in a separate function because development has gone like this:
'
' 1. the provided documentation does not give any information about what methods will throw what kinds of exceptions
' All functions return **HRESULTs**, I assumed that there weren't going to be /any/ exceptions
'
' It turns out the HRESULTs cover certain errors and exceptions cover certain errors, with some overlap...
'
' 2. I determined experimentally during testing that it throws a <Library Defined Exception> if I give it an invalid entry name, okay, I can work with that
'
' 3. I determined experimentally that if I stop the service while we're connected, all functions throw a COMException, not convenient but ok.
'
' 4. Weeks later, I figure out that it also occasionally throws InvalidComObjectException if the service is stopped under the same situation
'
' 5. Weeks later, I find out that it occasionally throws ApplicationExceptions under the same situation
'
' 6. Weeks later, I find out that certain functions will additionally throw an InvalidCastException
'
' note that there's no change in input to the library to cause these different exceptions, and the only cause
' is the loss of connection to the DCOM server
'
' in the last code setup I had to manually update every try/catch block that had anything to do with this library every time
' I found something new...
If (TypeOf (ThrownException) Is InvalidCastException) Then
'I really don't like catching this, but I get this if the COM object fails to bind to the interface,
'which occasionally happens if the service isn't there or isn't set up right
Return True
ElseIf (TypeOf (ThrownException) Is ApplicationException) Then
'I really don't like catching this either, same reason as above
Return True
'I don't believe this is an exhaustive list of every exception that can be thrown due to loss of DCOM connection,
'please let me know if you know of more exceptions that can be thrown...
ElseIf (TypeOf (ThrownException) Is Runtime.InteropServices.COMException) Then
Return True
ElseIf (TypeOf (ThrownException) Is Runtime.InteropServices.InvalidComObjectException) Then
Return True
Else
Return False
End If
End Function
I feel like this is a poor solution for the problem, though. It seems better than just swallowing all exceptions but not by much.
Is there any exception that encompasses all possible DCOM errors?
Alternatively, is there some other way to detect that a connection to a service has been lost?
EDIT:
Is this situation typical for DCOM in .NET?

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

VB.NET: How to terminate a process that is already running outside of the current project

Hello once again and I need some help. I am developing an automatic updating component to update one of my other apps in vb.net. The problem is that IFF an update is available, the update app has to be able to "kill" the app that is being updated. Note that the update app and program app are 2 separate projects and have no connection what so ever. I am wondering if there is a way to kill that program app's process from the update app. I have looked at several examples, but they start and stop the process in the code. I want to be able to search for a process by name and then kill it(NOT START AS IT IS ALREADY RUNNING).
Any help is appreciated!
(NOTE: Those who redirect to another link will not get best answer)
I have done something similar to this before. If I am understanding your requirements correctly then you need to use the GetProcess in the System.Diagnostic namespace. There is more information about it here: http://msdn.microsoft.com/en-us/library/system.diagnostics.process.kill.aspx.
Here you can use this function and pass the process name as a parameter.
Public Function killProcess(ByVal procName As String) As Boolean
Try
Dim proc = Process.GetProcessesByName(procName)
For i As Integer = 0 To proc.Count - 1
proc(i).Kill()
Next i
Return True
Catch ex As Exception
Return False
End Try
End Function
This function takes the process name such as "chrome" and checks for multiple instances and kills each of them.
You can call this function as bellow
killProcess("Process Name")

Dynamic programming in VB

We develop applications for SAP using their SDK. SAP provides a SDK for changing and handling events occuring in the user interface.
For example, with this SDK we can catch a click on a button and do something on the click. This programming can be done either VB or C#.
This can also be used to create new fields on the pre-existing form. We have developed a specific application which allows users to store the definition required for new field in a database table and the fields are created at the run time.
So far, this is good. What we require now is that the user should be able to store the validation code for the field in the database and the same should be executed on the run time.
Following is an example of such an event:
Private Sub SBO_Application_ItemEvent(ByVal FormUID As String, ByRef pVal As SAPbouiCOM.ItemEvent, ByRef BubbleEvent As Boolean) Handles SBO_Application.ItemEvent
Dim oForm As SAPbouiCOM.Form
If pVal.FormTypeEx = "ACC_QPLAN" Then
If pVal.EventType = SAPbouiCOM.BoEventTypes.et_LOST_FOCUS And pVal.BeforeAction = False Then
oProdRec.ItemPressEvent(pVal)
End If
End If
End Sub
Public Sub ItemPressEvent(ByRef pVal As SAPbouiCOM.ItemEvent)
Dim oForm As SAPbouiCOM.Form
oForm = oSuyash.SBO_Application.Forms.GetForm(pVal.FormTypeEx, pVal.FormTypeCount)
If pVal.EventType = SAPbouiCOM.BoEventTypes.et_LOST_FOCUS And pVal.BeforeAction = False Then
If pVal.ItemUID = "AC_TXT5" Then
Dim CardCode, ItemCode As String
ItemCode = oForm.Items.Item("AC_TXT2").Specific.Value
CardCode = oForm.Items.Item("AC_TXT0").Specific.Value
UpdateQty(oForm, CardCode, ItemCode)
End If
End If
End Sub
So, what we need in this case is to store the code given in the ItemPressEvent in a database, and execute this in runtime.
I know this is not straight forward thing. But I presume there must be some ways of getting these kind of things done.
The SDK is made up of COM components.
Thanks & Regards,
Rahul Jain
I've not done this myself, but I think you're going to have to actually use the Systems.Runtime.CompilerServices functions to dynamically compile an assembly and then link it in. Another solution if you are using SQL Server might be to take advantage of the fact that you can write C# or VB.NET code in stored procedures. That might be a way.
Dim sqlstring1 As String = "Blah Blah Blah SQL here"
Dim Rs SAPbobsCOM.Recordset
Rs = GetDIConnection.GetBusinessObject(SAPbobsCOM.BoObjectTypes.BoRecordset)
rs.doquery(SqlString1)
You can create the code dynamically and compile it..
Have some simple interfaces to call the validation code and in all your dynamic code, implement the interface(s). This way, you can load assembly dynamically and get the class as an interface and use that interface directly..

Detecting Installed Excel Version (and Service Packs)

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)