[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." }