My Technical Notes

Saturday, 7 March 2015

A PowerShell Wrapper around Subst

Below is a wrapper module for the `subst` command found on the normal cmd shell. We define three functions following the Verb-SingularNoun pattern where the noun is `Subst`. A `subst` is a drive mapping (a "substitution").

  • `Get-Subst` - returns objects representing each drive mapping made with `subst`.
  • `New-Subst` - make a new drive mapping. It has a switch called `AutoAssignDrive`, so we don't have to provide a drive letter - it will automatically assign one for us.
  • `Remove-Subst` - remove an existing drive mapping.

Function InvokeSubst {
    $inNetWorkPath = (Get-Location).ProviderPath.StartsWith('\\');

    if ($inNetWorkPath) {
        Push-Location 'C:\'
    }

    $output = cmd.exe /C subst $args

    if ($inNetWorkPath) {
        Pop-Location
    }

    $output
}

Function GetFirstAvailableDriveLetter() {
    $usedDriveLetters = Get-PSDrive | Where { $_.Provider.Name -eq 'FileSystem' } | Select -ExpandProperty 'Name' 
    $lettersJOrder = . { ([int]'J'[0])..([int]'Z'[0]); ([int]'A'[0])..([int]'I'[0]) } | ForEach { [char]$_ }
    $availableLetters = Compare-Object $lettersJOrder $usedDriveLetters | Select -ExpandProperty InputObject
    if ($availableLetters.Length) {
        $availableLetters | Select -First 1    
    } else {
        throw "No available drive letters"
    }
}

Function Get-Subst() {
    $output = InvokeSubst
    if ($output -eq $null) {
        @()    
    } else  {
        $mappings = ($output -split "\r\n")
        $mappings | ForEach { 
            New-Object PSObject -Property @{ Drive = $_.SubString(0, 1); Path = $_.SubString(8) }
        }
    }
}

Function New-Subst([string]$Drive, [string]$Path, [switch]$AutoAssignDrive) {
    if (-not $Path) {
        throw "The Path argument must be specified"
    } elseif ((-not $Drive) -and (-not $AutoAssignDrive)) {
        throw 'Either specify the drive (using $Drive argument) or use switch $AutoAssignDrive, which will automatically assign a drive to the path'
    }

    $p = (Resolve-Path $Path).ProviderPath
    
    $driveLetter = & {
        if ($Drive) { 
            $Drive 
        } else {
            (GetFirstAvailableDriveLetter)
        }
    }

    $output = InvokeSubst "$driveLetter`:" $p

    if ($output -ne $null -and ($output.Contains("Drive already SUBSTed") -or $output.Contains("Invalid parameter"))) {
        throw $output
    } else {
        # success
        if ($AutoAssignDrive) {
            Write-Verbose "Assigned drive $driveLetter`:"
            $driveLetter
        }
    }
}

Function Remove-Subst([string]$Drive) {
    $output = InvokeSubst "$Drive`:" "/D"
    if ($output -ne $null -and $output.Contains("Invalid parameter")) {
        Write-Error "Drive mapping does not exist"
    }
}

Export-ModuleMember -Function "*-*"

No comments: