Search code examples
windowspowershellautomation

how to create a short but still unique directory name in powershell


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 :-/)


Solution

  • 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 26 characters), which is a little more complex as there is no standard .Net method for this:

    $Chars = ('A'..'Z') + ('2'..'7')
    $Bytes = (New-Guid).ToByteArray()
    $Bits  = -join $Bytes.ForEach{ [Convert]::ToString($_, 2).PadLeft(8, '0') }
    -Join ($Bits -Split '(?<=\G.{5})').foreach{ $Chars[[Convert]::ToInt32($_, 2)] }
    DZ77OUQNDRQUTGP5ATAM7KCWCB
    

    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.