Connecting to TM1 through VBA

Post Reply
geneticjim
Posts: 15
Joined: Fri May 22, 2015 3:44 pm
OLAP Product: TM1
Version: 10.2.2 FP7
Excel Version: 2016
Location: California

Connecting to TM1 through VBA

Post by geneticjim »

Hi all,

A teammate and I have been working on connecting to TM1 via VBA code and are stumped with one thing that I am hoping someone can help with? We've done quite a bit of searching online and in this forum but must be missing something in our understanding.

We have the code working but what we have done is comment out a little portion and hard-code encrypted credentials. The problem is, it appears that the hard-coded credentials (which we got by tracing through the VBA code) changes daily. It we uncomment what we have commented, then we get a pop up box asking for credentials and that is what we are trying to get around. The end goal is that I will run some Python script on my pc that points to this VBA. We will update our big spreadsheets with a lot of DBRW's in them using Python and VBA so when we get in in the morning, we will have updated spreadsheets.

We aren't consultants - we're just users trying to make things more efficient :)

Here is the full code:

Code: Select all

Option Explicit

'General
Private Declare Function TM1_API2HAN Lib "tm1.xll" () As Long
Private Declare Function TM1ValPoolCreate Lib "tm1api.dll" (ByVal hUser As Long) As Long
Private Declare Sub TM1ValPoolDestroy Lib "tm1api.dll" (ByVal hPool As LongPtr)
Private Declare Function TM1SystemGetServerConfig Lib "tm1api.dll" (ByVal hPool As Long, ByVal sServer As Long) As Long
Private Declare Function TM1SystemServerConnectWithCAMPassport Lib "tm1api.dll" (ByVal hPool As Long, ByVal sServer As Long, ByVal camArgs As Long) As Long
Private Declare Sub TM1SystemAdminHostSet Lib "tm1api.dll" (ByVal hUser As Long, ByVal AdminHosts As String)

'Value properties
Private Declare Function TM1ValTypeArray Lib "tm1api.dll" () As Long
Private Declare Function TM1ValTypeBool Lib "tm1api.dll" () As Long
Private Declare Function TM1ValTypeError Lib "tm1api.dll" () As Long
Private Declare Function TM1ValTypeIndex Lib "tm1api.dll" () As Long
Private Declare Function TM1ValTypeObject Lib "tm1api.dll" () As Long
Private Declare Function TM1ValTypeReal Lib "tm1api.dll" () As Long
Private Declare Function TM1ValTypeString Lib "tm1api.dll" () As Long

'Value Type
Private Declare Function TM1ValType Lib "tm1api.dll" (ByVal hUser As Long, ByVal value As Long) As Integer

'Value String
Private Declare Function TM1ValStringW Lib "tm1api.dll" (ByVal hPool As Long, ByRef InitString As Any, ByVal MaxSize As Long) As Long
Private Declare Function TM1ValStringMaxSize Lib "tm1api.dll" (ByVal hUser As Long, ByVal vString As Long) As Long
Private Declare Sub TM1ValStringGetW_VB Lib "tm1api.dll" (ByVal hUser As Long, ByVal vString As Long, ByRef res As Any, ByVal max As Long)
Private Declare Function TM1ValStringWMaxSize Lib "tm1api.dll" (ByVal hUser As Long, ByVal vString As Long) As Long

'Value Array
Private Declare Function TM1ValArray Lib "tm1api.dll" (ByVal hPool As Long, ByRef sArray() As Long, ByVal MaxSize As Long) As Long
Private Declare Function TM1ValArrayGet Lib "tm1api.dll" (ByVal hUser As Long, ByVal vArray As Long, ByVal index As Long) As Long
Private Declare Function TM1ValArrayMaxSize Lib "tm1api.dll" (ByVal hUser As Long, ByVal vArray As Long) As Long
Private Declare Sub TM1ValArraySet Lib "tm1api.dll" (ByVal vArray As Long, ByVal val As Long, ByVal index As Long)
Private Declare Sub TM1ValArraySetSize Lib "tm1api.dll" (ByVal vArray As Long, ByVal Size As Long)

'Errors
Private Declare Function TM1ErrorSystemServerClientPasswordExpired Lib "tm1api.dll" () As Long
Private Declare Function TM1ErrorSystemServerClientAlreadyConnected Lib "tm1api.dll" () As Long
Private Declare Function TM1ErrorSystemServerClientConnectFailed Lib "tm1api.dll" () As Long
Private Declare Function TM1ErrorSystemServerNotFound Lib "tm1api.dll" () As Long
Private Declare Function TM1ErrorSystemOutOfMemory Lib "tm1api.dll" () As Long
Private Declare Function TM1ErrorSystemServerIncompatibleVersion Lib "tm1api.dll" () As Long
Private Declare Function TM1ErrorSystemServerMaxConnectionsExceeded Lib "tm1api.dll" () As Long
Private Declare Function TM1ErrorClientMaximumPortsExceeded Lib "tm1api.dll" () As Long
Private Declare Function TM1ErrorSystemServerIsInShutdownMode Lib "tm1api.dll" () As Long
Private Declare Function TM1ErrorSystemServerClientExceedMaxLogonNumber Lib "tm1api.dll" () As Long
Private Declare Function TM1ErrorSystemServerIntegratedSecurityRequired Lib "tm1api.dll" () As Long
Private Declare Function TM1ErrorSystemServerIntegratedSecurityRefused Lib "tm1api.dll" () As Long
Private Declare Function TM1ErrorAuthorizedConnectionFailed Lib "tm1api.dll" () As Long
Private Declare Function TM1ValErrorCode Lib "tm1api.dll" (ByVal hUser As Long, ByVal vError As Long) As Long

'Windows
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (hpvDest As Any, hpvSource As Any, ByVal cbCopy As Long)
Declare Function LoadLibraryEx Lib "kernel32" Alias "LoadLibraryExA" (ByVal lpLibFileName As String, ByVal hFile As Long, ByVal dwFlags As Long) As Long

'TM1 CAM CONNECT BRIDGE
Private Declare Function GetCamPassport Lib "n_connect_cam_bridge.dll" (ByVal camuri As String, ByVal servername As String, ByVal passport As String, ByVal Size As Long) As Long

Public Const LOAD_WITH_ALTERED_SEARCH_PATH = &H8&
Dim libPath As String


Public Sub TEST_N_CONNECT_CAM()

    'these will be a parameter
    Dim sAdminHost As String
    Dim sServerName As String

    sAdminHost = "adminhosthere"
    sServerName = "serverinstancehere"

    N_CONNECT_CAM sAdminHost, sServerName

End Sub

Private Sub N_CONNECT_CAM(sAdminHost As String, sServerName As String)

    Dim hUser As Long
    Dim hPool As Long
    Dim hServerName As Long
    Dim hServer As Long
    Dim hServerConfig As Long
    Dim vArr As Long
    Dim vArrItem As Long
    Dim sCamURI As String
    Dim hCamArgs As Long
    Dim voParamArray(1) As Long
    Dim ret As Long
    Dim sPassportLong As String * 255
    Dim sPassport As String
    Dim hPassport As Long

    Dim rcCon As Long
    Dim rcApi As Long

    'ensure tm1p.xla is loaded
    If Not TM1PExists Then
        Debug.Print "TM1 Perspectives is not loaded"
        Exit Sub
    End If

    'loading libraries
    If libPath = "" Then
        Debug.Print "Error getting library path"
        GoTo Done
    End If
    rcApi = LoadLibraryEx(libPath & "\tm1api.dll", 0, LOAD_WITH_ALTERED_SEARCH_PATH)
    rcCon = LoadLibraryEx(libPath & "\n_connect_cam_bridge.dll", 0, LOAD_WITH_ALTERED_SEARCH_PATH)

    If rcApi = 0 Or rcCon = 0 Then
        Debug.Print "Error loading libraries"
        GoTo Done
    End If

    'getting user handle from perspectives
    hUser = TM1_API2HAN()
    'setting the admin host
    TM1SystemAdminHostSet hUser, sAdminHost

    'memory allocation
    hPool = TM1ValPoolCreate(hUser)

    'getting the configuration from the server
    hServerName = TM1ValString(hPool, sServerName, 0)
    hServerConfig = TM1SystemGetServerConfig(hPool, hServerName)

    If TM1ValType(hUser, hServerConfig) = TM1ValTypeError() Then
        Debug.Print "Error getting configuration from server " & sServerName
        GoTo Done
    End If

    vArr = TM1ValArrayGet(hUser, hServerConfig, 2)
    vArrItem = TM1ValArrayGet(hUser, vArr, 1)

    'getting the cam uri from the configuration
    If TM1ValType(hUser, vArrItem) = TM1ValTypeString() Then
        TM1ValStringGet_VB hUser, vArrItem, sCamURI, 0
    Else
        Debug.Print "Error getting CAMURI"
        GoTo Done
    End If

    'getting the passport from the cam environment
    sPassport = "MTsxMDf6ZmM0ozNkM2Qtr2rzYi1hYmE2LWZjuTE1OTM1NTg5ZTBltzhmOjE4NHE1NzHwHzI7Mtsz1zA7"
    
   ' ret = GetCamPassport(StrConv(sCamURI, vbUnicode), StrConv(sServerName, vbUnicode), sPassportLong, 255)
   ' If ret > 0 Then
   '     Debug.Print "Error getting passport"
   ' End If
   ' sPassportLong = Trim(Left(StrConv(sPassportLong, vbFromUnicode), 255))
   ' Dim i As Integer
   ' i = InStr(sPassportLong, Chr(0))
   ' If (i > 0) Then
   '     sPassport = Trim(Left(sPassportLong, i - 1))
   ' Else
   '     sPassport = Trim(sPassportLong)
   ' End If

   ' If sPassport = "" Then
   '     Debug.Print "Error getting passport (empty)"
   '     GoTo Done
   ' End If

    'connecting to TM1 using the passport
    hCamArgs = TM1ValArray(hPool, voParamArray, 1)
    If TM1ValType(hUser, hCamArgs) = TM1ValTypeError() Then
        Debug.Print "Error creating array for cam arguments"
        HandleServerConnectionError hUser, hServer
        GoTo Done
    End If

hPassport = TM1ValString(hPool, sPassport, 0)
TM1ValArraySet hCamArgs, hPassport, 1

hServer = TM1SystemServerConnectWithCAMPassport(hPool, hServerName, hCamArgs)

If TM1ValType(hUser, hServer) = TM1ValTypeError() Then
    Debug.Print "Error connecting to the server"
    GoTo Done
End If

Done:
If hPool > 0 Then
    TM1ValPoolDestroy (hPool)
End If

End Sub

Private Function TM1ValString(ByVal hPool As Long, ByVal InitString As String, ByVal MaxSize As Long) As Long
    Dim buf() As Byte

    buf = StringToByteArray(InitString, True, True)
    TM1ValString = TM1ValStringW(hPool, buf(0), MaxSize)

End Function

Private Function StringToByteArray(strInput As String, _
                                Optional bReturnAsUnicode As Boolean = True, _
                                Optional bAddNullTerminator As Boolean = False) As Byte()
    
    Dim bytBuffer() As Byte
    Dim lLenB As Long

    If bReturnAsUnicode Then        'UTF-16
        lLenB = LenB(strInput)
        If bAddNullTerminator Then
            ReDim bytBuffer(lLenB + 1)
        Else
            ReDim bytBuffer(lLenB - 1)
        End If
        CopyMemory bytBuffer(0), ByVal StrPtr(strInput), lLenB
    Else                            'ANSI
        bytBuffer = StrConv(strInput, vbFromUnicode)
    End If
    
    StringToByteArray = bytBuffer
    
End Function

Private Sub TM1ValStringGet_VB(ByVal hUser As Long, ByVal vString As Long, ByRef res As String, ByVal max As Integer)

       Dim buf() As Byte
       Dim cSize As Long
       cSize = 2 * TM1ValStringWMaxSize(hUser, vString)
        If cSize = 0 Then
            res = ""
        Else
    
            ReDim buf(cSize - 1)
            TM1ValStringGetW_VB hUser, vString, buf(0), cSize
            res = Left(buf, cSize)
             'remove the trailing null string and spaces
            Dim i As Integer
            i = InStr(res, Chr(0))
            If (i > 0) Then
                res = Trim(Left(res, i - 1))
            Else
                res = Trim(res)
            End If
            
       End If
End Sub

Private Sub HandleServerConnectionError(hUser As Long, hServer As Long)

Dim err1 As Long
Dim err2 As Long
Dim err3 As Long
Dim err4 As Long
Dim err5 As Long
Dim err6 As Long
Dim err7 As Long
Dim err8 As Long
Dim err9 As Long
Dim err10 As Long
Dim err11 As Long
Dim err12 As Long
Dim err13 As Long
Dim err14 As Long

Dim errorcode As Long

err1 = TM1ErrorSystemServerClientPasswordExpired()
err2 = TM1ErrorSystemServerClientAlreadyConnected()
err3 = TM1ErrorSystemServerClientConnectFailed()
err4 = TM1ErrorSystemServerClientConnectFailed()
err5 = TM1ErrorSystemServerNotFound()
err6 = TM1ErrorSystemOutOfMemory()
err7 = TM1ErrorSystemServerIncompatibleVersion()
err8 = TM1ErrorSystemServerMaxConnectionsExceeded()
err9 = TM1ErrorClientMaximumPortsExceeded()
err10 = TM1ErrorSystemServerIsInShutdownMode()
err11 = TM1ErrorSystemServerClientExceedMaxLogonNumber()
err12 = TM1ErrorSystemServerIntegratedSecurityRequired()
err13 = TM1ErrorSystemServerIntegratedSecurityRefused()
err14 = TM1ErrorAuthorizedConnectionFailed()

errorcode = TM1ValErrorCode(hUser, hServer)

If errorcode = err1 Then Debug.Print "TM1ErrorSystemServerClientPasswordExpired"
If errorcode = err2 Then Debug.Print "TM1ErrorSystemServerClientAlreadyConnected"
If errorcode = err3 Then Debug.Print "TM1ErrorSystemServerClientConnectFailed"
If errorcode = err4 Then Debug.Print "TM1ErrorSystemServerClientConnectFailed"
If errorcode = err5 Then Debug.Print "TM1ErrorSystemServerNotFound"
If errorcode = err6 Then Debug.Print "TM1ErrorSystemOutOfMemory"
If errorcode = err7 Then Debug.Print "TM1ErrorSystemServerIncompatibleVersion"
If errorcode = err8 Then Debug.Print "TM1ErrorSystemServerMaxConnectionsExceeded"
If errorcode = err9 Then Debug.Print "TM1ErrorClientMaximumPortsExceeded"
If errorcode = err10 Then Debug.Print "TM1ErrorSystemServerIsInShutdownMode"
If errorcode = err11 Then Debug.Print "TM1ErrorSystemServerClientExceedMaxLogonNumber"
If errorcode = err12 Then Debug.Print "TM1ErrorSystemServerIntegratedSecurityRequired"
If errorcode = err13 Then Debug.Print "TM1ErrorSystemServerIntegratedSecurityRefused"
If errorcode = err14 Then Debug.Print "TM1ErrorAuthorizedConnectionFailed"

End Sub

Public Function TM1PExists() As Boolean
    On Error GoTo error_handler
    TM1PExists = False
    Dim ad As AddIn
    ' First look in the addins collection
    For Each ad In Application.AddIns
        If ad.Name = "tm1p.xla" And ad.Installed = True Then
            TM1PExists = True
            libPath = ad.Path
            Exit Function
        End If
    Next
    
    Dim bk As Workbook
    ' next look in the workbooks collection (if opened as a normal book)
    For Each bk In Application.Workbooks
        If bk.Name = "tm1p.xla" Then
            TM1PExists = True
            libPath = bk.Path
            Exit Function
        End If
    Next
    ' finally ask for the book by name
    Set bk = Application.Workbooks("tm1p.xla")
    If Not bk Is Nothing Then
        TM1PExists = True
        libPath = bk.Path
        Exit Function
    End If

    Exit Function
error_handler:
    TM1PExists = False
    Err.Clear
End Function

The only part of this code that we got from an IBM site that we have changed is sAdminHost and sServerName to our specific names:

Code: Select all

Public Sub TEST_N_CONNECT_CAM()

    'these will be a parameter
    Dim sAdminHost As String
    Dim sServerName As String

    sAdminHost = "adminhosthere"
    sServerName = "serverinstancehere"

    N_CONNECT_CAM sAdminHost, sServerName

End Sub
And this is the section in which we commented out and simply hard-coded the "sPassport" to the encrypted that we got when tracing through the VBA. The sPassport is what changes on a daily basis from what we are seeing:

Code: Select all

    'getting the cam uri from the configuration
    If TM1ValType(hUser, vArrItem) = TM1ValTypeString() Then
        TM1ValStringGet_VB hUser, vArrItem, sCamURI, 0
    Else
        Debug.Print "Error getting CAMURI"
        GoTo Done
    End If

    'getting the passport from the cam environment
    sPassport = "MTsxMDf6ZmM0ozNkM2Qtr2rzYi1hYmE2LWZjuTE1OTM1NTg5ZTBltzhmOjE4NHE1NzHwHzI7Mtsz1zA7"
    
   ' ret = GetCamPassport(StrConv(sCamURI, vbUnicode), StrConv(sServerName, vbUnicode), sPassportLong, 255)
   ' If ret > 0 Then
   '     Debug.Print "Error getting passport"
   ' End If
   ' sPassportLong = Trim(Left(StrConv(sPassportLong, vbFromUnicode), 255))
   ' Dim i As Integer
   ' i = InStr(sPassportLong, Chr(0))
   ' If (i > 0) Then
   '     sPassport = Trim(Left(sPassportLong, i - 1))
   ' Else
   '     sPassport = Trim(sPassportLong)
   ' End If

   ' If sPassport = "" Then
   '     Debug.Print "Error getting passport (empty)"
   '     GoTo Done
   ' End If

    'connecting to TM1 using the passport
    hCamArgs = TM1ValArray(hPool, voParamArray, 1)
    If TM1ValType(hUser, hCamArgs) = TM1ValTypeError() Then
        Debug.Print "Error creating array for cam arguments"
        HandleServerConnectionError hUser, hServer
        GoTo Done
    End If

I hope this makes sense. Thanks so much for any insight you can provide!
tomok
MVP
Posts: 2836
Joined: Tue Feb 16, 2010 2:39 pm
OLAP Product: TM1, Palo
Version: Beginning of time thru 10.2
Excel Version: 2003-2007-2010-2013
Location: Atlanta, GA
Contact:

Re: Connecting to TM1 through VBA

Post by tomok »

All this code is using the API to connect to TM1. If your aim is just to open a spreadsheet, update values in the DBRW formulas, and re-save, why in the world would you use the API. You are aware that TM1 has a library of macro functions for use in Excel that will assist you in getting this done? There shouldn't be any API coding needed.
Tom O'Kelley - Manager Finance Systems
American Tower
http://www.onlinecourtreservations.com/
geneticjim
Posts: 15
Joined: Fri May 22, 2015 3:44 pm
OLAP Product: TM1
Version: 10.2.2 FP7
Excel Version: 2016
Location: California

Re: Connecting to TM1 through VBA

Post by geneticjim »

HI Tomak - actually, we aren't familiar with how to do it without API. We are very much open to it but just haven't been able to figure it out without. The only stumbling block we have is the log in. We can't have a pop up box when it's done automatically, thus, need to pass along credentials or figure it out some other way. We have security mode 5 for our TM1 security. The page I was referring to is: http://www-01.ibm.com/support/docview.w ... wg21959177

This was the most helpful one we found and actually got it to work for us. The only draw back is that it has a pop up box asking for credentials. I totally agree that it sure as heck is complicated and if you know of an easier way, I'd love to hear it! :)

Thanks for the input!
BrianL
MVP
Posts: 264
Joined: Mon Nov 03, 2014 8:23 pm
OLAP Product: TM1
Version: 9.5.2 10.1 10.2 PA2
Excel Version: 2016

Re: Connecting to TM1 through VBA

Post by BrianL »

First, I agree with Tomok. You'll probably be better served by the VBA macros installed with the perspectives or cafe clients.

Since you are trying to hardcode credentials into your source, have you looked at using TM1SystemServerConnectWithCAMNamespace instead? The CAM passport can be thought of as an authenticated session id that is temporary by nature. To reliably use CAM passport you really do need to obtain the passport value from BI every time you want to pass it in.

I've heard there may be some options to automatically obtain a passport through various scripting measures when SSO is enabled, but you don't mention if that applies to your situation. I thought there was some talk on this forum about this in the context of tm1runti.exe, but I can't dig it up right now. However google led me to this blog post that's missing the specific code required: http://ykud.com/blog/cognos/tm1-cognos/ ... nglesignon
geneticjim
Posts: 15
Joined: Fri May 22, 2015 3:44 pm
OLAP Product: TM1
Version: 10.2.2 FP7
Excel Version: 2016
Location: California

Re: Connecting to TM1 through VBA

Post by geneticjim »

Thanks Brian.

Do you know if you can use TM1SystemServerConnectWithCAMNamespace with security mode 5?

We had tried that but when it got to it, it didn't error, it didn't do anything.

We were using API then, too, because it seemed that every time we looked, we were told we needed to call TM1APIInitialize. The different places we looked all said we had to call that before any TM1 VBA functions were used. Therefore, a bit confused as to how you guys state it can be used with out API. Simpler is better, but it seems that with security mode 5, things aren't as easy.

Thanks again!
tomok
MVP
Posts: 2836
Joined: Tue Feb 16, 2010 2:39 pm
OLAP Product: TM1, Palo
Version: Beginning of time thru 10.2
Excel Version: 2003-2007-2010-2013
Location: Atlanta, GA
Contact:

Re: Connecting to TM1 through VBA

Post by tomok »

geneticjim wrote:Therefore, a bit confused as to how you guys state it can be used with out API. Simpler is better, but it seems that with security mode 5, things aren't as easy.
I don't think anyone told you that you could use Visual Basic API functions without the API. The code you showed us is Visual Basic, using Visual Basic functions of the API. Yes, it can be used as VBA but they aren't necessarily VBA functions, They'll work in just a pure Visual Basic application OR inside VBA. What I mentioned to you were the macro functions that you can call from inside Excel VBA only when you have Perspectives loaded. The reason they are very attractive is because they are very easy to use and you can do things with them in just one function that can take you numerous functions inside the API. TM1SystemServerConnectWithCAMNamespace is an API function call, not part of the TM1 Macro language suite.
Tom O'Kelley - Manager Finance Systems
American Tower
http://www.onlinecourtreservations.com/
lotsaram
MVP
Posts: 3703
Joined: Fri Mar 13, 2009 11:14 am
OLAP Product: TableManager1
Version: PA 2.0.x
Excel Version: Office 365
Location: Switzerland

Re: Connecting to TM1 through VBA

Post by lotsaram »

tomok wrote: I don't think anyone told you that you could use Visual Basic API functions without the API. The code you showed us is Visual Basic, using Visual Basic functions of the API. Yes, it can be used as VBA but they aren't necessarily VBA functions, They'll work in just a pure Visual Basic application OR inside VBA. What I mentioned to you were the macro functions that you can call from inside Excel VBA only when you have Perspectives loaded. The reason they are very attractive is because they are very easy to use and you can do things with them in just one function that can take you numerous functions inside the API. TM1SystemServerConnectWithCAMNamespace is an API function call, not part of the TM1 Macro language suite.
Tom I think you've missed the point. I believe that the OP has every intention of using regular TM1 macro functions in Excel. The issue that they have is with logging in as the N_Connect macro doesn't support CAM. This is why they are having to muck around with TM1SystemServerConnectWithCAMNamespace and the API, it's just to log on. Once the connection is established then it would all be regular TM1RECALC1, etc., etc.
Please place all requests for help in a public thread. I will not answer PMs requesting assistance.
geneticjim
Posts: 15
Joined: Fri May 22, 2015 3:44 pm
OLAP Product: TM1
Version: 10.2.2 FP7
Excel Version: 2016
Location: California

Re: Connecting to TM1 through VBA

Post by geneticjim »

Thanks Lotsaram. That is correct. All of the code was directly from IBM and is VBA sitting in an Excel module. It works just as it is, however, since we are automating, we need to pass along credentials without the pop up box. The only way we know how to do this is as I showed by commenting out the original and then hard-coding the encrypted credentials we obtain from tracing through the macro. We have to do this once a day, however, and that isn't very automated. Thus, we are trying to figure out how to just pass along our credentials without having to enter them or grab the encrypted version once a day.

We want to have this on a schedule and done over night. Therefore, we can't have a pop-up box.

Thanks again.
Post Reply