CreateGuid: A Reliable Way to Generate GUIDs in VBA

A well-tested method to generate GUIDs in both 32-bit and 64-bit VBA.

CreateGuid: A Reliable Way to Generate GUIDs in VBA

I had the need to generate a GUID (globally unique identifier) in VBA today.

This seemed like a simple enough task, but there's surprisingly little good information available on the topic in 2022.  

What NOT To Do

Here are a few of the most common search results that I found:

Scripting Guys: Two-liner

I started with this Scripting Guys article that seemed to be exactly what I needed.

Alas, I received a permissions error when trying to run the code:

This is the result of a security fix and is by design:

This control is blocked as a security measure to help prevent malicious code from running in Office applications.
Workaround  The preferred method is to change the code to use Windows API CoCreateGuid instead of CreateObject("Scriptlet.TypeLib").Guid

Rolling Your Own

This next one comes from Daniel Ferry: Excel VBA: Create GUIDs, Easily.

I intentionally posted this as a screenshot and not copy/paste-able text: this is an unreliable way to generate unique GUIDs.

Seems straightforward enough.  And this will indeed create Version 4 GUIDs.  There's just one small problem.  If you need a bunch of them all at once, they may not be unique.  And that kind of defeats the purpose of a globally unique identifier.

With the caveat that I did not reproduce the following results, I suspected this might be the case before I even saw the comment (EDIT: I ended up being able to reproduce these results; see below for details):

I came across this article by coincidence, looking for an answer to the same need. I produced 1200 GUID's with this method and I got at least 3 repeated results, which is not acceptable for me.

Three collisions (at least) out of only 1,200 generated GUIDs is definitely a problem.  

Most likely, the problem has to do with the implicit seed being passed to the Randomize function.  If the optional [number] argument is not used, the function relies on the results of the VBA Timer function to provide a seed.  The Timer function provides "fractional portions of a second," but rapid and repeated calls to the Timer function will likely result in at least a few instances where the exact same seed value is returned.  This in turn may lead to duplicated "randomness":

Generating pseudorandom numbers is one of those deceptively difficult computer science challenges.  Which is why it's best to rely on a library that was explicitly developed to generate GUIDs.

CoCreateGUID: A Well-Tested Option

The CoCreateGuid Win32 API function is part of the COM library ().  

As the COM ecosystem relies heavily on GUIDs that are, in fact, globally unique, I have a high degree of confidence that this is one of our best–if not the best–options in Windows (feel free to prove me wrong in the comments...I'll update the article accordingly).

The code below is adapted from one of my favorite sites to get VBA-compatible Windows API code samples: AllAPI.net.  The site has not been maintained for years (e.g., you'll need to update all the samples to run in 64-bit VBA), but it is still a treasure trove of great information.

The function below was adapted from the CoCreateGuid page:

'These Declare lines go at the very top of the code module
#If VBA7 Then
    Private Declare PtrSafe Function CoCreateGuid Lib "ole32" (id As Any) As Long
#Else
    Private Declare Function CoCreateGuid Lib "ole32" (id As Any) As Long
#End If


' ----------------------------------------------------------------
' Procedure  : CreateGUID
' Author     : Dan (webmaster@1stchoiceav.com)
' Source     : http://allapi.mentalis.org/apilist/CDB74B0DFA5C75B7C6AFE60D3295A96F.html
' Adapted by : Mike Wolfe
' Republished: https://nolongerset.com/createguid/
' Date       : 8/5/2022
' ----------------------------------------------------------------
Public Function CreateGUID() As String
    Const S_OK As Long = 0
    Dim id(0 To 15) As Byte
    Dim Cnt As Long, GUID As String
    If CoCreateGuid(id(0)) = S_OK Then
        For Cnt = 0 To 15
            CreateGUID = CreateGUID & IIf(id(Cnt) < 16, "0", "") + Hex$(id(Cnt))
        Next Cnt
        CreateGUID = Left$(CreateGUID, 8) & "-" & _
                     Mid$(CreateGUID, 9, 4) & "-" & _
                     Mid$(CreateGUID, 13, 4) & "-" & _
                     Mid$(CreateGUID, 17, 4) & "-" & _
                     Right$(CreateGUID, 12)
    Else
        MsgBox "Error while creating GUID!"
    End If
End Function

Testing GUID Creation

To be sure we are generating unique GUIDs, I devised the following simple test routine:

Sub TestCreateGuid()
    Const Iterations As Long = 10000
    
    Dim GuidDict As Object 'Scripting.Dictionary
    Set GuidDict = CreateObject("Scripting.Dictionary")
    
    Dim i As Long
    For i = 1 To Iterations
        Dim Guid As String
        Guid = CreateGUID
        
        If GuidDict.Exists(Guid) Then
            Debug.Print "Duplicate GUID created: " & Guid
            Stop
        Else
            GuidDict.Add Guid, ""
        End If
    Next i
    
    Debug.Print GuidDict.Count; " unique GUIDs generated out of "; Iterations; " attempts"
End Sub

I ran the test with additional iterations until I reached a practical limit of 1,000,000–which took about five minutes to complete.  

CreateGUID() Is Rock Solid

Here are the results of testing the CreateGUID() function:

GUID$() ... Not So Much

Here are the results of testing the GUID$() function:

Eleven duplicates out of 10,000 is indeed unacceptable.

Image by Gerd Altmann from Pixabay

All original code samples by Mike Wolfe are licensed under CC BY 4.0