Deze post is geïmporteerd van de oude blog en is nog niet geconverteerd naar de nieuwe syntax.
The prompt is dead, long live the prompt! That's the feeling PowerShell should give you, the new command line shell from Microsoft. Today, I'll try to determine exactly what the Power part in PowerShell stands for.
What exactly makes it so powerful?
I belief it will gain its fair share of supporters from the fact that it's so extensible. To demonstrate it's flexibility, I'll be writing a small extension today, a so called Cmdlet. By the end of the post, this should be the result:
As you can see, it looks just like a normal command prompt, on steroids however.
First of all, PowerShell has tab completion, not only for file and directory names, but also for commands and parameters. Typing 'Get-H' and hitting tab will cycle through 'Get-History', 'Get-Host' and 'Get-Help'. Typing a dash behind a command, indicating you want to add a property and hitting tab will cycle through all available properties for the command, for example typing 'Format-List -' and hitting tab will give you 'Format-List -Property' followed by all other properties.
Another thing which gives PowerShell its power is the fact that it runs on .NET, which enables you to write things like:
[code]
PS C:\> $arr = New-Object System.Collections.ArrayList
PS C:\> $arr.Add("David")
PS C:\> $arr.Add("Cumps")
PS C:\> $arr.Count
2
PS C:\> $arr
David
Cumps
[/code]
Besides typing these commands in the shell itself, you can also write them in a text file, with the extension .ps1, and execute these scripts in the shell. To execute script however, the security has to be changed first, since by default PowerShell will only run signed scripts, no matter where they come from.
To change the security to run your own unsigned scripts, but still require downloaded scripts to be signed, type the following into PowerShell:
[code]
Set-ExecutionPolicy RemoteSigned
[/code]
Just like known Linux shells, PowerShell always runs a script when you start it, the so called profile script. Typing $profile in PowerShell will display its location on your system. Initially no profile will exist yet, so let's set one up...
Type 'notepad $profile' in PowerShell to open a new Notepad instance. When you run Vista as a non-admin, this might give a 'file does not exist' message. You can safely ignore this, as long as you save your file to the correct location afterwards (the path that $profile displayed).
The first change will be a very simple one, changing the title and window colors. Paste the following into Notepad and save it:
[code]
# Set up PowerShell looks
(Get-Host).UI.RawUI.BackgroundColor = "Black"
(Get-Host).UI.RawUI.ForegroundColor = "White"
(Get-Host).UI.RawUI.WindowTitle = "David Cumps » PowerShell"
# And go!
cls
[/code]
If you now restart PowerShell, or type '. $profile', the profile will be applied and you'll notice we have a standard black and white prompt again, with our custom title. This is the first custom script PowerShell has run on your computer. To get more scripts, have a look at The Script Center Script Repository, which is filled with scripts for various system administration purposes.
Scripts are scripts however, they simple use commands available to them. To see a list of all available commands enter 'Get-Command' and have a look, typing 'Get-Alias' will give you a list of all familiar DOS commands, mapped to their respective commands. The nice part is that we can create our own custom commands, called Cmdlets, written in any .NET language and simply plug them in to PowerShell, available to help us automate tasks even easier.
By default, Microsoft ships a collection of Cmdlets, which you saw earlier, to provide all the known DOS commands for us. With these basic Cmdlets, we can already do some nice things, for example if you wanted to get all running processes ordered by CPU time, you could simply type the following:
[code]
PS C:\> Get-Process | Where-Object {$_.CPU -gt 0} | Sort-Object CPU -descending
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
783 34 60768 62960 308 226,75 2040 explorer
404 15 112932 121448 243 163,38 5480 firefox
650 16 29720 25444 136 125,03 5052 winamp
53 2 1868 14292 48 34,22 2624 notepad
571 48 72824 67572 340 10,44 1964 devenv
[/code]
As you noticed, Cmdlets can easily be piped to each other to create powerful constructs. But, enough of the default things, let's build something!
The Cmdlet should support the following features:
A Cmdlet is nothing more then a Class Library which uses System.Management.Automation, inherits from Cmdlet and applies the Cmdlet attribute. Don't forget to add a reference to C:\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0\System.Management.Automation.dll when you create the new project, to get access to the Cmdlet base class and CmdletAttribute.
I'll let the code speak for itself, with a small recap afterwards.
[csharp]
using System;
using System.Management.Automation;
namespace CumpsD.HelloWorld
{
[Cmdlet(VerbsCommon.Get, "Hello")]
public class HelloCmdlet : Cmdlet
{
// An array is used to support the following scenario:
// Get-Process | Get-Hello
[Parameter(Position = 0, ValueFromPipelineByPropertyName = true, Mandatory = false, HelpMessage = "Name to greet.")]
public string[] Name { get; set; }
// Called when cmdlet is invoked.
protected override void BeginProcessing()
{
// You could open a database connection here for example.
base.BeginProcessing();
}
// Called when we have pipeline input.
protected override void ProcessRecord()
{
// this.Name could be null in BeginProcessing (no parameters passed)
// but could be filled when ProcessRecord gets called (parameters from pipeline)
if (this.Name == null)
{
// No arguments have been supplied, neither manual nor pipeline.
this.WriteObject("Hello World!");
}
else
{
foreach (string name in this.Name)
{
this.WriteObject(String.Format("Hello {0}!", name));
}
}
}
// Called after every record has been processed.
protected override void EndProcessing()
{
base.EndProcessing();
}
// Called when the user hits CTRL+C
protected override void StopProcessing()
{
base.StopProcessing();
}
}
}
[/csharp]
First of all, we define the name of our Cmdlet in the attribute, Get-Hello in this case. Then we create our only property as an array, to support multiple parameters being passed in, and specify that its allowed to retrieve its value from the pipeline. When it is called through the pipeline, it will take each object returned from the previous Cmdlet, look if there is a property called Name on there, and use that value for our property.
After this initial setup, it's time to implement the processing of the Cmdlet. The PowerShell infrastructure will always call BeginProcessing and ProcessRecord, even if you don't supply any parameters, as documented in the code above.
Simply coding up this class won't cut it however. Somehow it needs to get inserted into the PowerShell environment. For this, we create a very simple installer class, which will install all Cmdlets it can find in our assembly into PowerShell. Don't forget to add System.Configuration.Install as a reference to get access to the RunInstaller attribute.
[csharp]
using System;
using System.ComponentModel;
using System.Management.Automation;
namespace HelloWorld
{
[RunInstaller(true)]
public class HelloSnapIn : PSSnapIn
{
public override string Name
{
get { return "HelloSnapIn"; }
}
public override string Vendor
{
get { return "David Cumps"; }
}
public override string Description
{
get { return "This Windows PowerShell snap-in contains the Get-Hello cmdlet."; }
}
}
}
[/csharp]
That's all the code needed to create a Cmdlet! We could install it now, but let's make our lives easier first and configure Visual Studio to support debugging of this Cmdlet. Open the properties of the project and configure the Debug tab as follows:
Start external program: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
Command line arguments: -noexit -command icmd HelloWorld.dll HelloSnapIn
This will start PowerShell and install the latest version of our Cmdlet whenever we go in Debug mode. When you run it at this stage, it'll throw an error however, because it doesn't know the icmd alias yet. Simply add the following to the PowerShell profile:
[code]
# Needed for some commands where administrator permissions are needed
function elevate
{
$file, [string]$arguments = $args;
$psi = new-object System.Diagnostics.ProcessStartInfo $file;
$psi.UseShellExecute = $true;
$psi.Arguments = $arguments;
$psi.Verb = "runas";
$p = new-object System.Diagnostics.Process;
$p.StartInfo = $psi;
$p.Start();
$p.WaitForExit();
$p.Close();
}
# Easily install new cmdlets
function InstallSnappin([string]$dll, [string]$snappin)
{
$path = Get-Location;
$assembly = $path.Path + "\" + $dll;
elevate C:\Windows\Microsoft.NET\Framework\v2.0.50727\installutil.exe $assembly | out-null;
Add-PSSnapin $snappin | out-null;
Get-PSSnapin $snappin;
}
set-alias icmd InstallSnappin
[/code]
At this stage, going in Debug mode will build the assembly, open PowerShell, execute installutil as an admin, with a UAC prompt under Vista, and install the Cmdlet, after which you are ready to test it and more importantly, use breakpoints! Don't forget to modify the command line arguments in the Debug properties to reflect your assembly name and installer class name when you create a new project.
Let's have a look if we managed to implement our requirements:
[code]
PS C:\> # Called stand-alone without parameters.
PS C:\> Get-Hello
Hello World!
PS C:\> # Called stand-alone with one parameter.
PS C:\> Get-Hello "David Cumps"
Hello David Cumps!
PS C:\> # Called stand-alone with multiple parameters.
PS C:\> Get-Hello "David", "Readers"
Hello David!
Hello Readers!
PS C:\> # Called in a pipeline receiving input parameters.
PS C:\> Get-Process | Where-Object {$_.CPU -gt 10} | Sort-Object CPU -desc | Get-Hello
Hello explorer!
Hello dwm!
Hello winamp!
Hello OUTLOOK!
Hello firefox!
Hello notepad!
PS C:\> # Output its result to the pipeline.
PS C:\> Get-Hello "A", "C", "B"
Hello A!
Hello C!
Hello B!
PS C:\> Get-Hello "A", "C", "B" | Sort-Object
Hello A!
Hello B!
Hello C!
[/code]
Hooray! We managed to set our development environment up to be productive, being able to easily install the latest version of our Cmdlet and having the ability to debug, and we wrote a Cmdlet offering all the nice features needed to incorporate it into our future scripts. Mission successful I'd say.
As usual, I've uploaded a .zip file which includes the solution to the above Cmdlet, including the Debug properties and the full PowerShell profile I'm using.
What do you think about PowerShell? Will it make your life easier as a system administrator?
What exactly makes it so powerful?
I belief it will gain its fair share of supporters from the fact that it's so extensible. To demonstrate it's flexibility, I'll be writing a small extension today, a so called Cmdlet. By the end of the post, this should be the result:
As you can see, it looks just like a normal command prompt, on steroids however.
First of all, PowerShell has tab completion, not only for file and directory names, but also for commands and parameters. Typing 'Get-H' and hitting tab will cycle through 'Get-History', 'Get-Host' and 'Get-Help'. Typing a dash behind a command, indicating you want to add a property and hitting tab will cycle through all available properties for the command, for example typing 'Format-List -' and hitting tab will give you 'Format-List -Property' followed by all other properties.
Another thing which gives PowerShell its power is the fact that it runs on .NET, which enables you to write things like:
[code]
PS C:\> $arr = New-Object System.Collections.ArrayList
PS C:\> $arr.Add("David")
PS C:\> $arr.Add("Cumps")
PS C:\> $arr.Count
2
PS C:\> $arr
David
Cumps
[/code]
Besides typing these commands in the shell itself, you can also write them in a text file, with the extension .ps1, and execute these scripts in the shell. To execute script however, the security has to be changed first, since by default PowerShell will only run signed scripts, no matter where they come from.
To change the security to run your own unsigned scripts, but still require downloaded scripts to be signed, type the following into PowerShell:
[code]
Set-ExecutionPolicy RemoteSigned
[/code]
Just like known Linux shells, PowerShell always runs a script when you start it, the so called profile script. Typing $profile in PowerShell will display its location on your system. Initially no profile will exist yet, so let's set one up...
Type 'notepad $profile' in PowerShell to open a new Notepad instance. When you run Vista as a non-admin, this might give a 'file does not exist' message. You can safely ignore this, as long as you save your file to the correct location afterwards (the path that $profile displayed).
The first change will be a very simple one, changing the title and window colors. Paste the following into Notepad and save it:
[code]
# Set up PowerShell looks
(Get-Host).UI.RawUI.BackgroundColor = "Black"
(Get-Host).UI.RawUI.ForegroundColor = "White"
(Get-Host).UI.RawUI.WindowTitle = "David Cumps » PowerShell"
# And go!
cls
[/code]
If you now restart PowerShell, or type '. $profile', the profile will be applied and you'll notice we have a standard black and white prompt again, with our custom title. This is the first custom script PowerShell has run on your computer. To get more scripts, have a look at The Script Center Script Repository, which is filled with scripts for various system administration purposes.
Scripts are scripts however, they simple use commands available to them. To see a list of all available commands enter 'Get-Command' and have a look, typing 'Get-Alias' will give you a list of all familiar DOS commands, mapped to their respective commands. The nice part is that we can create our own custom commands, called Cmdlets, written in any .NET language and simply plug them in to PowerShell, available to help us automate tasks even easier.
By default, Microsoft ships a collection of Cmdlets, which you saw earlier, to provide all the known DOS commands for us. With these basic Cmdlets, we can already do some nice things, for example if you wanted to get all running processes ordered by CPU time, you could simply type the following:
[code]
PS C:\> Get-Process | Where-Object {$_.CPU -gt 0} | Sort-Object CPU -descending
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
783 34 60768 62960 308 226,75 2040 explorer
404 15 112932 121448 243 163,38 5480 firefox
650 16 29720 25444 136 125,03 5052 winamp
53 2 1868 14292 48 34,22 2624 notepad
571 48 72824 67572 340 10,44 1964 devenv
[/code]
As you noticed, Cmdlets can easily be piped to each other to create powerful constructs. But, enough of the default things, let's build something!
The Cmdlet should support the following features:
- Called stand-alone without parameters.
- Called stand-alone with one parameter.
- Called stand-alone with multiple parameters.
- Called in a pipeline receiving input parameters.
- Output its result to the pipeline.
A Cmdlet is nothing more then a Class Library which uses System.Management.Automation, inherits from Cmdlet and applies the Cmdlet attribute. Don't forget to add a reference to C:\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0\System.Management.Automation.dll when you create the new project, to get access to the Cmdlet base class and CmdletAttribute.
I'll let the code speak for itself, with a small recap afterwards.
[csharp]
using System;
using System.Management.Automation;
namespace CumpsD.HelloWorld
{
[Cmdlet(VerbsCommon.Get, "Hello")]
public class HelloCmdlet : Cmdlet
{
// An array is used to support the following scenario:
// Get-Process | Get-Hello
[Parameter(Position = 0, ValueFromPipelineByPropertyName = true, Mandatory = false, HelpMessage = "Name to greet.")]
public string[] Name { get; set; }
// Called when cmdlet is invoked.
protected override void BeginProcessing()
{
// You could open a database connection here for example.
base.BeginProcessing();
}
// Called when we have pipeline input.
protected override void ProcessRecord()
{
// this.Name could be null in BeginProcessing (no parameters passed)
// but could be filled when ProcessRecord gets called (parameters from pipeline)
if (this.Name == null)
{
// No arguments have been supplied, neither manual nor pipeline.
this.WriteObject("Hello World!");
}
else
{
foreach (string name in this.Name)
{
this.WriteObject(String.Format("Hello {0}!", name));
}
}
}
// Called after every record has been processed.
protected override void EndProcessing()
{
base.EndProcessing();
}
// Called when the user hits CTRL+C
protected override void StopProcessing()
{
base.StopProcessing();
}
}
}
[/csharp]
First of all, we define the name of our Cmdlet in the attribute, Get-Hello in this case. Then we create our only property as an array, to support multiple parameters being passed in, and specify that its allowed to retrieve its value from the pipeline. When it is called through the pipeline, it will take each object returned from the previous Cmdlet, look if there is a property called Name on there, and use that value for our property.
After this initial setup, it's time to implement the processing of the Cmdlet. The PowerShell infrastructure will always call BeginProcessing and ProcessRecord, even if you don't supply any parameters, as documented in the code above.
Simply coding up this class won't cut it however. Somehow it needs to get inserted into the PowerShell environment. For this, we create a very simple installer class, which will install all Cmdlets it can find in our assembly into PowerShell. Don't forget to add System.Configuration.Install as a reference to get access to the RunInstaller attribute.
[csharp]
using System;
using System.ComponentModel;
using System.Management.Automation;
namespace HelloWorld
{
[RunInstaller(true)]
public class HelloSnapIn : PSSnapIn
{
public override string Name
{
get { return "HelloSnapIn"; }
}
public override string Vendor
{
get { return "David Cumps"; }
}
public override string Description
{
get { return "This Windows PowerShell snap-in contains the Get-Hello cmdlet."; }
}
}
}
[/csharp]
That's all the code needed to create a Cmdlet! We could install it now, but let's make our lives easier first and configure Visual Studio to support debugging of this Cmdlet. Open the properties of the project and configure the Debug tab as follows:
Start external program: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
Command line arguments: -noexit -command icmd HelloWorld.dll HelloSnapIn
This will start PowerShell and install the latest version of our Cmdlet whenever we go in Debug mode. When you run it at this stage, it'll throw an error however, because it doesn't know the icmd alias yet. Simply add the following to the PowerShell profile:
[code]
# Needed for some commands where administrator permissions are needed
function elevate
{
$file, [string]$arguments = $args;
$psi = new-object System.Diagnostics.ProcessStartInfo $file;
$psi.UseShellExecute = $true;
$psi.Arguments = $arguments;
$psi.Verb = "runas";
$p = new-object System.Diagnostics.Process;
$p.StartInfo = $psi;
$p.Start();
$p.WaitForExit();
$p.Close();
}
# Easily install new cmdlets
function InstallSnappin([string]$dll, [string]$snappin)
{
$path = Get-Location;
$assembly = $path.Path + "\" + $dll;
elevate C:\Windows\Microsoft.NET\Framework\v2.0.50727\installutil.exe $assembly | out-null;
Add-PSSnapin $snappin | out-null;
Get-PSSnapin $snappin;
}
set-alias icmd InstallSnappin
[/code]
At this stage, going in Debug mode will build the assembly, open PowerShell, execute installutil as an admin, with a UAC prompt under Vista, and install the Cmdlet, after which you are ready to test it and more importantly, use breakpoints! Don't forget to modify the command line arguments in the Debug properties to reflect your assembly name and installer class name when you create a new project.
Let's have a look if we managed to implement our requirements:
[code]
PS C:\> # Called stand-alone without parameters.
PS C:\> Get-Hello
Hello World!
PS C:\> # Called stand-alone with one parameter.
PS C:\> Get-Hello "David Cumps"
Hello David Cumps!
PS C:\> # Called stand-alone with multiple parameters.
PS C:\> Get-Hello "David", "Readers"
Hello David!
Hello Readers!
PS C:\> # Called in a pipeline receiving input parameters.
PS C:\> Get-Process | Where-Object {$_.CPU -gt 10} | Sort-Object CPU -desc | Get-Hello
Hello explorer!
Hello dwm!
Hello winamp!
Hello OUTLOOK!
Hello firefox!
Hello notepad!
PS C:\> # Output its result to the pipeline.
PS C:\> Get-Hello "A", "C", "B"
Hello A!
Hello C!
Hello B!
PS C:\> Get-Hello "A", "C", "B" | Sort-Object
Hello A!
Hello B!
Hello C!
[/code]
Hooray! We managed to set our development environment up to be productive, being able to easily install the latest version of our Cmdlet and having the ability to debug, and we wrote a Cmdlet offering all the nice features needed to incorporate it into our future scripts. Mission successful I'd say.
As usual, I've uploaded a .zip file which includes the solution to the above Cmdlet, including the Debug properties and the full PowerShell profile I'm using.
What do you think about PowerShell? Will it make your life easier as a system administrator?
You are going to <i>love</i> the PowerShell cmdlets for Digipede that Robert Anderson will be releasing soon. Powerful stuff, and so darn cool. Long live the prompt!
O, sexy news :) Curious to see which features it'll have
Hello Mr. David. your blog about PowerShell cmdets is really helpful. I would like to ask u few cmdlet questions.
1.Could u plz tell me how can I write a cmdlet that creates a recursive function to print the following Fibonacci
sequence:
0,1,1,2,3,5,8
2. How can I launch any program (e.g.Calculator or notepad) through the PowerShell prompt?
3. Using Registry Provider, how can I find out if any hotfixes have been installed on my server.
I mean my cmdlet should print an output that clearly outlines if any hotfixes have been installed or not.
Thanks in advance.
thanks dude. may also want to mention that your sample source code is visual studio 2008. The following does not build in 2005.
Hello Again ; )
Just wanted to thank you again, your example worked great out of the box in VS 2008, and I am able to hit the ground running now. Much appreciated!!!!
I'm just starting to use Powershell (CTP V2) but to be honest I'm really failing to see any advantage over the DOS prompt and a decent scripting language such as Python or Perl. Both of those are as simple to install on Windows as the PowerShell *and* a development environment, don't have a compile/debug cycle, and have a wealth of existing code, libraries, and knowledge-base I can call on. And I'll always trust a CPAN library installation of a Perl module I can read the source of more than I will ever trust even a signed cmdLet I've downloaded from the internet. Python, Perl, Ruby... etc also have the advantage that they aren't platform specific so even though I work entirely on Windows I can ask for help from Unix/Solaris/Linux guys. Which I can't with Powershell.
The only feature I've wanted to use that I can't get from installing "your-choice-of script-language-here" is remoting, and then I find out that remoting doesn't work with my servers (2003 SE R2 + SP2) anyway.
/cough, ssh.
And even just trivially some of the command parameter passing reminds me of my days writing DCL scripts and libraries on VMS, and as far as I'm concerned that's not a good thing. And the inconsistent use of '-' or '_' as a word separator is just infuriating. Is it "Get-Help about-foo" or "Get-Help about_foo"?
If you're going to reinvent the wheel and big it up with the prefix "Power" then it needs to be better than Powershell is just now.
@Nick
I think you under estimate it a bit... I am new to scripting all together, but can Perl or Python, or BaSH load DLLs?
can they integrate with AD, Exchange, SharePoint, MOM??
Another point, all output in powershell is a DOTNET object, even text, is a system.string object (i think thats it)
I know I can do the same tasks as VBS, in 1/2 the amount of code in powershell.
Just my thoughts...