4

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?

5
  • AFAIK, unloading isn't possible. I've also encountered this. Commented Mar 10, 2016 at 13:24
  • Is a new powershell profile an option? Commented Mar 10, 2016 at 13:24
  • Possible duplicate of Powershell Unload Module... completely Commented Mar 10, 2016 at 13:31
  • 1
    This question is not a duplicate of the question @beavel mentions since in this case, the question is about using Visual Studio and debugging the module, not trying to unload it and copy over the file like the other question calls out. In the context of debugging, there is an answer, which I've posted below. Commented Mar 10, 2016 at 18:42
  • @KoryGill I can see your point. But technically, there is no way to unload the a DLL once it has been loaded into an AppDomain as the question I referenced indicates. This question states unloading, but is really about work after re-reading. Your suggested workflow is definitely an improvement. Commented Mar 10, 2016 at 21:58

4 Answers 4

7

Anytime I see something like this:

It is very unhandy to close the 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 debut, and so on for every single debugging session...

I immediately think, "I should create a script to do these tasks for me".

There are solutions. You can start a second PowerShell (like another answer suggested). Another solution is to use a script to do some work for you, and to top it off, you can add this to your VS project.

Create a script in your profile to start PowerShell

function Start-DebugPowerShell
{
    PowerShell -NoProfile -NoExit -Command {
        function prompt {
            $newPrompt = "$pwd.Path [DEBUG]"
            Write-Host -NoNewline -ForegroundColor Yellow $newPrompt
            return '> '
        }
    }
}
Set-Alias -Name sdp -Value Start-DebugPowerShell

Edit debug settings for your Cmdlet project

Start external program:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

Command line arguments:

-NoProfile -NoExit -Command "Import-Module .\MyCoolCmdlet.dll"

Debug your Module

Now from Visual Studio, start debugger with F5, and you have a new PowerShell window with your Cmdlet loaded, and you can debug it however you like.

Use the 'sdp' alias from any PowerShell window

Since the Start-DebugPowerShell function is in our profile and we gave it an alias sdp, you can use this to start a second instance of PowerShell anytime you need it.

Sign up to request clarification or add additional context in comments.

1 Comment

This should be marked as the answer. Kory's solution works fantastic in VS 2019 with Powershell 7 as well. Just change the External program to pwsh.exe.
1

I'm afraid there is not much you can do about this behavior as far as I know. One trick is to immediately start a new PowerShell session inside your existing session before loading the DLL. Then you can exit out of the second one and you have a brand new without the DLL loaded. Just remember to start a new "secondary" session before loading it again in case you need to unload it again.

2 Comments

I think you are right (unfortunately). For debugging, the correct process needs to be attached in Visual Studio as well. So starting a debugging session requires always some 'manual work'.
Kory Gill's answer below is fantastic. It should be marked as the answer to this question. It works great in VS2019 with PowerShell 7 too.
1

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

Comments

0

<edit: this is now doc'd fully for pwsh and powershell.exe at https://learn.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 ;-)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.