Is it possible to create a truly unique directory name (i.e. based on uuid) that is shorter then the default guid format?
so far I've been able to come up with this:
function Get-UglyButShortUniqueDirname {
[CmdletBinding()]
param ()
$t = "$([System.Guid]::NewGuid())".Replace("-", "")
Write-Verbose "base guid: $t"
$t = "$(0..$t.Length | % { if (($_ -lt $t.Length) -and !($_%2)) { [char][byte]"0x$($t[$_])$($t[$_ 1])" } })".replace(" ", "").Trim()
Write-Verbose "guid as ascii: $t"
([System.IO.Path]::GetInvalidFileNameChars() | % { $t = $t.replace($_, '.') })
Write-Verbose "dirname: $t"
$t
}
With this I can generate directory names that look weird but take only about ~16 characters, which is way better than the default 32 characters of a plain guid (without dashes).
The thing I'm a bit concerned about: as 'invalid file name characters' are stripped and replaced with dots, those identifiers do not hold up to the same "uniqueness promise" as a guid does.
(struggling with legacy 260 char path-name limitations in Win-based automation environments :-/)
CodePudding user response:
I would Base32-encode the GUID. With 26 characters the result will be slightly longer than Base64, but you won't lose bits of randomness on case-insensitive file systems. Also it uses only basic alphanumeric characters, which IMO looks better ;-).
Unfortunately there is no built-in Base32 encoder in .NET. First I've adopted the encoding part of this C# answer but then I got fancy and modified it to use the z-base32 variant, which is easier on the human eye and saves a few characters by not using padding.
Add-Type -TypeDefinition @'
public class ZBase32Encoding {
private const string Charset = "ybndrfg8ejkmcpqxot1uwisza345h769";
public static string ToString(byte[] input) {
if (input == null || input.Length == 0) {
throw new System.ArgumentNullException("input");
}
int charCount = (int)System.Math.Ceiling(input.Length / 5d) * 8;
char[] returnArray = new char[charCount];
byte nextChar = 0, bitsRemaining = 5;
int arrayIndex = 0;
foreach (byte b in input) {
nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
returnArray[arrayIndex ] = Charset[nextChar];
if (bitsRemaining < 4) {
nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
returnArray[arrayIndex ] = Charset[nextChar];
bitsRemaining = 5;
}
bitsRemaining -= 3;
nextChar = (byte)((b << bitsRemaining) & 31);
}
//if we didn't end with a full char
if (arrayIndex != charCount) {
returnArray[arrayIndex ] = Charset[nextChar];
}
return new string(returnArray);
}
}
'@
foreach( $i in 1..5 ) {
[ZBase32Encoding]::ToString((New-Guid).ToByteArray())
}
Output:
k5q8ds6jn89rjeex59ewyw6ync
7hjtizchq4mwjp61ft14qrbjah
jtsmn99ys3qrjr64ayk3bcpeae
rarrrhz4xmfrmm4qgpzh4u5q6r
r8zporesbi7ridz8yjgrwmgkjc
CodePudding user response:
Convert your quid to Base64 which gives you a 24 characters string and (as mentioned by zett42) it is required to replace the possible slash (/). besides, you might save another two characters by removing the unnecessary padding:
[System.Convert]::ToBase64String((NewGuid).ToByteArray()).SubString(0,22).Replace('/', '-')
zp92wiHcdU 0Eb9Cw2z0VA
BUT, there is actually a flaw in this idea: folder names are case insensitive, meaning that the folder naming might not be as unique as the original guid.
Therefore you might want to fall back on Base32 (which needs 28 characters), which is a little more complex as there is no standard .Net method for this:
$Bytes = (New-Guid).ToByteArray()
$Bits = -join $Bytes.ForEach{ [Convert]::ToString($_, 2).PadLeft(8, '0') }
[regex]::Replace($Bits, '.{5}', { param($Match)
'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'[[Convert]::ToInt32($Match.Value, 2)]
})
DJTSK2NBJ4LUPH54QETCPCSRC111
You might do something similar to include special characters, but I would be very careful about that as not every file system might support that.
