可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I've got a module setup to be like a library for a few other scripts. I can't figure out how to get a class declaration into the script scope calling Import-Module
. I tried to arrange Export-Module
with a -class
argument, like the -function
, but there isn't a -class
available. Do I just have to declare the class in every script?
The setup:
- holidays.psm1 in ~\documents\windows\powershell\modules\holidays\
- active script calls
import-module holidays
- there is another function in holidays.psm1 that returns a class object correctly, but I don't know how to create new members of the class from the active script after importing
Here is what the class looks like:
Class data_block
{
$array
$rows
$cols
data_block($a,$r,$c)
{
$this.array = $a
$this.rows = $r
$this.cols = $c
}
}
回答1:
According to here and here, you can use classes defined in your module by doing the following in PowerShell 5:
using module holidays
回答2:
using
is Prone to Pitfalls
The using
keyword is prone to various pitfalls as follows:
- The
using
statement does not work for modules not in PSModulePath
unless you specify the module's full path in the using
statement. This is rather surprising because although a module is available via Get-Module
the using
statement may not work depending on how the module was loaded.
- The
using
statement can only be used at the very beginning of a "script". No combination of [scriptblock]::Create()
or New-Module
seems overcome this. A string passed to Invoke-Expression
seems to act as a sort of standalone script; a using
statement at the beginning of such a string sort of works. That is, Invoke-Expression "using module $path"
can succeed but the scope into which the contents of the module are made available seems rather inscrutable. For example, if Invoke-Expression "using module $path"
is used inside a Pester scriptblock, the classes inside the module are not available from the same Pester scriptblock.
The above statements are based on this set of tests.
.NewBoundScriptBlock()
Seems to Work Reliably
Invoking a scriptblock bound to the module containing the class seems to work reliably to export instances of a class and does not suffer from the pitfalls that using
does. Consider this module which contains a class and has been imported:
New-Module 'ModuleName' { class c {$p = 'some value'} } |
Import-Module
Invoking [c]::new()
inside a scriptblock bound to the module produces an object of type [c]
:
PS C:\> $c = & (Get-Module 'ModuleName').NewBoundScriptBlock({[c]::new()})
PS C:\> $c.p
some value
回答3:
I found a way to load the classes without the need of "using module".
In your MyModule.psd1 file use the line:
ScriptsToProcess = @('Class.ps1')
And then put your classes in the Class.ps1 file:
class MyClass {}
Update: Although you don't have to use "using module MyModule" with this method you still have to either:
- Run "using module MyModule"
- Or run "Import-Module MyModule"
- Or call any function in your module (so it will auto import your module on the way)
回答4:
You pretty much cannot. According to about_Classes
help:
Class keyword
Defines a new class. This is a true .NET Framework type. Class members are public, but only public within the module scope. You can't refer to the type name as a string (for example, New-Object doesn't work), and in this release, you can't use a type literal (for example, [MyClass]) outside the script/module file in which the class is defined.
This means, if you want to get yourself a data_block
instance or use functions that operate those classes, make a function, say, New-DataBlock
and make it return a new data_block
instance, which you can then use to get class methods and properties (likely including static ones).
回答5:
This certainly does not work as expected.
The idea in PS 5 is that you can define your class in a separate file with a .psm1 extension.
Then you can load the definition with the command (eg):
using module C:\classes\whatever\path\to\file.psm1
This must be the first line in your script (after comments).
What causes so much pain is that even if the class definitions are called from a script, the modules are loaded for the entire session. You can see this by running:
get-module
You will see the name of the file you loaded. No matter if you run the script again, it will NOT reload the class definitions! (Won't even read the psm1 file.) This causes much gnashing of teeth.
Sometimes - sometimes - you can run this command before running the script, which will reload the module with refreshed class definitions:
remove-module file
where file is the name without path or extension. However to save your sanity I recommend restarting the PS session. This is obviously cumbersome; Microsoft needs to clean this up somehow.
回答6:
I've encountered multiple issues regarding PowerShell classes in v5 as well.
I've decided to use the following workaround for now, as this is perfectly compatible with .net and PowerShell:
Add-Type -Language CSharp -TypeDefinition @"
namespace My.Custom.Namespace {
public class Example
{
public string Name { get; set; }
public System.Management.Automation.PSCredential Credential { get; set; }
// ...
}
}
"@
The benefit is that you don't need a custom assembly to add a type definition, you can add the class definition inline in your PowerShell scripts or modules.
Only downside is that you will need to create a new runtime to re-load the class definition after is has been loaded for the first time (just like loading assemblies in a c#/.net domain).
回答7:
The way I've worked around this problem is to move your custom class definition into an empty .ps1 file with the same name (like you would in Java/C#), and then load it into both the module definition and your dependent code by dot sourcing. I know this isn't great, but to me it's better than having to maintain multiple definitions of the same class across multiple files...
回答8:
To update class definitions while developing select the code for the class and press F8
to run the selected code. Not as clean as the -Force
option on the Import-Module
command. Seeing as Using Module
doesn't have that option and Remove-Module
is sporadic at best, this is the best way I have found to develop a class and see the results without having to close down the ISE and start it up again.
回答9:
the using statement is the way to go if it works for you. otherwise this seems to work as well.
testclass.psm1
Use a function to deliver the class
class abc{
$testprop = 'It Worked!'
[int]testMethod($num){return $num * 5}
}
function abc(){
return [abc]::new()
}
Export-ModuleMember -Function abc
someScript.ps1
Import-Module path\to\testclass.psm1
$testclass = abc
$testclass.testProp # returns 'It Worked!'
$testclass.testMethod(500) # returns 2500
$testclass | gm
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
testMethod Method int testMethod(System.Object num)
ToString Method string ToString()
testprop Property System.Object testprop {get;set;}
回答10:
I have tried every approach on this page and I have YET to find one that works repeatedly. I find the module caching is the most frustrating aspect of this... I can import classes as modules and even get code to work, but powershell only recognizes the new class intermittently.. its almost like Powershell just forgets you ever added one...