• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1567
  • Last Modified:

prompt for new linked table manager location

Hello,

I'm pretty stumped on this.
I have two db (supervisor and client)
some of the tables have to be synced between them.

In the past I used a mysql table on our host, but this time I cannot do that.
Syncing will have to happen through a tumbdrive (usb key)

Each db has linked tables to a db called sync.
I have macros in each db to take or give information to  the sync db.

However, I cannot control whether or not the drive letter for the sync db will always be the same, or the location the same, and don't know how to be able to have the 'linked table manager' find it.

Does anybody know a way to code a "find sync db".

Basically, I'm hoping to have the same type of popup window as a 'save file' or 'open file' window.
Have my user point to the 'sync db'
Have linked tables point to that automatically,
and run the rest of the macro or code.

I've tried a couple on the internet (http://www.mvps.org/access/tables/tbl0009.htm)
but couldn't get that to fit my needs.

Any help would be greatly appreciated.
Thanks.





0
jpostuma
Asked:
jpostuma
  • 4
  • 3
1 Solution
 
ducky801Commented:

I've attached some code for you that i personally use (because i'm in a situation quite like yours where i need to point my database to different environments that hold the same tables with different data.  Please see the code snippet.
 Note, i've commented out the routines DeleteLinkedTables, DeleteTempTables, and DeleteRelationships so you don't inadvertantly do something you don't want to in your database.  
In a nutshell this code will (after you un-comment the routines mentioned):
delete all realationships in your local database
delete all linked tables in your local database
delete any table prefixed with "TempTable_" (i use this to clean up after myself)
ask you which tables you'd like to import (defaults to dbf, but can be adapted to your needs) (separate table names by semicolon)
links in all of those tables

Option Compare Database
Public ShortLinkTables As String 'this string holds names of commonly used tables.  we use it to do a fast import of just these
 
 
Public Sub LinkDBF(Optional boo_SilentMode As Boolean = False, Optional str_SilentModePath As String)
'***********************************************************************************
'***Created by Adam Reese                                                        ***
'***01/08/2008                                                                   ***
'***This routine iterates through a path supplied by the user and links all .dbf ***
'***files into the local database which can then be queried upon                 ***
'***********************************************************************************
 
Dim DBFName As String         'The name of the current .dbf file
Dim CorePath As String        'the path that the user supplies to be searched.
Dim LinkedTableName As String 'this is the same as DBFName without ".dbf", the destination table name in the .mdb
Dim DBFCount As Integer       'used to track how many tables ended up being imported
Dim SkippedCount As Integer   'used to track how many tables were skipped over (because the table didn't exist in the path)
Dim Stime As Date             'start time
Dim Etime As Date             'end time
Dim TotTime                   'total run time
Dim UseShortLink As Boolean 'tels the program if we are doing a short link or the standard
 
ShortLinkTables = "Table1; Table2; Table3;"
 
 
'delete all existing relationships in the local .mdb
'if we didn't do this the tables wouldn't be allowed to be deleted later on in this sub
Call DeleteRelationships
 
'delete all linked tables (not non-linked tables)
Call DeleteLinkedTables
 
 
 
    'if we are in silent mode and have a variable in str_SilentModePath then use that variable
    If str_SilentModePath <> "" Then
    CorePath = str_SilentModePath
    End If
 
 
'get the core path from the user if null 
If boo_SilentMode = True And str_SilentModePath <> "" Then 'we have all info we need
Else 'we still need to get some data from the user
    If CorePath = "" Then
    CorePath = InputBox("What is the core path?", "Input Required", "R:\")
    End If
End If
 
    'if corepath doesn't end in "\", make it so
    If Right(CorePath, 1) <> "\" Then 'it doesn't end in "\"
        CorePath = CorePath & "\"
    End If
 
'determine if this is a short link or a standard link
If boo_SilentMode = False Then 'we arent in silent mode so get input
    test = MsgBox("Do you want to use  the short link method?" & Chr(10) & Chr(10) & "Short link will only import commonly used tables (as well as those you specify) instead of every one.", vbYesNo)
Else 'we are in silentmode so move on without user input
    test = vbYes
End If
 
If test = vbYes Then
    UseShortLink = True
End If
 
'if we are using short link and not in silentmode, show the user the list and get any other tables they may care to import
If UseShortLink = True And boo_SilentMode = False Then 'show the list
ShortLinkTables = InputBox("These are the tables that will be imported by the program." & Chr(10) & "You can add more table names, seperated by semicolons.", "Input Required", ShortLinkTables)
End If
 
'replace all of the " "'s in the string cause it will be easier to parse that way
ShortLinkTables = Replace(ShortLinkTables, " ", "")
 
 
'add a ";" to the end of it if needed (string needs to end in semicolon to successfully become "" at end of parsing
If Right(ShortLinkTables, 1) <> ";" Then
ShortLinkTables = ShortLinkTables & ";"
End If
 
 
'start the timer
Stime = Format(Now(), "HH:mm:ss")
 
 
 
'import dbf files
If UseShortLink = False Then 'we are doing the complete searc so use this loop
    DBFName = Dir(CorePath & "*.dbf") 'get the first .dbf file in the core path
    Do Until DBFName = "" 'go until "" is returned (happens when there are no files left to look at)
        
        'determine LinkedTableName
        LinkedTableName = Left(DBFName, Len(DBFName) - 4) ' minus 4 chars for ".dbf"
            
            'try to delete the table in case it already exists (other wise the table will be linked with a '1' at the end)
            On Error Resume Next 'turn off errors so we don't get one if the table doesn't exist
            DoCmd.DeleteObject acTable, LinkedTableName
            On Error GoTo 0 'turn errors back on
                
                'link the table into the local .mdb
                On Error Resume Next
                DoCmd.TransferDatabase acLink, "dBase 5.0", CorePath, acTable, DBFName, LinkedTableName, False
                On Error GoTo 0
                    
                
                    'incremebt dbfcount
                    DBFCount = DBFCount + 1
    DBFName = Dir 'look for the next .dbf file
    Loop
Else 'we are using short link so use this loop
    Do Until ShortLinkTables = ""
    
        'parse out next table name
        DBFName = Left(ShortLinkTables, InStr(1, ShortLinkTables, ";"))
            'remove it from shortlinktables string
            ShortLinkTables = Right(ShortLinkTables, Len(ShortLinkTables) - Len(DBFName))
                'remove the ";" from the end of DBFName
                DBFName = Replace(DBFName, ";", "")
                    'append extension
                    If Right(DBFName, 4) <> ".dbf" Then 'there is no extension
                    DBFName = DBFName & ".dbf" 'add extension
                    End If
                        'test to see if table exists in core path
                        If Dir(CorePath & DBFName) = "" Then 'the table doesn't exist in the path
                        MsgBox ("Sorry, but the table " & DBFName & " doesn't exist in the path " & CorePath & ". " & Chr(10) & "Skipping over.")
                        SkippedCount = SkippedCount + 1
                        Else 'it exists in the path so link it in
                        
                                'determine LinkedTableName
                                LinkedTableName = Left(DBFName, Len(DBFName) - 4) ' minus 4 chars for ".dbf"
            
                                'try to delete the table in case it already exists (other wise the table will be linked with a '1' at the end)
                                On Error Resume Next 'turn off errors so we don't get one if the table doesn't exist
                                DoCmd.DeleteObject acTable, LinkedTableName
                                On Error GoTo 0 'turn errors back on
                                    
                                    'link the table into the local .mdb
                                    On Error Resume Next
                                    DoCmd.TransferDatabase acLink, "dBase 5.0", CorePath, acTable, DBFName, LinkedTableName, False
                                    On Error GoTo 0
                                    
                    
                                        'incremebt dbfcount
                                        DBFCount = DBFCount + 1
                        End If
 
     Loop
End If
'at this point we have gone thru each .dbf file and there are no more
'stop the timer
Etime = Format(Now(), "HH:mm:ss")
'calc tottime
TotTime = Format((Etime - Stime), "HH:mm:ss")
 
'report back
If boo_SilentMode = False Then 'Report back
    If DBFCount <> 0 Then
        MsgBox ("Success!!!" & Chr(10) & Chr(10) & "Core path was: " & CorePath & Chr(10) & "Procedure started at: " & Stime & Chr(10) & "Procedure ended at:  " & Etime & Chr(10) & "Total Run time:           " & TotTime & Chr(10) & Chr(10) & DBFCount & " .dbf files were linked." & Chr(10) & SkippedCount & " .dbf files were skipped.")
        Else 'there were 0 files
        MsgBox ("No .dbf files were found in " & CorePath & Chr(10) & "Or" & Chr(10) & CorePath & " is not mapped correctly.")
    End If
Else 'we were using silentmode so don't report back
End If
 
 
End Sub
 
Sub DeleteRelationships()
'this code was taken from the internet somewhere.
'it is called by the LinkDBF routine
'this code deletes all relationships in the current databse (WITHOUT Confirmation!)
    'Dim db As Database      ' Current DB
    'Dim rex As Relations    ' Relations of currentDB.
    'Dim rel As Relation     ' Relationship being deleted.
    'Dim iKt As Integer      ' Count of relations deleted.
 
	
    'Set db = CurrentDb()
    'Set rex = db.Relations
    'iKt = rex.Count
    'Do While rex.Count > 0
    '    Debug.Print rex(0).Name
    '    rex.Delete rex(0).Name
    'Loop
    'DeleteAllRelationships = iKt & " relationship(s) deleted"
 
End Sub
 
Sub DeleteLinkedTables()
    'this code was taken from the internet somewhere then adapted to suit my needs
    'it will determine if a table is linked or not by using the dir
    'function to test for its existence in the core path
    'is there a better way to tell if the table is TRULY linked or not?  probably so
    
    'Dim obj As AccessObject, dbs As Object
    'Dim TableName As String
    'Set dbs = Application.CurrentData
    
    ' Check each object of the AllTables collection
   ' For Each obj In dbs.AllTables
        ' When you find a table, display its name
   '     TableName = obj.Name
   '     If Left(TableName, 4) <> "Msys" And GetLinkedDBName(TableName) <> "" And TableName <> "BNF01" Then  'if not a system table and name doesn't exist in corepath then consider it linked
        DoCmd.DeleteObject acTable, TableName
    '    End If
    'Next obj
 
 
End Sub
 
Sub DeleteTempTables()
   
    'Dim obj As AccessObject, dbs As Object
    'Dim TableName As String
    'Set dbs = Application.CurrentData
    
    ' Check each object of the AllTables collection
    'For Each obj In dbs.AllTables
    '    ' When you find a table, display its name
     '   TableName = obj.Name
     '   If Left(TableName, 10) = "TempTable_" Then  'if not a system table and name doesn't exist in corepath then consider it linked
     '   DoCmd.DeleteObject acTable, TableName
     '   End If
    'Next obj
 
 
End Sub
'===============================================================
' The GetLinkedDBName() function requires the name of a
' linked Microsoft Access table, in quotation marks, as an
' argument. The function returns the full path of the originating
' database if successful, or returns "" if unsuccessful.
'===============================================================
 
Function GetLinkedDBName(TableName As String)
   Dim db As DAO.Database, Ret
   On Error GoTo DBNameErr
   Set db = CurrentDb()
   Ret = db.TableDefs(TableName).Connect
   GetLinkedDBName = Right(Ret, Len(Ret) - (InStr _
      (1, Ret, "DATABASE=") + 8))
   Exit Function
DBNameErr:
   GetLinkedDBName = ""
End Function

Open in new window

0
 
Chuck WoodCommented:
I think this will do what you want. The first procedure (LinkTable) should be used as a template for finding your sync DB and linking a table. The rest of the code should be put into a separate module named basFileOpenSave.

Please let me know if you have any questions.

'=======================================================
' USE THIS PROCEDURE AS A TEMPLATE IN YOUR NORMAL CODE
'=======================================================
Public Sub LinkTable()
On Error GoTo HandleError
    '=== get the path and name to the Access file
    '===================================================
    '  requires the module: basFileOpenSave
    '===================================================
    ' set the filter
    Dim strFilter As String
    ' use only the filter items you need
    strFilter = ahtAddFilterItem(strFilter, "Access files (*.mdb)", "*.mdb")
    strFilter = ahtAddFilterItem(strFilter, "All files (*.*)", "*.*")
    ' select filter 1 (Access file) when the dialog opens
    Dim intFilterIndex As Integer
    intFilterIndex = 1
    ' set up the initial directory
    Dim strInitDir As String
    ' set the initial directory to the user's desktop
    strInitDir = Environ("USERPROFILE") & "\Desktop"  '<=== set the initial directory here
    ' set up the default extension
    Dim strDefaultExt As String
    strDefaultExt = "mdb"
    ' set the Access file name
    Dim strFileName As String
    strFileName = "sync.mdb"  '<=== set the name of your DB here
    ' open the file save dialog box
    Dim strDB As String
    strDB = ahtCommonFileOpenSave(, strInitDir, strFilter, intFilterIndex, _
        strDefaultExt, strFileName, , , True)
    '=== link to the table in the Access file
    '===================================================
    '  requires a reference to:
    '       Microsoft ADO Ext. 6.0 for DDL and Security
    '===================================================
    Dim strSQL As String
    Dim tbl As ADOX.Table
    Dim intI As Integer
    Dim cat As ADOX.Catalog
    Set cat = New ADOX.Catalog
    Set tbl = New ADOX.Table
    ' if the database path does not exist,
    If Dir(strDB) = "" Then
        ' warn the user and exit
        MsgBox "This database path does not exist:" & _
            vbNewLine & vbNewLine & strDB, vbCritical, "Path Invalid"
        GoTo ExitHere
    End If
    ' set a connection to the database
    cat.ActiveConnection = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & strDB & ";"
    ' set the table name
    Dim strTableName As String
    strTableName = "MyTable"  '<=== set the name of your table here
    ' loop through the tables,
    For Each tbl In cat.Tables
        ' if this is the table we are linking,
        If tbl.Name = strTableName Then
            ' link the table
            DoCmd.TransferDatabase acLink, "Microsoft Access", _
                strDB, acTable, tbl.Name, tbl.Name, False
        End If
    Next tbl
    ' clean up
    Set tbl = Nothing
    Set cat = Nothing
ExitHere:
    Exit Sub
HandleError:
    Select Case err.Number
        Case 3011
            Resume Next
        Case Else
            MsgBox "Error:" & err.Number & vbNewLine & _
                err.Description & vbNewLine & _
                "In:LinkTable"
            Resume ExitHere
    End Select
End Sub
 
'===================================================
' PLACE THIS IN A MODULE NAMED basFileOpenSave.bas
'===================================================
Option Compare Database
Option Explicit
'This code was originally written by Ken Getz.
'It is not to be altered or distributed,
'except as part of an application.
'You are free to use it in any application,
'provided the copyright notice is left unchanged.
'
' Code courtesy of:
' Microsoft Access 95 How-To
' Ken Getz and Paul Litwin
' Waite Group Press, 1996
 
Type tagOPENFILENAME
    lStructSize As Long
    hwndOwner As Long
    hInstance As Long
    strFilter As String
    strCustomFilter As String
    nMaxCustFilter As Long
    nFilterIndex As Long
    strFile As String
    nMaxFile As Long
    strFileTitle As String
    nMaxFileTitle As Long
    strInitialDir As String
    strTitle As String
    Flags As Long
    nFileOffset As Integer
    nFileExtension As Integer
    strDefExt As String
    lCustData As Long
    lpfnHook As Long
    lpTemplateName As String
End Type
 
Declare Function aht_apiGetOpenFileName Lib "comdlg32.dll" _
    Alias "GetOpenFileNameA" (OFN As tagOPENFILENAME) As Boolean
 
Declare Function aht_apiGetSaveFileName Lib "comdlg32.dll" _
    Alias "GetSaveFileNameA" (OFN As tagOPENFILENAME) As Boolean
Declare Function CommDlgExtendedError Lib "comdlg32.dll" () As Long
 
Global Const ahtOFN_READONLY = &H1
Global Const ahtOFN_OVERWRITEPROMPT = &H2
Global Const ahtOFN_HIDEREADONLY = &H4
Global Const ahtOFN_NOCHANGEDIR = &H8
Global Const ahtOFN_SHOWHELP = &H10
' You won't use these.
'Global Const ahtOFN_ENABLEHOOK = &H20
'Global Const ahtOFN_ENABLETEMPLATE = &H40
'Global Const ahtOFN_ENABLETEMPLATEHANDLE = &H80
Global Const ahtOFN_NOVALIDATE = &H100
Global Const ahtOFN_ALLOWMULTISELECT = &H200
Global Const ahtOFN_EXTENSIONDIFFERENT = &H400
Global Const ahtOFN_PATHMUSTEXIST = &H800
Global Const ahtOFN_FILEMUSTEXIST = &H1000
Global Const ahtOFN_CREATEPROMPT = &H2000
Global Const ahtOFN_SHAREAWARE = &H4000
Global Const ahtOFN_NOREADONLYRETURN = &H8000
Global Const ahtOFN_NOTESTFILECREATE = &H10000
Global Const ahtOFN_NONETWORKBUTTON = &H20000
Global Const ahtOFN_NOLONGNAMES = &H40000
' New for Windows 95
Global Const ahtOFN_EXPLORER = &H80000
Global Const ahtOFN_NODEREFERENCELINKS = &H100000
Global Const ahtOFN_LONGNAMES = &H200000
 
Function ahtCommonFileOpenSave( _
            Optional ByRef Flags As Variant, _
            Optional ByVal InitialDir As Variant, _
            Optional ByVal Filter As Variant, _
            Optional ByVal FilterIndex As Variant, _
            Optional ByVal DefaultExt As Variant, _
            Optional ByVal FileName As Variant, _
            Optional ByVal DialogTitle As Variant, _
            Optional ByVal hwnd As Variant, _
            Optional ByVal OpenFile As Variant) As Variant
' This is the entry point you'll use to call the common
' file open/save dialog. The parameters are listed
' below, and all are optional.
'
' In:
' Flags: one or more of the ahtOFN_* constants, OR'd together.
' InitialDir: the directory in which to first look
' Filter: a set of file filters, set up by calling
' AddFilterItem. See examples.
' FilterIndex: 1-based integer indicating which filter
' set to use, by default (1 if unspecified)
' DefaultExt: Extension to use if the user doesn't enter one.
' Only useful on file saves.
' FileName: Default value for the file name text box.
' DialogTitle: Title for the dialog.
' hWnd: parent window handle
' OpenFile: Boolean(True=Open File/False=Save As)
' Out:
' Return Value: Either Null or the selected filename
Dim OFN As tagOPENFILENAME
Dim strFileName As String
Dim strFileTitle As String
Dim fResult As Boolean
    ' Give the dialog a caption title.
    If IsMissing(InitialDir) Then InitialDir = CurDir
    If IsMissing(Filter) Then Filter = ""
    If IsMissing(FilterIndex) Then FilterIndex = 1
    If IsMissing(Flags) Then Flags = 0&
    If IsMissing(DefaultExt) Then DefaultExt = ""
    If IsMissing(FileName) Then FileName = ""
    If IsMissing(DialogTitle) Then DialogTitle = ""
    If IsMissing(hwnd) Then hwnd = Application.hWndAccessApp
    If IsMissing(OpenFile) Then OpenFile = True
    ' Allocate string space for the returned strings.
    strFileName = left(FileName & String(256, 0), 256)
    strFileTitle = String(256, 0)
    ' Set up the data structure before you call the function
    With OFN
        .lStructSize = Len(OFN)
        .hwndOwner = hwnd
        .strFilter = Filter
        .nFilterIndex = FilterIndex
        .strFile = strFileName
        .nMaxFile = Len(strFileName)
        .strFileTitle = strFileTitle
        .nMaxFileTitle = Len(strFileTitle)
        .strTitle = DialogTitle
        .Flags = Flags
        .strDefExt = DefaultExt
        .strInitialDir = InitialDir
        ' Didn't think most people would want to deal with
        ' these options.
        .hInstance = 0
        '.strCustomFilter = ""
        '.nMaxCustFilter = 0
        .lpfnHook = 0
        'New for NT 4.0
        .strCustomFilter = String(255, 0)
        .nMaxCustFilter = 255
    End With
    ' This will pass the desired data structure to the
    ' Windows API, which will in turn it uses to display
    ' the Open/Save As Dialog.
    If OpenFile Then
        fResult = aht_apiGetOpenFileName(OFN)
    Else
        fResult = aht_apiGetSaveFileName(OFN)
    End If
 
    ' The function call filled in the strFileTitle member
    ' of the structure. You'll have to write special code
    ' to retrieve that if you're interested.
    If fResult Then
        ' You might care to check the Flags member of the
        ' structure to get information about the chosen file.
        ' In this example, if you bothered to pass in a
        ' value for Flags, we'll fill it in with the outgoing
        ' Flags value.
        If Not IsMissing(Flags) Then Flags = OFN.Flags
        ahtCommonFileOpenSave = TrimNull(OFN.strFile)
    Else
        ahtCommonFileOpenSave = vbNullString
    End If
End Function
 
Function ahtAddFilterItem(strFilter As String, _
    strDescription As String, Optional varItem As Variant) As String
' Tack a new chunk onto the file filter.
' That is, take the old value, stick onto it the description,
' (like "Databases"), a null character, the skeleton
' (like "*.mdb;*.mda") and a final null character.
 
    If IsMissing(varItem) Then varItem = "*.*"
    ahtAddFilterItem = strFilter & _
                strDescription & vbNullChar & _
                varItem & vbNullChar
End Function
 
Private Function TrimNull(ByVal strItem As String) As String
Dim intPos As Integer
    intPos = InStr(strItem, vbNullChar)
    If intPos > 0 Then
        TrimNull = left(strItem, intPos - 1)
    Else
        TrimNull = strItem
    End If
End Function

Open in new window

0
 
jpostumaAuthor Commented:
cwood-wm-com:

this code is amazing.
I am having a problem though.

the code will run fine, but the linked tables afterwards won't be pointing to the new location.

I've enabled trust for all locations. (trusted the whole computer for a while to test)
I linked to the sync db manually.
I then moved sync db to a different folder and ran this code, but the tables were still synced to the old location.

It will prompt me for the sync db.
it will run though it's code, producting no errors,
it's just the linked tables afterwards are still on the old location.

any thoughts?
I believe it would be a lot to post the db here, but if you like, i will be happy to strip it and post both db's in a zip.

thanks.
0
Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

 
Chuck WoodCommented:
You need to unlink the tables before you link them.
Here is some code to unlink all the tables.
If you only need to unlink one table,  use syntax similar to this:

If tbl.Name = strTableName Then

    '=== after this code
    ' set a connection to the database
    cat.ActiveConnection = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & strDB & ";"
    '== add this code to unlink all the tables
    For Each tbl In cat.Tables
        If tbl.Type = "LINK" Then
            ' delete this table
            DoCmd.DeleteObject acTable, tbl.Name
        End If
    Next tbl

Open in new window

0
 
jpostumaAuthor Commented:
Quick question

 Dim strTableName As String
    strTableName = "MyTable"  '<=== set the name of your table here
    ' loop through the tables,

Is this looking specifically for tables in sync db named "mytable"

I have several tables named different things.
Should I rename them to have something in common?

Thanks a lot
0
 
Chuck WoodCommented:
Q: Is this looking specifically for tables in sync db named "mytable"?
A: Yes

You do not need to rename your tables. See the snippet for how to handle multiple tables.

    ' loop through the tables,
    For Each tbl In cat.Tables
        ' if this is the one of the tables we are linking,
        If tbl.Name = "tblFirst" Or tbl.Name = "tblSecond" Or tbl.Name = "tblThird" Then
            ' link the table
            DoCmd.TransferDatabase acLink, "Microsoft Access", _
                strDB, acTable, tbl.Name, tbl.Name, False
        End If
    Next tbl

Open in new window

0
 
jpostumaAuthor Commented:
Thank you very much for this it is great.

If you don't mind a follow up question:
What I ended up doing is deleting the linked tables right before the the import.
However, if a user cancels while selecting the location of sync the tables are still deleted.
Therefore the next time the user goes to run the code, it fails because it can't find the tables to delete.

What I would like to do is:
if table exists
delete
end if
(I had to name the 10 tables seperately, as I have a split db)

Thank you.  This was great.
0
 
jpostumaAuthor Commented:
nevermind,
I figured it out

If it helps others:
I decided to move the delete commands to after the import linked tables,
and then started every time with no linked tables.

Works great,
thanks again.
0

Featured Post

Keep up with what's happening at Experts Exchange!

Sign up to receive Decoded, a new monthly digest with product updates, feature release info, continuing education opportunities, and more.

  • 4
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now