Published: 10 Mar 2010
By: Manning Publications

In this article the author explains how PSModuleInfo object for a module can be retrieved. Further, he shows how code can be injected into the module to manipulate the state of a module without having to reload it. He also explains how to directly set some metadata elements, like the module description, and some other PSModuleInfo object features.

Contents [hide]

About the book

This is a sample chapter of the book Windows PowerShell in Action, Second Edition. It has been published with the exclusive permission of Manning.


Written by: Bruce Payette
Pages: 700
Publisher: Manning
ISBN: 9781935182139

Get 30% discount

DotNetSlacker readers can get 30% off the full print book or ebook at www.manning.com using the promo code dns30 at checkout.

Converting a PowerShell Script into a Module Series

Introduction

In this article, we'll look at some of the more sophisticated things we can do with modules. These features are not intended for typical day-to-day use but they do allow for some very sophisticated scripting. As always, if you aren't just scripting for yourself, have pity on the person who will have to maintain your code and avoid "stunt- scripting".

The PSModuleInfo Object

PowerShell modules, like everything in PowerShell, are objects we can work with directly. The type of the object used to reference modules is System.Management.Automation.PSModuleInfo.

This is what Get-Module returns. PSModuleObject can be used to get basic information about a module and for a lot of other things. Let's take a look at what can be done (and try to explain why you'd do those things).

Invocation in the module context

A module-level scope is used to isolate the private variables and functions. When we execute code where function and variable lookup is done in a module scope, we call this "executing in the module context". This is, of course, what happens any time we execute a function that has been exported from a module. However, we can also cause arbitrary code to be executed in the module context even though it wasn't defined in that context. In effect, we're pushing code into the module context. This is done with a PSModuleInfo object using the call operator &.

Author's Note

Yes, this ability to inject code into a module context violates all principles of isolation and information hiding. And, from a language perspective, this is a bit terrifying. Regardless, people do it all the time when debugging. One of nice things about dynamic languages is that you are effectively running the debugger attached all the time.

To try this out, we'll need a module object to play with. Let's load a counter module. First, we'll use the Select-Object cmdlet to limit what gets output to the first 8 lines, as that is all that we're concerned with here.

This module has private state in the form of the two variables $count and $increment and one public function Get-Count. Now import it

and use Get-Module to get the module reference:

We could have done this in one step with the -PassThru parameterr but we're using two steps here to illustrate that these techniques can be done with any in-memory module. Now run the Get-Count function, and it returns 1, as it should right after the module is first loaded.

Now set a global variable $count using the Set-Variable command. (Again we're using the command instead of assignment to set the variable, for illustration purposes.)

When we run Get-Count again, of course it returns 2, since the $count it uses exists in the module context.

So far, nothing much to see. Now let's do something a bit fancier. Let's see what the current value of $count in the module context is. We can do this by invoking Get-Variable in the module context with the call operator:

The call to Get-Count returns 34; so, we have successfully changed the value of the variable it uses in its operation.

Ok. We know how to get and set state in the module; let's try altering the code. First we'll look at the body of the Get-Count function:

Although we've redefined the function in the module, we have to re-import the module in order to get the new definition into our function table.

Now that we've done that, we can call the function again to make sure we're getting what we expected.

and yes, Get-Count is now incrementing by 2 instead of 1.

All of the tweaks that we've been making on the module only affect the module in memory. The module file on disk hasn't changed:

If we use the -Force parameter on Import-Module, we'll force the system to reload the file from disk, reinitializing everything to the way it was:

This is one of the characteristics of dynamic languages - the ability of programs to modify themselves in a profound way at runtime and then restore the original state. Next, we'll look at how to we can use properties on the PSModuleInfo to access the members of a module without importing them.

Accessing modules exports using the PSModuleInfo object

The exported members of a module are discoverable through properties on the PSModuleInfo object that represents the module. This gives us a way to look at the exported members without having to import them into our environment. For example, the list of exported functions is available in the ExportedFunctions member. These properties are hashtables, indexed by the name of the exported member. Let's look at some examples of what we can do using these properties.

As always, we need a module to work with. In this case, we'll use a dynamic module. Dynamic modules don't require a file on disk which makes them easy to use for experiments. We'll create a dynamic module and save the PSModuleInfo object in a variable $m.

and now we can use the export lists on the PSModuleInfo to see what was exported.

In the output, we see that one function and one variable are exported. We also see the function turns up in the ExportedCommands member. Modules can export more than one type of command - functions, aliases, or cmdlets - and this property exists to provide a convenient way to see all commands regardless of type.

Author's Note

By implementing the exported member properties as hashtables, we allow you to access and manipulate the state of the module in a fairly convenient way. The downside is that the default output for the exported members is a bit strange, especially for functions where we see things like [foo, foo]. These tables map the name of a command to the CommandInfo object for that command. When the contents of the table are displayed, both the key and the value are displayed as strings. And, since the presentation of a CommandInfo object as a string is the name of the object, we see the name twice.

Let's use the ExportedFunctions property to see how the function foo is defined. We'll use the property syntax instead of explicit indexing to access the member because it's easier to write:

The value returned from the expression is a CommandInfo object. This means that we can use the call operator '&' to invoke this function:

We can also use the PSModuleInfo object to change the value of the exported variable $x:

Call the function again to validate this change.

and the return value from the call is the updated value as expected. Next, we'll look at some of the methods on PSModuleInfo objects.

Using the PSModuleInfo methods

The call operator is not the only way to use the module info object. The object itself has a number of methods that can be useful. Let's take a look at some of these methods:

We'll cover the first two listed Invoke() and NewBoundScriptBlock(). (Check out my book for a detailed coverage of AsCustomObject().)

The Invoke() method

This method is essentially a .NET programmer's way of doing what we did earlier with the call operator. Assuming we still have our counter module loaded, let's use this method to reset the count and change the increment to 5. First get the module info object:

Now invoke a scriptblock in the module context using the method:

The corresponding invocation using the call operator would b

which is more scripter friendly. Either way, let's try to verify the result.

And the count was reset and Get-Count now increments by 5 instead of 1. Next we'll look at a way to attach modules to a scriptblock.

The NewBoundScriptBlock() method

A module-bound scriptblock is a piece of code - a scriptblock - that has the module context to use attached to it. Normally an unbound scriptblock is executed in the caller's context but, once a scriptblock is bound to a module, it always executes in the module context. In fact that's how exported functions work - they are implicitly bound to the module that defined them.

Let's use this mechanism to define a scriptblock that will execute in the context of the counter module. First we need to get the module (again). We could use Get-Module as before but now that we know that exported functions are bound to a module, we can just use the Module property on an exported command to get the module info object. Let's do this with Get-Count.

Now we can get the module for this command

Next we need to define the scriptblock we're going to bind. We'll do this and place the scriptblock into a variable.

This scriptblock takes a single parameter which it uses to set the module level $increment variable. Now let's bind it to the target module. Note that this doesn't bind the module to the original scriptblock. Instead is creates a new scriptblock with the module attached.

Now test using the scriptblock to set the increment. Invoke the scriptblock with the call operator passing in an increment of 10

And verify that the increment has been changed.

OK, good! However, if we want to use this mechanism frequently, it would be useful to actually have a named function. We can do this by assigning to the function drive.

And now the increment is 100 per the argument to the Set-CountIncrement. Now let's use Get-Command to look at the function we've defined:

and, like Get-Count, it's listed as being associated with the counter module. Now that we've introduced the idea of a function being dynamically attached to a module, we really should have a more in-depth discussion about the context where a function gets evaluated. We'll do just that now.

The Defining Module vs. the Calling Module

Here, we'll go into more detail about how the execution context for a module is established.

Commands always have two module contexts - the context where they were defined and the context they were called from. This is a somewhat subtle concept. Before PowerShell had modules, this wasn't terribly interesting except for getting filename and line number information for the location where the function was called and where it was defined. With modules, this distinction becomes more significant. Among other things, the module where the command was defined contains the module specific resources like the manifest PrivateData. For functions, the ability to access the two contexts allows the function to access the caller's variables instead of the module variables.

Accessing the defining module

The module in which a function was defined can be retrieved by using the expression $MyInvocation.MyCommand.Module. Similarly, the module in which a cmdlet was defined is available through the instance property this.MyInvocation.MyCommand.Module. If the function is defined in the global scope (or 'top level'), the module field will be $null. Let's try this. First define a function at the top level.

then run it, formatting the result as a list showing the module name and PrivateData fields.

Nothing was output because the defining module at the top level is always null. Now let's define the function inside a module. We'll use a here-document to create a .psm1 file.

Now load the file and run the same test command as we did previously.

This time the result of the function was not null - we see the module name and, of course, the PrivateData field is empty because there was no module manifest to provide this data. We can remedy this by creating a module manifest to go along with the .psm1 file. This abbreviated manifest defines the minimum - the module version, then module to process - and specifies a hash table for PrivateData.

Now load the module using the manifest and -force to make sure everything gets updated.

Run the test command:

and we see that the PrivateData field is now also filled in.

Accessing the calling module

The module from which a function was called can be retrieved using the expression $PSCmdlet.SessionState.Module. Similarly, the module a cmdlet was called from is available through this.SessionState.Module. In either case, if the command is being invoked from the top level, this value will be null because there is no "global module".

Author's Note

It's unfortunate that we didn't get a chance to wrap the global session state in a module before we shipped. This means that this kind of code has to be special cased for the module being $null some of the time.

Working with both contexts

Now let's look at a very tricky scenario where we access both contexts at once. This is something that is rarely necessary but, when needed, absolutely required.

In functions and script modules, accessing the module session is trivial since unqualified variables are resolved in the module context by default. To access the caller's context we need to use the caller's session state which is available as a property on $PSCmdlet. Let's update the Test-ModuleContext module to access a variable "testv" both in the caller's context and the module context. Here's the module definition.

This defines our test function, specifying the cmdlet binding to be used so we can access $PSCmdlet. The module body also defines a module-scoped variable $testv. The test function will emit the value of this variable and then use the expression

to get the value of the caller's $testv variable. Let's load the module

Now define a global $testv

and run the command.

And we see the module $testv was correctly displayed as "123" and the caller's variable is the global value "456". Now, wait a minute, you say! We could have done this much more easily by simply specifying $global:testv. This is true if we were only interested in accessing variables at the global level. But sometimes we want to get the local variable in the caller's dynamic scope. Let's try this. We'll define a new function "nested" that will set a local $testv.

This function-scoped $testv variable is the "caller's variable" we want to access so we should get "789" instead of the global value "456". Let's try this:

and it works. The module $testv was returned as "123" and the caller's testv returned the value of the function-scoped variable instead of the global variable.

So when would we need this functionality? If we want to write a function that manipulates the caller's scope - say, something like the Set-Variable cmdlet implemented as a function, then we'd need this capability. Another time we might need to do this is when we want to access the value of locally scoped configuration variables like $OFS.

Setting module properties from inside a script module

Manifests are required to set metadata on a module but it turns out that there is a way for the script module to do some of this itself during the module load operation. In order to do this it needs to have access to its own PSModuleInfo object during the load. This can be retrieved using the rather awkward expression

$MyInvocation.MyCommand.ScriptBlock.Module

However, once we have the PSModuleInfo object, the rest is easy. Let's try it out by setting the Description property on our own module.

Setting the module description

In this example, we're going to set the Description property for a module from within the module itself. We'll create a module file in the current directory called setdescription.psm1. Let's look at the contents of this file:

On the first line of the module, we copy the reference to the PSModuleInfo object into a variable $mInfo. On the second line, we assign a value to the Description property on that object. Let's try it out. We'll import the module

and then call Get-Module, piping into Format-List so we can just see the module name and its description.

and there we go. We've dynamically set the Description property on our module.

As well as being able to set this type of metadata entry on the PSModuleInfo object, there are a couple of behaviors we can control as well. We'll look at how this works right now.

Controlling when modules can be unloaded

The module AccessMode feature allows us to restrict when a module can be unloaded. There are two flavors of restriction: static and constant. A static module is a module that can't be removed unless the -Force option is used on the Remove-Module cmdlet. A constant module can never be unloaded and will remain in memory until the session that loaded it ends. This model parallels the pattern for making variables and functions constant.

To make a module either static or constant, we need to set the AccessMode property on the module's PSModuleInfo object to the appropriate setting. Set it to ReadOnly for static modules and Constant for constant modules. Let's look at how this is done. Here's an example script module called readonly.psm1 that makes itself ReadOnly.

The first line of the module is the same as in the previous example. It retrieves the PSModuleInfo object. The next line sets the AccessMode to "readonly". We'll load this module and verify the behavior.

We've verified that it's been loaded so let's try and remove it:

When we try and remove the module, we get an error stating that -Force must be used to remove it. Let's do that.

This time we don't get an error. We verify that the module has been removed by calling Get-Module:

Nothing was returned confirming that the module has been removed. The same approach is used to mark a module as constant.

And now, the final feature we're going to cover: how to run an action when a module is unloaded.

Running an action when a module is removed

Sometimes we need to do some to clean-up when a module is unloaded. For example, if the module establishes a persistent connection to a server, when the module is unloaded, we'll want that connection to be closed. An example of this pattern can be seen in implicit remoting. The PSModuleInfo object provides a way to do this through its OnRemove property.

To set up an action to execute when a module is unloaded, assign a scriptblock defining the action to the OnRemove property on the module's PSModuleInfo object. Here is an example that shows how this is done:

then remove it

and the message from the scriptblock was printed confirming that the OnRemove action was executed.

Summary

Here are some of the key topics we covered:

  • Modules in memory are represented by a PSModuleInfo object. This object allows us to perform a number of advanced scenarios with modules.
  • The PSModuleInfo object for a module can be retrieved using Get-Module. Alternatively, the module object for a function can be retrieved using the Module property on scriptblock for that function.
  • If we have access to the PSModuleInfo object for a module, we can inject code into the module where it will be executed in the module context. This allows us to manipulate the state of a module without having to reload it. This feature is primarily intended for diagnostic and debugging purposes.
  • From within a script module, we can use the PSModuleInfo object to directly set some metadata elements, like the module description.
  • PSModuleInfo objects have an AccessMode field that controls the ability to update or remove a module from the session. This field is set to ReadWrite by default but can be set to Static, requiring the use of the -Force parameter to update it or Constant where it cannot be removed from the session. A Constant module remains in the session until the session ends.
  • To set up an action to be taken when a module is removed, a scriptblock can be assigned to the OnRemove property on the PSModuleInfo object for that module.
Get 30% discount

DotNetSlacker readers can get 30% off the full print book or ebook at www.manning.com using the promo code dns30 at checkout.

Converting a PowerShell Script into a Module Series

<<  Previous Article Continue reading and see our next or previous articles Next Article >>

About Manning Publications

Manning Publication publishes computer books for professionals--programmers, system administrators, designers, architects, managers and others. Our focus is on computing titles at professional levels. We care about the quality of our books. We work with our authors to coax out of them the best writi...

This author has published 33 articles on DotNetSlackers. View other articles or the complete profile here.

Other articles in this category


Developing a Hello World Java Application and Deploying it in Windows Azure - Part I
This article demonstrates how to install Windows Azure Plugin for Eclipse, create a Hello World appl...
Android for .NET Developers - Building a Twitter Client
In this article, I'll discuss the features and capabilities required by an Android application to ta...
Ref and Out (The Inside Story)
Knowing the power of ref and out, a developer will certainly make full use of this feature of parame...
Developing a Hello World Java Application and Deploying it in Windows Azure - Part II
In this article we will see the steps involved in deploying the WAR created in the first part of thi...
Android for .NET Developers - Using Web Views
In this article, I'll show a native app that contains a web-based view. The great news is that HTML ...

You might also be interested in the following related blog posts


Why is ASP.NET encoding &s in script URLs? A tale of looking at entirely the wrong place for a cause to a non-existing bug. read more
Twilight: A Silverlight Twitter Badge read more
Copy Pictures To Folders By Date Taken with Powershell read more
New Release: BGI SCORM LMS Portal v2.5.0 - New Certification Manager with WYSIWYG Certificate Designer - DotNetNuke Integrated read more
How my team does agile read more
Anatomy of a Subtle JSON Vulnerability read more
Utilizing the Microsoft AJAX Framework and ClientAPI to Develop Rich Modules: Part III read more
Utilizing the Microsoft AJAX Framework and ClientAPI to Develop Rich Modules: Part II read more
URL Rewrite for IIS - SEO Friendly URLs love it ! read more
EOAST - Evolution of a software thingy - Part 1 read more
Top
 
 
 

Discussion


Subject Author Date
placeholder Same function name exported from two different Powershell Modules Bernd Kriszio 3/14/2010 6:28 AM

Please login to rate or to leave a comment.

Free Agile Project Management Tool from Telerik
TeamPulse Community Edition helps your team effectively capture requirements, manage project plans, assign and track work, and most importantly, be continually connected with each other.