Can you remove an Add-ed Type in PowerShell again?
Asked Answered
R

6

89

I'm currently writing a library in C# and was using PowerShell to quickly test it on some occasions. However, this prevents me from re-building the project as PowerShell obviously still has the DLL open.

Is there a way of unloading the DLL again after adding it with Add-Type? The documentation doesn't seem to have clues on that and the obvious candidate would be Remove-Type (which doesn't exist – there is only one command anyway with Type as its noun). It gets cumbersome to close PowerShell and do all the stuff of navigating to the build directory and adding the type again each time I want to rebuild.

Rostellum answered 30/7, 2010 at 8:6 Comment(2)
The documentation now says this: "You can't unload a type or change it." See learn.microsoft.com/en-us/powershell/module/…Hungerford
I sometimes create a one-letter alias in my $Profile that runs the script I'm testing, especailly in cases like @Rostellum points out. Using MS Terminal nowadays makes it easy to open/close tabs. Occasionally I'll skip the alias entirely and just make my $Profile execute the test script so that it runs as soon as a new tab is opened.Crosier
A
73

Like the others say, this is a .NET behavior. Assemblies loaded into an AppDomain cannot be unloaded. Only the AppDomain can be unloaded, and powershell uses a single appdomain. I blogged a bit about this some years ago:

https://web.archive.org/web/20170707034334/http://www.nivot.org/blog/post/2007/12/07/WhyAppDomainsAreNotAMagicBullet

When I test like this, I usually keep a shell open and use a nested shell to do tests. start powershell, cd to bin location then run "powershell" to start nested shell (new process.) "exit" to start over, and run "powershell" again.

Attorneyatlaw answered 30/7, 2010 at 14:55 Comment(2)
@jpaugh unfortunately my hosting company deleted my free account, and I neglected to export my blog data. I've updated the link to archive dot org.Attorneyatlaw
Sorry to hear that! I can say that logdown.com did not delete my free account; after several years of inactivity, I'm getting back into it. YMMV, but I recommend it. Edit: URLHannibal
D
55

I find the simplest way to get around this problem is to wrap the Add-Type and the test code inside of a Start-Job. Start-Job will create a background process, and the type will be loaded there. Once you are done, the process goes away and you're free to retry.

Here's an example of how it looks:

$job = Start-Job -ScriptBlock {

    Add-Type -path 'my.dll'
    $myObj = new-object My.MyTestClassName

    $result = $myObj.TestMethod
    $result
}
Wait-Job $job
Receive-Job $job

The output from the test method will be echoed to the console.

Dannadannel answered 30/7, 2010 at 19:19 Comment(5)
I like this method, although in my case I need the result of a variable set in the background job. Without writing it to a file, I'm not sure how I would get that information. Nevertheless, I think it deserves a +1Propertius
@SlogmeisterExtraordinaire In the job, echo the result. In the main script, call Receive-Job to fetch it.Deafanddumb
I added an example of how to do this. Any typos or mistakes in the example are mine alone, and not @Start-Automating's.Wendall
Thanks. This really helped me with confused loaded assemblies: https://mcmap.net/q/57581/-powershell-on-windows-quot-windows-data-protection-api-dpapi-is-not-supported-on-this-platform-quotTennies
Would have been nice to see this with the Hello World example above.Blithe
N
26

If your assembly doesn't require a binding context you can do this:

$bytes = [System.IO.File]::ReadAllBytes("Path_To_Your_Dll.dll")
[System.Reflection.Assembly]::Load($bytes)
Nonbelligerent answered 30/7, 2010 at 9:30 Comment(4)
and how this would made it possible to unload the assembly?Inclinatory
You wouldn't be able to unload, but OTOH the DLL will not be kept open and locked as the read has been independent of the load (ReadAllBytes doesn't keep the file open or locked). So it depends on what the OP wants; this will fix the problem of "I can't build" which seemed to be the driver for wanting an unload.Trustless
This is nice. One small discrepancy/drawback, is that Add-Type seems to load the types in the indicated assembly and all of its dependencies, while this only loads the types from the indicated assembly. So if you need types from upstream assemblies, you'll need to use this to load them as well.Lawannalawbreaker
This works great! Even for DLLs I downloded into memory. No need to save them as a file first. Many thanks for sharing.Tangier
H
6

Here is a complete example that allows to run the Add-Type command as a background job so that the assembly is unloaded once it finishes:

# Start-Job will not preserve the working directory, so do it manually
# Other arguments can also be passed to the job this way
$cd = Split-Path $MyInvocation.MyCommand.Path
$jobParams = @{
    'cd' = $cd
}

Start-Job -InputObject $jobParams -ScriptBlock {
    cd $Input.cd
    Add-Type -Path assembly.dll
} | Receive-Job -Wait -AutoRemoveJob

Receive-Job -Wait will make sure that the output of the job is received since otherwise it will be lost.

Handedness answered 10/1, 2017 at 14:25 Comment(1)
Can you clarify where the C# code goes, and what other parameters you need to change in your snippet? Also what is the assembly.dll you refer to? In my code there is no dll in the Powershell part.Blithe
A
1

Visual Studio Code:

Settings -> Extensions -> PowerShell -> Debugging: Create Temporary Integrated Console

Check checkbox: "Creates a temporary PowerShell Extension Terminal for each debugging session. This is useful for debugging PowerShell classes and binary modules."

This results in a delay of several seconds every time you press F5, which is annoying. I often work around this by shifting focus to the terminal window (mouse clicking it), pressing up arrow, verifying the correct script is now in the command prompt, and pressing Enter. To me, this is a little less annoying than waiting, but still gives me the option to reload the terminal (pressing F5) when I know I need a new instance PowerShell.

Ahwaz answered 17/6, 2022 at 16:8 Comment(0)
C
0

I have been facing to similar problem. It is not possible to unload a type/assembly (that's because it applies to .NET framework).

In .NET you can solve it if you crate a new application domain (System.AppDomain) and load the assembly into that domain. It is possible to unload the app domain and that unloads all the dlls as well.

I haven't tried it yet, because for me it is much simpler to close a tab in Console and open new one.

Canal answered 30/7, 2010 at 8:58 Comment(1)
Powershell only uses a single appdomain, creating a new appdomain, loading types into that appdomain and then unloading the appdomain does not unload the type.Unrivaled

© 2022 - 2024 — McMap. All rights reserved.