Created
September 11, 2023 03:28
-
-
Save jborean93/6b549be3fb7373b11f1c1d93b4cdcc07 to your computer and use it in GitHub Desktop.
Generate UUIDv5 values in PowerShell
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright: (c) 2023, Jordan Borean (@jborean93) <[email protected]> | |
# MIT License (see LICENSE or https://opensource.org/licenses/MIT) | |
class EncodingTransformAttribute : System.Management.Automation.ArgumentTransformationAttribute { | |
[object] Transform([System.Management.Automation.EngineIntrinsics]$engineIntrinsics, [object]$InputData) { | |
$result = switch ($InputData) { | |
{ $_ -is [System.Text.Encoding] } { $_ } | |
{ $_ -is [string] } { | |
switch ($_) { | |
ASCII { [System.Text.ASCIIEncoding]::new() } | |
BigEndianUnicode { [System.Text.UnicodeEncoding]::new($true, $true) } | |
BigEndianUTF32 { [System.Text.UTF32Encoding]::new($true, $true) } | |
ANSI { | |
# I don't like using the term ANSI here but it's better than Default used in Windows | |
# PowerShell and it's more relevant to the terms that Windows uses. This is the "ANSI" | |
# system codepage set | |
[System.Text.Encoding]::GetEncoding([System.Globalization.CultureInfo]::CurrentCulture.TextInfo.ANSICodePage) | |
} | |
OEM { [System.Console]::OutputEncoding } | |
Unicode { [System.Text.UnicodeEncoding]::new() } | |
UTF8 { [System.Text.UTF8Encoding]::new($false) } | |
UTF8BOM { [System.Text.UTF8Encoding]::new($true) } | |
UTF8NoBOM { [System.Text.UTF8Encoding]::new($false) } | |
UTF32 { [System.Text.UTF32Encoding]::new() } | |
default { [System.Text.Encoding]::GetEncoding($_) } | |
} | |
} | |
{ $_ -is [int] } { [System.Text.Encoding]::GetEncoding($_) } | |
default { | |
throw [Management.Automation.ArgumentTransformationMetadataException]::new( | |
"Could not convert input '$_' to a valid Encoding object." | |
) | |
} | |
} | |
return $result | |
} | |
} | |
Function New-Uuid5 { | |
[OutputType([Guid])] | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory, Position = 0)] | |
[Guid] | |
$Namespace, | |
[Parameter(Mandatory, Position = 1)] | |
[object] | |
$Value, | |
[Parameter()] | |
[EncodingTransformAttribute()] | |
[System.Text.Encoding] | |
$Encoding = 'UTF8' | |
) | |
<# | |
.SYNOPSIS | |
Generates a UUID5 value from a namespace and value. | |
.EXAMPLE | |
Create a UUID5 value for a Windows Terminal Profile | |
$terminalNamespace = '2bde4a90-d05f-401c-9492-e40884ead1d8' | |
New-Uuid5 $terminalNamespace 'Ubuntu' -Encoding Unicode | |
.PARAMETER Namespace | |
A known UUID to use as the namespace when generating the UUID5 value. | |
.PARAMETER Value | |
The value to use alongside the namespace to generate the final UUID5 value. | |
Can be a byte array to use as is, otherwise the value will be casted to a | |
string and converted to a byte array using the -Encoding value. | |
.PARAMETER Encoding | |
The encoding to use when convert the -Value to bytes. | |
Defaults to UTF8 but can be any other encoding that is needed. | |
#> | |
# Thanks to the following answer | |
# https://stackoverflow.com/questions/10867405/generating-v5-uuid-what-is-name-and-namespace | |
if ($Value -is [System.Collections.Generic.IEnumerable[byte]]) { | |
$valueBytes = [System.Linq.Enumerable]::ToArray($Value) | |
} | |
else { | |
$valueBytes = $Encoding.GetBytes([string]$Value) | |
} | |
# Guid.ToByteArray() is in the little endian order for time_low, time_mid, | |
# and time_hi_version. The hashing operation must use the big endian | |
# ordered GUID. | |
$namespaceBytes = $Namespace.ToByteArray() | |
[System.Array]::Reverse($namespaceBytes, 0, 4) | |
[System.Array]::Reverse($namespaceBytes, 4, 2) | |
[System.Array]::Reverse($namespaceBytes, 6, 2) | |
$buffer = [byte[]]::new($namespaceBytes.Length + $valueBytes.Length) | |
[System.Buffer]::BlockCopy($namespaceBytes, 0, $buffer, 0, $namespaceBytes.Length) | |
[System.Buffer]::BlockCopy($valueBytes, 0, $buffer, $namespaceBytes.Length, $valueBytes.Length) | |
$sha1 = [System.Security.Cryptography.SHA1]::Create() | |
$hash = $sha1.ComputeHash($buffer) | |
$guidBytes = [byte[]]::new(16) | |
[System.Buffer]::BlockCopy($hash, 0, $guidBytes, 0, $guidBytes.Length) | |
# Set high-nibble to 5 to indicate type 5 | |
$guidBytes[6] = $guidBytes[6] -band 0x0F | |
$guidBytes[6] = $guidBytes[6] -bor 0x50 | |
# Set upper two bits to 0b10 | |
$guidBytes[8] = $guidBytes[8] -band 0x3F | |
$guidBytes[8] = $guidBytes[8] -bor 0x80 | |
# Need to convert back to the little endian for dotnet | |
[System.Array]::Reverse($guidBytes, 0, 4) | |
[System.Array]::Reverse($guidBytes, 4, 2) | |
[System.Array]::Reverse($guidBytes, 6, 2) | |
[Guid]::new($guidBytes) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment