My “getting started” series ran out of steam a bit partly because I didn’t get much feedback on them — maybe you’re not interested, or maybe it wasn’t easy enough, or was just too confusing. In any case, I want to put up at least this one last post to suggest that you get the PowerShell Code Repository set up, and to show you the final version of my profile script and how it loads the various pieces it needs, and then I’ll send you on your way.
Once you’ve got your PowerShell all installed and have set up your first profile to auto-load … you’re going to want some scripts (well, maybe you’ll want to learn more about how to use PowerShell, but go with me on this)!
One of the best places to look for scripts is the PowerShell Code Repository, and although you can browse and search on the website, you can also do it using the PoshCode module (or the version 1 compatible script). These scripts include a Get-PoshCode cmdlet which you can use with search terms to get a list of scripts and cmdlets back, or with numeric IDs to download scripts (you’ll see what I mean later on, for now, go ahead and grab the appropriate version of that script).
I’m going to assume you put it into your AutoModules folder. If you grabbed the module, it should be saved to WindowsPowerShell\AutoModules\PoshCode\PoshCode.psm1 otherwise, to WindowsPowerShell\AutoModules\PoshCode.ps1 … but you may have run into a minor problem if you load the .ps1 version ). Both the module and the script are signed, but they are signed by my self-issued code signing certificate which your computer almost certainly doesn’t trust… You can use the signature to verify that the file hasn’t been modified since I signed it, but that’s about all (and even that’s a bit of a trick). To actually use the script (module), you’ll need to sign the script yourself (see the steps and how to get a certificate in part 2).
If you’re on CTP2, this would be a good time to get my Authenticode script module to help with signing, and to learn a little about those PoshCode cmdlets … (more…)
This continues a short series of posts about getting started with PowerShell … with a few tips about things you can do to keep your PowerShell profile safe and organized. Your “profile” is the script that is automatically loaded when you start up PowerShell. Really, I should say that your profile is the set of scripts which are loaded by default when you start up PowerShell. “By default” because you can always skip loading them by passing the -NoProfile switch to PowerShell.exe, and a set because PowerShell does, in fact, attempt to load at least four scripts when you run it:
PowerShell loads “machine” profile scripts (which are located in the PowerShell folder) and “user” profile scripts (located in your Documents\WindowsPowerShell folder). But there’s a little more to it than that: PowerShell is a scripting engine which can be hosted inside any app, PowerShell.exe is a DOS-style console which is the default host. There are several third-party hosts available such as PowerShell Plus and PowerGUI and several open source hosts such as BgShell and PoshConsole … in order to support this ecosystem of hosts, the default PowerShell behavior is to load a host-specific profile script (for both the machine settings and the local-user settings). Not all hosts will do that, but anyway … the default host loads Microsoft.PowerShell_profile.ps1 and Profile.ps1 from both the user and machine locations.
By default, none of those profiles actually exist. Once you’ve installed everything as in Part 1, you should have a Profile.ps1 file provided by PSCX. This profile defines a whole bunch of values that are used by various PSCX cmdlets and scripts, so you may want to change some of it, but you should be careful about just deleting things until you’re well acquainted with the PSCX cmdlets. Over time, I’ve added settings to my profile for other snapins as well, and there gets to be a lot of noise in there that’s specific to different snapins, so instead of just leaving all of that in my main profile, I rename the Profile.ps1 file provided by PSCX and then dot-source it from a new blank profile script.
In fact, and I found that I started collecting a lot of scripts in my WindowsPowerShell folder so I created a sub-folder for them, and I automatically load everything that’s in that folder, so I don’t have to manually dot-source things when I add a new snapin profile.
In order to make sure that automatically loading scripts doesn’t become a way for people to attack my computer, I made a decision awhile ago that I would only auto-load signed scripts. The how and why of this is a bit much to get into, and I wrote about Generating Windows Authenticode Code-Signing Certificates with OpenSSL a while back, so you can read that if you want more details, I want to review the simplest steps. (more…)
26 May
Modules are a new feature of PowerShell v2 which were added to CTP2 with very little fanfare and no documentation. After struggling with the demo from the release notes, and searching in vain for help files, I was finally able to figure some of this out with some help from x0n of Nivot Ink and since I still can’t find anything on the web about modules, I figured I’d share what little I know, even though I’m sure the moment I do the PowerShell team will write it up properly on their blog
(
Edit I was off by three months, but the PowerShell team has finally started explaining these) ... let’s start with a direct quote from the release notes:
Modules allow script developers and administrators to partition and organize their Windows PowerShell code in self-contained, reusable units. Code from a module executes in its own self-contained context and does not affect the state outside of the module. Modules also enable you to define a restricted runspace environment by using a script.
The point of a script module is that it allows much better control over what portions of a script are exposed in your runspace. Code from a module executes in its own self-contained context and does not have to affect the state outside of the module. Modules have access to the $PsScriptRoot variable which holds the path to the folder the script is running in, which allows you to dot-source additional scripts from the module directory, so you can split up your code into multiple files however you like — perhaps a file per function, or whatever works for you. Modules can even define a restricted runspace environment. If you’re a programmer, the best analogy seems to be to think of a powershell script module as basically a “class” with public and private methods and members. Obviously it can be a bit more than that, since cmdlets are rather more than simple methods, but try to keep that model in mind.
One key feature of modules (for scripters) is that the self-contained state of modules is also persistent, and there can be private and public member functions (or cmdlets). This might be the key feature of script modules: Script-scope variables are visible to all functions loaded inside the module, and their state and values are persistent through multiple calls to cmdlets or functions from the same module. They are invisible to code outside the module, which means that multiple functions that work together don’t need to store settings or state in the global scope, and don’t have to worry about name clashes (other than the publically exported members, we’ll get to that later).
As I’ve been experimenting, it turns out there’s way more to this than simple script modules. Modules appear destined to replace snapins as the main way to extend PowerShell. In fact, in the current CTP2 you can load most existing snapins as a module instead, which means you don’t have to be an administrator to load a new snapin — there’s no need to registery them by running InstallUtil.exe — you can simply place them in a folder and tell PowerShell where to find them.
You can load any script as a module by specifying the full path as it’s name — that’s not the best way to handle modules. The best way is to create a folder with the module’s name inside a package directory. The environment variable PsPackagePath (which I believe will be changed to PSModulePath or even ModulePath in the next release) contains a list of folders where modules can be placed. The defaults are two folders called “Packages,” one in your Documents\WindowsPowerShell and one in $PSHome … the folders aren’t created by the CTP2 installer, and they’ll probably be changed to “Modules” in a future release.
In one of those path folders (or the default locations), you simply create a folder with the package name, and then put your module in it with the same name as it’s base name and either a .ps1, .psm1, .dll or .psd1 extension PSD1 files don’t actually work in CTP2. Having done that, you can load them by name without specifying the path. The cool thing about this is that you can actually create a folder on a USB or network drive, and stick all your modules in it … and then just add that folder to your PsPackagePath environment variable. It’s just like any other path variable, so you can specify multiple directory paths separated by semi-colons.
The key thing to understand (excuse me for repeating myself) is what PowerShell expects: inside the PsPackagePath there should be a folder for each module, and the name of the folder becomes the name by which you call the module — the module file and the folder should have the same name, so for instance if you want to load a script TestModule.ps1 as a module, you need to put it in ~\Documents\WindowsPowerShell\Packages\TestModule\TestModule.ps1 and then you can just Add-Module TestModule. The name of the folder is the name of the module, and the main module file must have the same name even if it’s a dll module.
Here is my TestModule.psm1 script. This example is fun because it demonstrates quite vividly the difference between a psm1 module and a ps1 script:
If you comment out the last line of that script and name it TestModule.ps1 (after putting it in an appropriate folder), you can Add-Module TestModule and run fooize and barize … they work fine. The problem is that the $count variable leaks into your runspace, and since it isn’t initialized as part of the script, you can’t reset it by re-running Add-Module TestModule, even with the -Force parameter, and Remove-Module TestModule won’t work. You can also arbitrarily access the count variable: "The count is $count" will print the current value, and $count = 42 will change it without running the associated code.
However, if you rename it to TestModule.psm1 and restore the Export-ModuleMember call … then when you run it, it has it’s own internal $count variable. In fact, if you do that without resetting your runspace from the previous example, you’ll see that even though your $count is still at 42, the fooize and barize functions are using a separate counter which you can’t modify externally. If you run Add-Module TestModule -Force now, the $count variable will reset!
There are so far four cmdlets which are available for dealing with modules, but there isn’t any help or documentation for any of them, short of the release notes, so apart from what I’ve said so far, let me give you their parameter set definitions, since that’s the best we’ve got, and I’ll add my explanations, as I understand them.
Get-Module [[-Name] <String[]>]Get-Module lets you get all, or some, of the loaded Modules. It returns PSModuleInfo objects for them which can be used in the Add and Remove cmdlets.
Remove-Module [-Name] <String[]>Remove-Module -ModuleInfo <PSModuleInfo[]>Oddly enough, in the current CTP Remove-Module doesn’t really do what you expect. As far as I can tell, all that it does, is removes the PSmoduleInfo from the list of loaded modules. This means you can run add-module against it again (otherwise, running Add-Module a second time on a module that’s already loaded does nothing unless you use the -Force parameter) but it doesn’t really do anything else. In fact, you can continue to use cmdlets and functions from these “removed” modules without any problems that I can see so far. I’m not sure if this is considered a bug, or if the team expects to leave it like this. I dearly hope that in the next CTP we will at least see the cmdlets and functions removed so they can’t be executed, especially since I can already remove the functions by hand by simply deleting them from the provider.
Add-Module [-Name] <String[]> [[-ImportList] <String[]>] [-Force] [-PassThru] [-ScriptBlock <ScriptBlock>]Add-Module [-Name] <String[]> [-ScriptBlock] <ScriptBlock> [[-ImportList] <String[]>] [-Force] [-PassThru]Add-Module [-Force] [-PassThru] [-ScriptBlock <ScriptBlock>] [-ModuleInfo <PSModuleInfo[]>]The Add-Module cmdlet is the main cmdlet that provides the functionality of the modules. It loads modules into your runspace, and it can load various types of modules:
I’ll fill in the details of how the ImportList works when I figure it out, I think it allows you to (further) restrict what is exported into your runspace from the module. You can use the -Force parameter to reload scripts you’ve already loaded, and you can specify -PassThru to have the PSModuleInfo object output from Add-Module (the same as running Get-Module)...
When you run Add-Module -Force, it reinitializes the script scope for the module. Take for example the previous script module named TestModule.psm1. If you Add-Module TestModule and then run “fooize” ... each time you run it, the number at the end of the string will increment. When you re-run Add-Module TestModule with the -Force parameter, the “count” variable is reset, but otherwise it remains, and even after you Remove-Module Test you can still run fooize and barize!
If you’re working on a compiled PowerShell snapin, you need to start working right now on what it would take to get it to load as a module, because unlike Snapins, Modules can be loaded by path, and more importantly, do not need installation by an administrator. This is going to mean that when PowerShell 2 becomes final, end users are going to have a clear preference for modules which are easier to trust (since they can be run entirely non-elevated), and easier to work with (since they can be carried on a memory stick or disk and loaded without installing on any computer).
Export-ModuleMember [-ExportList] <String[]> [-Update]The final module cmdlet is actually the second most important one — without it, nothing in your PowerShell script modules would be visible after you load them. Generally speaking, you run Export-ModuleMember and pass it a list of function or cmdlet names which you wish to export — to make them visible from outside the script.
Exporting a module member is analogous to marking a C# member method public, in that it marks it for use by external scripts, etc. You shouldn’t use this cmdlet in a plain .ps1 script (or on the command-line) because it won’t work, and it actually does weird things (like killing my profile) that I can’t explain
.
In terms of “protecting” your scripts from malicious users you should know that it’s possible to access the “private” variables and methods in a cmdlet, but it’s not possible to “change” them from outside. So for instance, if we take the example TestModule again — you’ll notice there’s a function at the bottom that we haven’t exported and that isn’t used at all — but which would allow us to set the count value. Let me demonstrate why: