I recently got thinking about the -AsCustomObject switch for the Import-Module cmdlet. I have seen it several times in discussions of implementing “classes” in PowerShell. Here’s a typical (i.e. trivial) example:
#module adder.psm1
function add-numbers($x,$y){
return $x+$y
}
With that module, we can do the standard module stuff:
PS> import-module adder
PS> add-numbers 1 2
3
Ok, that was way too basic. Here’s something a lot closer to the topic at hand:
PS> $adder=import-module adder -ascustomobject
PS> $adder | gm
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
add-numbers ScriptMethod System.Object add-numbers();
PS> $adder.add-numbers(1,2)
Unexpected token '-numbers' in expression or statement.
At line:1 char:11
PS> $adder."add-numbers"( 1, 2)
3
There are a several interesting things to notice about this example. First of all, note that the add-numbers function has become a scriptmethod on the $adder object. As the help topic for import-module states, the members of the custom object are the (exported) members of the module. When we try to call the add-numbers method, we find that our decision to use the noun-verb naming convention has bitten us. To use the method, we need to enclose the offending method name in quotes (both single and double work fine). Note that since this is a method we need to use commas to separate the arguments to the function.
A second thing to note is since this is a method, not a function, we can’t skip arguments.
PS> $adder.add-numbers(,2)
Note that we could definitely do
add-numbers -y 2
if we had used a normal import-module. Granted that in this case there would be no need to.
What if we try to fix the quotation issue by including an alias (say, AddNumbers) to the module and exporting it?
function add-numbers($x,$y){
return $x+$y
}
new-alias addNumbers add-numbers
export-modulemember -Function * -Variable * -alias *
Here’s what we find:
PS> $adder=import-module adder -ascustomobject -force
PS> $adder | gm
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
add-numbers ScriptMethod System.Object add-numbers();
PS> get-alias AddNumbers
Capability Name ModuleName
---------- ---- ----------
Script addNumbers -> add-numbers adder
Hey! Our alias is missing. Unfortunately, it got imported into the global scope (possibly hiding another function). Note that I used the -force switch to make sure that we re-import it if it was already loaded.
When I first read about the -asCustomObject switch, I could see myself using this to import modules that had conflicting function names, and using the custom objects to call the methods in question. However, consider a function with a large number of switches or parameters. With an “-ascustomobject” object, you would need to specify all of the switches or parameters. Again, what about a function which used parametersets? As it turns out, scriptmethods don’t seem to use parametersets. Here’s function to demonstrate:
function test-psets{
param([Parameter(ParameterSetName="Set1")]$x,
[Parameter(ParameterSetName="Set2")]$y)
switch ($PsCmdlet.ParameterSetName){
"Set1" {write-host "we're using Set1"}
"Set2" {write-host "we're using Set2"}
default {write-host "don't know what parameter set we're in"}
}
Write-host "we had better be using $($PsCmdlet.ParameterSetName)"
}
Calling that function on a custom object looks like this:
PS> $adder=import-module adder -ascustomobject -force
PS> $adder."test-psets"(1) #should use pset 1, since we're only using the first parameter
don't know what parameter set we're in
we had better be using
PS> $adder."test-psets"(1,2) #shouldn't be valid, since they're different parametersets
don't know what parameter set we're in
we had better be using
PS> #Sanity check to make sure the function works
PS> import-module adder
PS> test-psets -x 1
we're using Set1
we had better be using Set1
PS> test-psets -y 1
we're using Set2
we had better be using Set2
PS> test-psets -x 1 -y 2
test-psets : Parameter set cannot be resolved using the specified named parameters.
At line:1 char:1
+ test-psets -x 1 -y 2
+ ~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [test-psets], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,test-psets
So it seems that functions that utilize parametersets are going to be a lot less useful with -AsCustomObject imports.
As I mentioned, there are several examples floating around concerning creating new objects (or classes, depending on your perspective) using modules and this option. Given the drawbacks I’ve noted in this article, I think I’m going to stay away from that particular use case.
What do you think? Did I miss something important? Please let me know what your opinion is.
-Mike