1

I am mapping and un-mapping drives in my script. I ran into a need to determine if a drive letter is already mapped which is easy with test-path.

The problem is how do I go for the next available drive letter if there is one?

New-PSDrive -Name "S" -Root "\\<Name>\GYBWatch" -PSProvider "FileSystem" -Persist
dcaz
  • 847
  • 6
  • 15

2 Answers2

2
[char[]] $taken = (Get-PSDrive -Name [A-Z]).Name
$nextAvailable = ([char[]] (65..90)).Where({ $_ -notin $taken }, 'First')[0]

if (-not $nextAvailable) { throw "No drive letters available." }
  • (Get-PSDrive -Name [A-Z]) uses a wildcard expression to get information about all currently defined single-letter-name drives.

    • .Name uses member-access enumeration to return an array of the names of all these drives, i.e. the taken letters as strings.
    • The [char[]] type constraint applied to variable $taken converts the array of single-letter strings to an array of [char] instances.
  • [char[]] (65..90) programmatically creates the array of (English) uppercase letters, 'A', 'B', ..., 'Z', using .., the range operator. In PowerShell (Core) 7+ you can use character endpoints directly: 'A'..'Z'. Note that PowerShell is case-insensitive in general, so the case of these letters is irrelevant.

  • The .Where() array method iterates over each letter and returns the first letter ('First') that satisfies the condition:

    • $_ -notin $taken returns $true if the input letter at hand ($_) is not an element of (-notin) the $taken array, i.e. it returns $true if the letter isn't currently taken.

    • Note that [0] is applied to the .Where() call so as to ensure that the (at most) one and only result is treated as a scalar, which is necessary, because the .Where() and .ForEach() methods always return a collection. That said, in PowerShell the distinction between a scalar and a collection with a single element often doesn't matter.

Note: The above starts looking for available drive letters with letter A (drive A:).
To start looking from a different letter - say E - do the following:

  • Adjust the starting character in the letter-array-creation operation, ..

    [char[]] (69..90) # 'E', 'F', ..., 'Z'
    
    • 69, i.e. the decimal form of the Unicode code point of the uppercase E letter, was obtained with [int] [char] 'E'

    • Again, in PowerShell (Core) 7+ you could simply use 'E'..'Z'

  • As an optional optimization you could additionally adjust the wildcard pattern in the Get-PSDrive call:

    (Get-PSDrive -Name [E-Z]).Name
    

An alternative solution that avoids looping and linear searches in arrays:

  • Surprisingly, however, it is slower than the solution above, perhaps due to overhead of exception handling; in practice, both solutions will likely perform acceptably.

  • It takes advantage of the fact that you can pass multiple drive letters (names) to the -Name parameter; non-existent names trigger a non-terminating error that can be escalated to a terminating one with -ErrorAction Stop, which can then be trapped with try { ... } catch { ... }.

  • The offending name - the nonexistent drive letter - is reported in the error record's .TargetObject property (reported via the automatic $_ variable in the catch block, as an [ErrorRecord] instance).

$nextAvailable =
  try {
    $null = Get-PSDrive -ErrorAction Stop -Name ([char[]] (65..90))
  } catch {
    $_.TargetObject
  }

if (-not $nextAvailable) { throw "No drive letters available." }
mklement0
  • 382,024
  • 64
  • 607
  • 775
2
#quick and dirty 
#create Alphabet

   $alphabet = @()  
   for ([byte]$c = [char]'A'; $c -le [char]'Z'; $c++){  
       $alphabet += [char]$c  
   }  
   [String]::Join(", ", $alphabet)

#get all Letter which are not in use

   $alphabet | where { (Get-PSDrive -PSProvider FileSystem | select -ExpandProperty Name) -notcontains $_  }
Mr. S.
  • 17
  • 1
  • Nice, though note that you'll need to append `| Select-Object -First 1` in order to output just the _first_ available letter. A few tips: You can create the character array more succinctly and more efficiently via `..`, the [range operator](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Operators#range-operator-), as follows: `[char[]] (65..90)`, or - more conveniently in _PowerShell (Core) 7+_ - `'A'..'Z'`. (In general, it's [best to avoid `+=` to iteratively build arrays](https://stackoverflow.com/a/60708579/45375)). – mklement0 Sep 01 '21 at 15:37
  • Please add further details to expand on your answer, such as working code or documentation citations. – Community Sep 01 '21 at 16:24