1

I've been working on a VERY specific functionality "need" to tie into a custom Provider I'm writing in C#.

Basically I set out to find a way to replicate the

A:
B:

etc functions defined when PowerShell loads so instead of having to type

CD A:

You can just do the aforementioned

A:

I tried first to have my provider inject the functions into the runspace but it seems I'm completely missing the timing of how to get that to work so I went another route.

Basically I have a VERY simple PSM1 file UseColons.psm1

function Use-ColonsForPSDrives
{
    [CmdletBinding()] Param()

    Write-Verbose "Looping Through Installed PowerShell Providers"
    Get-PSProvider | % `
    {
        Write-Verbose "Found $($_.Name) checking its drives"
        $_.Drives | ? { (Get-Command | ? Name -eq "$($_.Name):") -eq $null } | `
        { 
            Write-Verbose "Setting up: `"function $($_.Name):() {Set-Location $($_.Name):}`""
            if ($Verbose)
            {
                . Invoke-Expression -Command "function $($_.Name):() {Set-Location $($_.Name):}" 
            }
            else
            {
                . Invoke-Expression -Command "function $($_.Name):() {Set-Location $($_.Name):}" -ErrorAction SilentlyContinue
        }
            Write-Verbose "Finished with drive $($_.Name)"
        }
    }
    # Cert and WSMan do not show up as providers until you try to naviagte to their drives
    # As a result we will add their functions manually but we will check if they are already set anyways
    if ((Get-Command | ? Name -eq "Cert:") -eq $null) { . Invoke-Expression -Command "function Cert:() {Set-Location Cert:}" }
    if ((Get-Command | ? Name -eq "WSMan:") -eq $null) { . Invoke-Expression -Command "function WSMan:() {Set-Location WSMan:}" }
}

. Use-ColonsForPSDrives

In simple terms it loops through all loaded providers, then through all the drives of each provider, then it checks if the Function: drive contains a function matching the {DriveName}: format and if one is not found it creates one.

The psd1 file is nothing more than export all functions

This is stored in the %ProgramFiles%\WindowsPowerShell\Modules path under its own folder

And finally I have profile.ps1 under the %windir%\system32\windowspowershell\v1.0 directory that just does

Remove-Module UseColons -ErrorAction SilentlyContinue
Import-Module UseColons

So when I load PowerShell or the ISE if I want to get to say dir through the variables I can just call

Variable:

Or if I need to switch back to the registry

HKLM:
HKCU:

Which when you are working with multiple providers typing that CD over and over as you switch is just annoying.

Now to the problem I'm still working on developing the actual PowerShell provider this was originally intended for. But when I debug it the UseColons module loads BEFORE visual studio turns around and loads the new provider so if I manually remove and import the module again it does its thing and I have all my drive functions for my provider.

I wanted to know after that LONG explanation how can I either:

  • Setup my UseColons module to load LAST
  • Find a way to have my Custom Provider (technically a module since it has the provider AND custom Cmdlets) load the UseColons module when it initializes
  • I don't want to remove it from my standard profile because it is very helpful when I'm not working on the new provider and just tooling around using powershell for administrative stuff.

    Hopefully someone can give me some ideas or point me in the direction of some good deeper dive powershell provider documentations and how-tos.

    halfer
    • 19,824
    • 17
    • 99
    • 186
    TofuBug
    • 573
    • 1
    • 6
    • 22

    2 Answers2

    1

    You can dynamically create a new function every time a new drive is added to your provider by overriding the NewDrive method like this:

    protected override PSDriveInfo NewDrive(PSDriveInfo drive)
    {
        InvokeCommand.InvokeScript($"New-Item -Path 'Function:\\' -Name 'global:{drive.Name}:' -Value {{ Set-Location $MyInvocation.MyCommand.Name }}");
        return drive;
    }
    

    That way, your provider drives have the same functionality as the built-in filesystem drives:

    Get-Item "Function:\C:" | select Name, ScriptBlock
    

    will return:

    Name ScriptBlock
    ---- -----------
    C:   Set-Location $MyInvocation.MyCommand.Name
    

    You can add a similar command to RemoveDrive to remove the function again.

    Of course, this will only work for drives in your own provider. If you want to do this for other providers, you can run this command:

    Get-PSDrive | ForEach-Object { New-Item -Path "Function:\\" -Name "global:$($_.Name):" -Value { Set-Location $MyInvocation.MyCommand.Name } -ErrorAction Ignore }
    

    The -ErrorAction Ignore is in case the command exists already.

    0

    In your module manifest (.psd1), you have a DLL as the RootModule?

    This is a horrible hack, and does not help for drives that get created in the future, but...

    In your module manifest, instead of YourProvider.dll as the RootModule, use Dummy.psm1 instead (can be an empty file). Then, for NestedModules, use @( 'YourProvider.dll', 'UseColons' ). This allows the UseColons module to be loaded after YourProvider.dll. (Dummy will be last.)

    jazzdelightsme
    • 457
    • 3
    • 14