The Right Way…?

So normally you would do this with PowerShell right? It’s easy.

1
2
$User = Get-ADUser someone -Properties 'mS-DS-ConsistencyGuid'
[convert]::ToBase64String( ([guid]$User.'mS-DS-ConsistencyGuid').ToByteArray() )

This would output the expected string for Office365. If you aren’t using mS-DS-ConsistencyGuid you would probably use ObjectGuid here instead.

The Hard Way!

So let’s say you have your users in MySQL for some reason. Maybe you are managing 10 domains and you need to be able to do cross domain lookups quickly… caughs

Well, here is how you can convert the mS-DS-ConsistencyGuid (stored as a string) to an ImmutableID.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
SELECT
    `msDSConsistencyGuid`,
    TO_BASE64(
        CONCAT(
            REVERSE(UNHEX(SUBSTRING_INDEX(`msDSConsistencyGuid`,'-',1))),
            REVERSE(UNHEX(SUBSTRING_INDEX(SUBSTRING_INDEX(`msDSConsistencyGuid`,'-',2),'-',-1))),
            REVERSE(UNHEX(SUBSTRING_INDEX(SUBSTRING_INDEX(`msDSConsistencyGuid`,'-',3),'-',-1))),
            UNHEX(REPLACE(SUBSTRING_INDEX(`msDSConsistencyGuid`,'-',-2),'-',''))
        )
    ) AS `ImmutableId`
FROM `users` WHERE `msDSConsistencyGuid` IS NOT NULL LIMIT 1

What is happening here? Well a Microsoft GUID is stored in mixed-edian format, which means that the first three compents are incoded little-edian and the last two are bit-edian order.

Here is a GUID: 6701fa1a-fb0f-45a8-9356-d302b2affbb1

Converted to a byte array that looks like:

ComponentString ValueByte ValueEdianess
16701fa1a1A FA 01 67Little
2fb0f0F FBLittle
345a8A8 45Little
4935693 56Big
5d302b2affbb1D3 02 B2 AF FB B1Big

Now MySQL when you UNHEX something it treats it as Big edian… if we just do something like this:

1
SELECT TO_BASE64(UNHEX(REPLACE('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',''))) AS `NOTImmutableId`
NOTImmutableId
ZwH6GvsPRaiTVtMCsq/7sQ==

We get something that looks like an ImmutableID, but it is not.

What I ended up doing is using SUBSTRING_INDEX to pull out each of the first three sections and the last two sections.

1
2
3
4
5
SELECT
    SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',1) AS `Component1`,
    SUBSTRING_INDEX(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',2),'-',-1) AS `Component2`,
    SUBSTRING_INDEX(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',3),'-',-1) AS `Component3`,
    SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',-2) AS `Component4and5`
Component1Component2Component3Component4and5
6701fa1afb0f45a89356-d302b2affbb1

On the last section I strip out the dash.

1
2
3
4
5
SELECT
    SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',1) AS `Component1`,
    SUBSTRING_INDEX(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',2),'-',-1) AS `Component2`,
    SUBSTRING_INDEX(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',3),'-',-1) AS `Component3`,
    REPLACE(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',-2),'-','') AS `Component4and5`
Component1Component2Component3Component4and5
6701fa1afb0f45a89356d302b2affbb1

UNHEX each section to get the binary values.

1
2
3
4
5
SELECT
    UNHEX(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',1)) AS `Component1`,
    UNHEX(SUBSTRING_INDEX(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',2),'-',-1)) AS `Component2`,
    UNHEX(SUBSTRING_INDEX(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',3),'-',-1)) AS `Component3`,
    UNHEX(REPLACE(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',-2),'-','')) AS `Component4and5`
Component1Component2Component3Component4and5
0x6701fa1a0xfb0f0x45a80x9356d302b2affbb1

REVERSE the first three components to convert to little-edian.

1
2
3
4
5
SELECT
    REVERSE(UNHEX(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',1))) AS `Component1`,
    REVERSE(UNHEX(SUBSTRING_INDEX(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',2),'-',-1))) AS `Component2`,
    REVERSE(UNHEX(SUBSTRING_INDEX(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',3),'-',-1))) AS `Component3`,
    UNHEX(REPLACE(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',-2),'-','')) AS `Component4and5`
Component1Component2Component3Component4and5
0x1afa01670x0ffb0xa8450x9356d302b2affbb1

CONCAT the whole thing and convert TO_BASE64 and we are done!

1
2
3
4
5
6
SELECT TO_BASE64(CONCAT(
    REVERSE(UNHEX(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',1))),
    REVERSE(UNHEX(SUBSTRING_INDEX(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',2),'-',-1))),
    REVERSE(UNHEX(SUBSTRING_INDEX(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',3),'-',-1))),
    UNHEX(REPLACE(SUBSTRING_INDEX('6701fa1a-fb0f-45a8-9356-d302b2affbb1','-',-2),'-',''))
)) AS `ImmutableID`
ImmutableID
GvoBZw/7qEWTVtMCsq/7sQ==

Now what?

You could create a generated column to generate your ImmutableId values and always have the latest value ready to go. Or just write a tiny bit of PowerShell and forget you ever saw this…

Converting from ImmutableID to GUID string

Just in case you need to go the other way…

1
2
3
4
5
6
7
SELECT CONCAT(
    LOWER(HEX(REVERSE(UNHEX(SUBSTRING(HEX(FROM_BASE64( 'GvoBZw/7qEWTVtMCsq/7sQ==' )),1,8))))),'-',
    LOWER(HEX(REVERSE(UNHEX(SUBSTRING(HEX(FROM_BASE64( 'GvoBZw/7qEWTVtMCsq/7sQ==' )),9,4))))),'-',
    LOWER(HEX(REVERSE(UNHEX(SUBSTRING(HEX(FROM_BASE64( 'GvoBZw/7qEWTVtMCsq/7sQ==' )),13,4))))),'-',
    LOWER(SUBSTRING(HEX(FROM_BASE64( 'GvoBZw/7qEWTVtMCsq/7sQ==' )),17,4)),'-',
    LOWER(SUBSTRING(HEX(FROM_BASE64( 'GvoBZw/7qEWTVtMCsq/7sQ==' )),21))
) AS `mSDSConsistencyGuid`
mSDSConsistencyGuid
6701fa1a-fb0f-45a8-9356-d302b2affbb1