Skip to content

Instantly share code, notes, and snippets.

@jborean93
Created September 11, 2023 03:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jborean93/6b549be3fb7373b11f1c1d93b4cdcc07 to your computer and use it in GitHub Desktop.
Save jborean93/6b549be3fb7373b11f1c1d93b4cdcc07 to your computer and use it in GitHub Desktop.
Generate UUIDv5 values in PowerShell
# Copyright: (c) 2023, Jordan Borean (@jborean93) <jborean93@gmail.com>
# 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