Windows PowerShell is an extensible automation engine consisting of a
command-line interpreter and a scripting language. It is completely written in
.NET and provides system administrators a powerful tool for automating
administration tasks.
Work is performed through so-called cmdlets (pronounced commandlets),
which are actually no more than registered implementations of PowerShell API
base classes which can be combined at will in PowerShell scripts.
In this post
series we'll explore the possibilities of connecting to and querying, as
well as performing some administrative tasks in ADAM through the PowerShell
interface. We will do this by developing a custom, ADAM-enabled PowerShell
snap-in.
Getting PowerShell
PowerShell 2.0 (which is the version we're going to use) is shipped with Windows
7, for other versions of Windows, please refer to
http://support.microsoft.com/kb/968929 to download the correct version for
your system.
Setting up the project
Using Visual Studio 2010, create a new library project named
Adam.PowerShell.Core and add references to the following assemblies:
- Adam.Core.dll
- Adam.Tools.dll
- System.Management.Automation.dll
- System.Configuration.Install.dll
Also, add a new class named AdamSnapIn
and deriving from the PSSnapIn class. The code listing for this class is shown below.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
[RunInstaller(true)]
public sealed class AdamSnapIn : PSSnapIn
{
public override string Description
{
get { return "This is a PowerShell snap-in that provides several ADAM cmdlets."; }
}
public override string Name
{
get { return "AdamProviderSnapin100"; }
}
public override string Vendor
{
get { return "ADAM Software"; }
}
}
|
This class enables our PowerShell snap-in to register itself within PowerShell
and provides descriptive information about the snap-in. All PowerShell providers
and cmdlets within the project will automagically be discovered and registered
when the snap-in is added to the PowerShell context.
Registering our PowerShell snap-in
In order for our PowerShell provider to work its magic, we need to register it in
the PowerShell environment first. This is done by running the InstallUtil.exe
utility on the output assembly of the project. The snap-in will register itself
using the information provided in the AdamSnapIn class (and its base class).
| ASP.NET |
1
2
|
Set-Alias InstallUtil $env:windir\Microsoft.NET\Framework\v2.0.50727\installutil
InstallUtil /i C:\ADAM\Samples\PowerShell\bin\Debug\Adam.PowerShell.Core.dll
|
You can verify that the snap-in registered correctly by running the
Get-PSSnapIn cmdlet with the Registered parameter, as shown below.
The output is the list of registered snap-ins and should contain our provider as
well.
| ASP.NET |
1
|
Get-PSSnapin -Registered
|
Now that we have registered our snap-in, it still isn't loaded into the current
console context. Therefore, we need to execute the Add-PSSnapIn cmdlet,
as shown below. If the operation is successful, we will be able to run our own
cmdlets.
| ASP.NET |
1
|
Add-PSSnapin AdamProviderSnapin100
|
Adding our first cmdlet
Now we have the basic infrastructure set up, let's create a PowerShell
cmdlet
that enables us to start the ADAM maintenance manager. Basically, we will create
a Adam.Core.CommandLine RunMaintenanceJobs command on steroids.
First off, we start by creating a
RunMaintenanceJobsCmdlet class deriving from the Cmdlet
class, and adding all available arguments as instance properties in this class.
Note that we can use the actual type of the properties, PowerShell will take the
burden of trying to convert them to what our cmdlet wants (and report errors
accordingly).
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
[Parameter]
public string Registration
{
get
{
return _Registration;
}
set
{
_Registration = value;
}
}
[Parameter]
public PSCredential Credential
{
get
{
return _Credential;
}
set
{
_Credential = value;
}
}
[Parameter]
public SwitchParameter TakeOver
{
get
{
return _TakeOver;
}
set
{
_TakeOver = value;
}
}
// Various other parameters go here...
|
Most notable in the code above is the PSCredential class used as the type for
the Credential
property. This class is part of the PowerShell API and contains a set of
security credentials. As we'll see later, we can use the built-in
Get-Credential cmdlet to pop up a credentials dialog and pass this into our
parameter.
Also worth mentioning is the SwitchParameter class used by the TakeOver property. This parameter is a switch and will only be set to true if the parameter is
actually defined on the command line. The SwitchParameter type automatically takes care of this.
Now let's get some processing logic up in this cmdlet.
To implement processing logic, you can
override four methods from the Cmdlet base class: BeginProcessing,
ProcessRecord, EndProcessing and StopProcessing.
The BeginProcessing method is executed after the command line arguments are
parsed and is therefore suitable for initializing resources like streams and
database connections. Here we use it to initialize the ADAM maintenance manager
and logging system.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
protected override void BeginProcessing()
{
// Make sure we can sucessfully log on and execute maintenance jobs.
App.DemandRole(RoleType.ExecuteMaintenanceJobs);
// Initialize the log listeners.)
if (LogManager.Listeners.Count == 0)
{
EventLogListener.Source = "ADAM PowerShell Provider";
LogManager.Listeners.Add(new ConsoleLogListener(LogSeverity.Normal));
LogManager.Listeners.Add(new EventLogListener(LogSeverity.Normal));
}
MaintenanceManager.IsRunningUnattended = true;
}
|
The ProcessRecord method is invoked for every record passed to the cmdlet.
A record here is not an ADAM record, but any object passed to the cmdlet through
the pipeline. Our first version will not provide any pipelining support but
we're going to override this method to start the maintenance manager threads.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
protected override void ProcessRecord()
{
// Creating all the necessary threads...
_WorkerThreads = new List<Thread>();
for (int i = 0; i < Threads; i++)
{
Thread thread = new Thread(RunMaintenanceJobsThreadStart);
thread.Start(this);
_WorkerThreads.Add(thread);
}
// Waiting until they're all done...
for (int index = 0; index < Threads; index++)
{
_WorkerThreads[index].Join();
}
}
private void RunMaintenanceJobsThreadStart(object obj)
{
// Initialize and run the ADAM maintenance manager.
}
|
The EndProcessing method is executed when the cmdlet is finishing and the
StopProcessing method is called when the user aborts the cmdlet execution using
the CTRL+C keystroke. In here we will just log off from ADAM in order to clean up
after ourselves.
Running the cmdlet
Let's try and run our cmdlet in PowerShell.
First off, remember you need to register the provider and add the snap-in to the
console context (scroll up, man!). After this, our cmdlet will be available from the shell:
| ASP.NET |
1
2
|
$Administrator = Get-Credential Administrator
Run-AdamMaintenanceJobs -Registration AdamUnitTests -Credential $Administrator
|
Notice how we use the existing Get-Credential cmdlet to provide a value
for our credential parameter. This illustrates how any parameter value can come
from variables in PowerShell. You can also inline the Get-Credential cmdlet if
you don't want to use variables:
| ASP.NET |
1
|
Run-AdamMaintenanceJobs -Registration AdamUnitTests -Credential (Get-Credential Administrator)
|
Another cool feature of PowerShell worth mentioning at this point is command
completion. When you're writing your command, you can use the TAB-key to
cycle through commands, parameter names and such.
Wrapping it up
This concludes our little introduction to PowerShell and cmdlet development in
combination with ADAM (for now). Given this example, you should be able to
develop more potent cmdlets. Also, stay tuned on this series as I will expand
this provider further and elaborate more on the intricacies of PowerShell
development in future articles.
As always, feel free to share your
experiences in the comments. Live long and prosper!