Reloading a modified class in Powershell - powershell

I've a script file containing a class definition. Using the new V5 Powershell syntax. I've another script file that dot source the class and uses it. I'm testing that file in the console.
On clean startup of the console it will load the class correctly. I then make some changes to the class and saved them back to file. Re-running the script does not appear to pick the changes up. It is as though once loaded into the console, the class is immutable and will not be parsed and reloaded.
This really hurt when developing, since I have to spin up a new console after any change to a class.
Is there a way to force Powershell to reload classes each time it sees them?
This occur in code debugged both in Powershell ISE and in the Powershell console (using vscode).

This is a known issue with classes in Powershell, and looks like the issue remains unresolved at this time of writing.
To workaround this today, you either need to start a new interactive session (which you don't want to do), or load your development class in a child instance of Powershell:
powershell.exe -Command { Your-Code }
# Alternatively, write a script to test your newly updated class
powershell.exe -File \path\to\Test-ClassScript.ps1

Related

The custom type is not recognised when running ps1 script from batch file

Custom type that is being used as a return type of a function is not recognized when executing from .bat file.
I have a PS v2.0 script to implement inheritance by class.
The base class contains a method like getSessionOption that returns a WinScp.SessionOption object. This is a utility script to sftp files to remote server. Now the server credential varies according to components, like I have to transfer files either server A, B or C. But script is same for all.
Just to make the credential logic separate from server to server, I have used multiple derived classes that inherits the base class as mentioned. like ASessionOption, BSessionOption etc is has its own version of getSessionOption method.
The problem:
When I am running the script from Poweshell command then it is producing the right result. But when trying to execute the script through a batch file(.bat) then the reference of WinScp.SessionOption is not recognized.
Though I have used App-Path to load the right assembly reference and that is why no issue found when executed from Powershell command window.

How to use interfaces in Powershell defined via Add-Type?

I developed a PowerShell module that relied on a .NET Assembly for some operations.
I refactored this module to not need that Assembly and noticed some strange behavior, which blocks me from removing the class at last:
Add-Type -TypeDefinition "public interface ICanTalk { string talk(); }" -Language CSharp
class Talker : ICanTalk {
[string] talk() { return "Well hello there"; }
}
If you run this commands interactively, it will succeed.
But as soon as I run it "en-bloque" in ISE or from a psm1 file, it will throw an error, stating it cannot find the interface defined in the Add-Type call.
I can reproduce the problem in both Windows-PowerShell and PowerShell Core (6.0.2)
What is the reason for the different behaviour and how can I tackle it?
To complement your own answer:
PowerShell class and enum definitions are parsed before execution begins and any types referenced in such definitions must either be:
loaded into the current session beforehand.
[only partially implemented as of Windows PowerShell v5.1 / PowerShell Core 7.3.1]
loaded via a using module / using assembly statement at the very
top of the file.
using module already works, but only if the referenced types are themselves PowerShell class / enum definitions.
As of PowerShell 7.3.1, types loaded from assemblies - whether via a module containing assemblies imported with using module or using assembly to directly load an assembly - aren't yet detected.
See the following GitHub issues:
#3461 (using module) and #2074 (using assembly)
#6652 - a meta issue tracking all class-related issues.
Allowing using assembly to detect .NET types at parse time has been green-lighted in GitHub issue #3641, and the necessary work is being tracked as part of GitHub issue #6652 - but it is unclear when this will happen, given that the issue hasn't received attention in several years.
The workaround in the meantime is to load the referenced types via a separate script beforehand, by using the module manifest's NestedModules entry (which can also be used to dot-source *.ps1 files).
Note: The ScriptsToProcess entry would work too, but the scripts it references are dot-sourced in (loaded into the current scope of) the caller, not the enclosing module.
With types from compiled code (including ad hoc-compiled code with Add-Type), that works too, because such types invariably become available session-globally, but it is conceptually cleaner to use NestedModules, because it dot-sources scripts in the enclosing module's scope
Outside of modules, the workaround is similar:
Put your class definition in a separate .ps1 file.
In your main script, define or load the types that your class definition depends on, and then dot-source the separate script file, which ensures that all its dependent types have already been loaded.
In a pinch, you can also use Invoke-Expression (which should generally be avoided), as shown in this answer.
Apparently PowerShell will read the file and handle class-definitions before executing the code.
To solve this problem, the Add-Type needs to be put into an own script file, which is run before the module loads (or in ISE, just run the code before running the class definitions).
This can be accomplished by using ScriptsToProcess from the PSD1 file.
Kudos to #TheIncorrigible1 for putting me on the track.
Why not defining all classes in C# in a separate file, than add it as
Add-Type -path MyClasses.cs
This way it will work with older PS versions
If you define -PassThru and bind the Add-Type to a variable, you can use the variable to examine what all is being added.

PowerShell: Start-Process Firefox, how do he know the path?

When I call the following code:
Start-Process Firefox
Then the PowerShell opens the browser. I can do that with several other programs and it works. My question is: How does the PowerShell know which program to open if I type Firefox? I mean, Im not using a concrete Path or something ...
I though it has something to do with the environment variables ... But I cannot find any variable there which is called Firefox ... How can he know?
I traced two halves of it, but I can't make them meet in the middle.
Process Monitor shows it checks the PATHs, and eventually checks HKLM\Software\Microsoft\Windows\CurrentVersion\App Paths\firefox.exe so that's my answer to how it finds the install location, and then runs it.
That registry key is for Application Registration which says:
When the ShellExecuteEx function is called with the name of an executable file in its lpFile parameter, there are several places where the function looks for the file. We recommend registering your application in the App Paths registry subkey.
The file is sought in the following locations:
The current working directory.
The Windows directory only (no subdirectories are searched).
The Windows\System32 directory.
Directories listed in the PATH environment variable.
Recommended: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths
That implies PowerShell calls the Windows ShellExecuteEx function, which finds FireFox as a registered application, or it tries the same kind of searching itself internally.
Going the other way to try and confirm that, the Start-Process cmdlet has a parameter set called UseShellExecute. The 'Notes' of that help says:
This cmdlet is implemented by using the Start method of the System.Diagnostics.Process class. For more information about this method, see Process.Start Method
Trying to trace through the source code on GitHub:
Here is the PowerShell source code for Start-Process.
Here, at this line it tries to look up the $FilePath parameter with CommandDiscovery.LookupCommandInfo.
Here it checks else if (ParameterSetName.Equals("UseShellExecute"))
Then Here is the .Start() function which either starts it with ShellExecute or Process.Start()
OK, not sure if ShellExecute and ShellExecuteEx behave the same, but it could be PS calling Windows, which is doing the search for "FireFox".
That CommandSearcher.LookupCommandInfo comes in here and follows to TryNormalSearch() which is implemented here and immediately starts a CommandSearcher which has a state machine for the things it will search
SearchState.SearchingAliases
Functions
CmdLets
SearchingBuiltinScripts
StartSearchingForExternalCommands
PowerShellPathResolution
QualifiedFileSystemPath
and there I get lost. I can't follow it any further right now.
Either it shortcuts straight to Windows doing the lookup
Or the PowerShell CommandSearcher does the same search somehow
Or the PowerShell CommandSearcher in some way runs out of searching, and the whole thing falls back to asking Windows search.
The fact that Process Monitor only logs one query in each PATH folder for "firefox.*" and then goes to the registry key suggests it's not doing this one, or I'd expect many more lookups.
The fact that it logs one query for "get-firefox.*" in each PATH folder suggests it is PowerShell's command searcher doing the lookup and not Windows.
Hmm.
Using Process Monitor I was able to trace the PowerShell.
It first searches the $env:path variable, then the $profile variable.
In my case firefox wasn't found and then it searches through a whole lot in the Registry and somehow finds it.
It might have something to do with how firefox is installed on the system.

Unblock-File - Why do I need to open a new session?

I am using Carbon's Powershell module for some work. When I move the folder to a different machine, the scripts within are flagged and blocked from being executed until I unblock them (which is fine). When I execute the following:
gci .\Carbon -Recurse | Unblock-File
I am still unable to import the module until I create a new Powershell session. The files are definitely unblocked at this point, but I continue to receive the same error until that new session has been created.
I've read over some technet articles and they state that you just need to close and open Powershell to resolve it, but no reasoning as to why this needs to occur.
This actually goes back to the .Net framework on which PowerShell is based. You're essentially loading a new assembly into the process. A blocked file is considered a "remote" file and by default .net is not set to load them.
How the Runtime Locates Assemblies
Checks whether the assembly name has been bound to before and, if so, uses the previously loaded assembly.
The thing is, this step caches "negative" loading as well (at least in my experience, from trying to load other assemblies). .Net doesn't have a way unload assemblies once they're loaded, so you have no other choice than to restart the process.

clear powershell session

Is there a commandlet that clears the current powershell session variables that I have added?
I am using the Add-Type commandlet, and I am getting the error "Cannot add type. The type name already exists."
A possible "work around": Open a powershell window and then to run your script enter powershell .\yourScriptHere.ps1
This launches a new powershell instance which exits when your script exits. If you want to "play" in the new instance then change the invocation to powershell -NoExit .\yourScriptHere.ps1 and the new instance will not exit when the script completes. Enter exit when you need another restart and hit the "up arrow" key to get the previous command. All script output will appear in the same window. The overhead for starting a new powershell instance is low -- appears to be less than 1 second on my laptop.
Unfortunately you can't unload .NET assemblies that have been loaded into the default AppDomain which is what Add-Type does. You can rename types or namespaces to limp along but at some point you just have to exit and restart PowerShell.
This is not a PowerShell limitation so much as it is a .NET/CLR limitation. You can load .NET assemblies into separate AppDomains which can be unloaded later but you would have to code that yourself and it imposes restrictions on the types you plan to use in the separate AppDomain. That is, those types need to work through .NET Remoting so they either have to derive from MarshByRefObject or they have to be serializable (and this applies to all the objects referenced by their properties, and so on down the object graph).