Debugging / unloading PowerShell Cmdlet

Viewed 1495

I am writing a PowerShell Cmdlet in C# using Visual Studio 2015. Debugging it works fine, but I cannot rebuild the DLL until I close the PowerShell window. The DLL seems to be in use and therefore cannot be deleted (not on the command line nor using explorer). I tried remove-module. This successfully removes my Cmdlet but I still cannot delete / overwrite it.

It is very unhandy to close PowerShell, rebuild the DLL and then reopen a new PowerShell, cd to the DLL path (usually deeply nested), re-import it again, start the command to debug, and so on for every single debugging session...

Is there no better solution to unload the DLL?

4 Answers

I used a helper powershell script which did most of it for me.

    $module = 'AccessLogParser'
    Push-Location $PSScriptroot

    dotnet build -o $PSScriptRoot\output\$module\bin
    Import-Module "$PSScriptRoot\Output\$module\bin\$module.dll"
    $VerbosePreference = $DebugPreference="continue"
    Write-Debug "$pid - $($PSVersionTable.PSVersion)"

with two options in launch config, the helper outputs the current process ID in which terminal process (bottom part of the vscode) is running, and I can select that PID in the dropdown when I launch debugger. Helpful when you want to develop something for old v5 powershell as well as v7 (pwsh.exe) version.

 {
            "name": ".NET Framework Attach",
            "type": "clr",
            "request": "attach",
            "processId": "${command:pickProcess}"
        },
        {
            "name": ".NET Core Attach",
            "type": "coreclr",
            "request": "attach",
            "processId": "${command:pickProcess}"
        }

<edit: this is now doc'd fully for pwsh and powershell.exe at https://docs.microsoft.com/en-us/powershell/scripting/dev-cross-plat/vscode/using-vscode-for-debugging-compiled-cmdlets - this doc discusses the DLL lock issue /edit>

To add to @kory-gill's answer, this is my launch.json:

{
    "name": ".NET Core Launch (console)",
    "type": "coreclr",
    "request": "launch",
    "preLaunchTask": "build",
    "cwd": "${workspaceFolder}",
    "program": "pwsh",
    "args": [
        "-NoExit",
        "-NoProfile",
        "-Command",
        "ipmo ${workspaceFolder}/DependencyTree/bin/Debug/netstandard2.0/DependencyTree.dll",
    ],
    "console": "integratedTerminal",
    "stopAtEntry": false
}

My project is called DependencyTree. Substitute your own project name.

The -NoProfile is important for me because my profile is large and breaks the debugger.

More generally, I have PSReadline set up, and you should too, to persist history across terminal sessions. So I can Ctrl-R and summon any command I have typed for a long way back. With that configured, the following is useful when working interactively with compiled or Powershell classes, and also when working with non-exported members of a module:

  1. Use your keyboard shortcuts to create and delete terminals. In VS Code, terminals should be considered disposable.
  2. $host.ExitNestedPrompt(); $Module = ipmo .\DependencyTree.psd1 -Force -PassThru; & $Module {$host.EnterNestedPrompt()}

The & $Module { ... } trick runs that scriptblock inside the module scope. Very useful for wizards ;-)

Related