# Tuesday, December 02, 2008

Normally parameters for a Cmdlet are defined directly as properties on the Cmdlet class itself. There are a few other ways for a Cmdlet to define parameters, and in this short series of three parts, I’ll cover the three variants you’ll likely to encounter as you become a more experienced PowerShell developer. First thing you need to do is to implement the System.Management.Automation.IDynamicParameters interface on your Cmdlet. This interface has one member, GetDynamicParameters. This interface can be implemented on either a Provider or a Cmdlet. In the former case, a provider is able to add new parameters at runtime to certain built-in Cmdlets like Get-ChildItem, Get-Item & Get-ItemProperty. It is typically used in a path context-aware manner – e.g. depending on whether the current argument is a folder or item, and what Type such a folder or item might be. We’re just to cover the Cmdlet variant in this post.

image

My example is just a simple Cmdlet named Get-OSVersion. It writes out Environment.OSVersion to the pipeline, and will dynamically add a parameter. It adds –EnsureElevated if you’re running Vista or higher, otherwise it will add –EnsureAdministrator. Statically defined dynamic parameter sets are defined by creating a simple class and decorating that class with parameter/properties you want to surface as if that class was the Cmdlet itself, then returning it from the GetDynamicProperties method at runtime. Simple!

  1. [Cmdlet(VerbsCommon.Get, "OSVersion")]   
  2. public class GetOSVersionCommand : PSCmdlet, IDynamicParameters   
  3. {   
  4.     private const string SWITCH_VISTA = "EnsureElevated";   
  5.     private const string SWITCH_WINXP = "EnsureAdministrator";   
  6.   
  7.     protected override void EndProcessing()   
  8.     {   
  9.         WriteObject(Environment.OSVersion);   
  10.            
  11.         string switchName = IsVistaOrHigher() ?   
  12.             SWITCH_VISTA : SWITCH_WINXP;   
  13.   
  14.         // not really a warning, just nice yellow text.   
  15.         WriteWarning(String.Format("{0}.IsPresent {1}",   
  16.             switchName, IsSwitchPresent(switchName)));   
  17.     }   
  18.   
  19.     // IDynamicParameters.GetDynamicParameters   
  20.     public object GetDynamicParameters()   
  21.     {   
  22.         if (IsVistaOrHigher())   
  23.         {   
  24.             return new VistaParameters();   
  25.         }   
  26.         else  
  27.         {   
  28.             return new WinXPParameters();   
  29.         }   
  30.     }   
  31.        
  32.     private bool IsSwitchPresent(string name)   
  33.     {   
  34.         // parameters bound at runtime   
  35.         Dictionary<stringobject> parameters = MyInvocation.BoundParameters;   
  36.   
  37.         // determine whether switch is set   
  38.         if (parameters.ContainsKey(name))   
  39.         {   
  40.             return ((SwitchParameter) parameters[name]).IsPresent;   
  41.         }   
  42.         return false;   
  43.     }   
  44.   
  45.     private static bool IsVistaOrHigher()   
  46.     {   
  47.         return (Environment.OSVersion.Version.Major >= 6);   
  48.     }   
  49.   
  50.     // dynamic parameters for Vista   
  51.     internal class VistaParameters   
  52.     {   
  53.         [Parameter]   
  54.         public SwitchParameter EnsureElevated   
  55.         {   
  56.             getset;   
  57.         }   
  58.     }   
  59.   
  60.     // dynamic parameters for Windows XP   
  61.     // (powershell won't run on windows 2000 ;-))   
  62.     internal class WinXPParameters   
  63.     {   
  64.         [Parameter]   
  65.         public SwitchParameter EnsureAdministrator   
  66.         {   
  67.             getset;   
  68.         }   
  69.     }   
  70. }  

Of course, adding Dynamic Parameters is no good if you don’t know how to read them back. In the code, you can see I’m using MyInvocation.BoundParameters to check if the Switches have been set.

posted on Tuesday, December 02, 2008 5:21:10 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Wednesday, November 19, 2008

It seems lots of people are catching the PowerShell bug these days. People who previously considered themselves as administrators have learned enough of the magic from using PowerShell from day to day over the last few months that they feel comfortable enough to tinker around with C#.

This is an example Cmdlet skeleton that is designed to work with Files and Directories. It looks after resolving wildcards, supports –WhatIf and –Confirm, accepting files and/or directories from the pipeline or accepting strings of paths from the pipeine. It also ensures that the only types of paths it can accept lie on the File System. Here’s an example of the invocation styles it supports:

cmdlet

Here’s the source code for this. It doesn’t really do a lot. It spits out two different types of custom objects: one for a file, and another for a directory. Feel free to do what you like with it; it’s yours. Have fun.

using System;
using System.Collections.Generic;
using System.IO;
using System.Management.Automation;
using Microsoft.PowerShell.Commands;
namespace PSQuickStart
{
    [Cmdlet(VerbsCommon.Get, Noun,
        DefaultParameterSetName = ParmamSetPath,
        SupportsShouldProcess = true)
    ]
    public class GetFileMetadataCommand : PSCmdlet
    {
        private const string Noun = "FileMetadata";
        private const string ParamSetLiteral = "Literal";
        private const string ParmamSetPath = "Path";
        private string[] _paths;
        private bool _shouldExpandWildcards;
        [Parameter(
            Position = 0,
            Mandatory = true,
            ValueFromPipeline = false,
            ValueFromPipelineByPropertyName = true,
            ParameterSetName = ParamSetLiteral)
        ]
        [Alias("PSPath")]
        [ValidateNotNullOrEmpty]
        public string[] LiteralPath
        {
            get { return _paths; }
            set { _paths = value; }
        }
        [Parameter(
            Position = 0,
            Mandatory = true,
            ValueFromPipeline = true,
            ValueFromPipelineByPropertyName = true,
            ParameterSetName = ParmamSetPath)
        ]
        [ValidateNotNullOrEmpty]
        public string[] Path
        {
            get { return _paths; }
            set
            {
                _shouldExpandWildcards = true;
                _paths = value;
            }
        }
        protected override void ProcessRecord()
        {
            foreach (string path in _paths)
            {
                // This will hold information about the provider containing
                // the items that this path string might resolve to.                
                ProviderInfo provider;
                // This will be used by the method that processes literal paths
                PSDriveInfo drive;
                // this contains the paths to process for this iteration of the
                // loop to resolve and optionally expand wildcards.
                List<string> filePaths = new List<string>();
                if (_shouldExpandWildcards)
                {
                    // Turn *.txt into foo.txt,foo2.txt etc.
                    // if path is just "foo.txt," it will return unchanged.
                    filePaths.AddRange(this.GetResolvedProviderPathFromPSPath(path, out provider));
                }
                else
                {
                    // no wildcards, so don't try to expand any * or ? symbols.                    
                    filePaths.Add(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(
                        path, out provider, out drive));
                }
                // ensure that this path (or set of paths after wildcard expansion)
                // is on the filesystem. A wildcard can never expand to span multiple
                // providers.
                if (IsFileSystemPath(provider, path) == false)
                {
                    // no, so skip to next path in _paths.
                    continue;
                }
                // at this point, we have a list of paths on the filesystem.
                foreach (string filePath in filePaths)
                {
                    PSObject custom;
                    // If -whatif was supplied, do not perform the actions
                    // inside this "if" statement; only show the message.
                    //
                    // This block also supports the -confirm switch, where
                    // you will be asked if you want to perform the action
                    // "get metadata" on target: foo.txt
                    if (ShouldProcess(filePath, "Get Metadata"))
                    {
                        if (Directory.Exists(filePath))
                        {
                            custom = GetDirectoryCustomObject(new DirectoryInfo(filePath));
                        }
                        else
                        {
                            custom = GetFileCustomObject(new FileInfo(filePath));
                        }
                        WriteObject(custom);
                    }
                }
            }
        }
        private PSObject GetFileCustomObject(FileInfo file)
        {
            // this message will be shown if the -verbose switch is given
            WriteVerbose("GetFileCustomObject " + file);
            // create a custom object with a few properties
            PSObject custom = new PSObject();
            custom.Properties.Add(new PSNoteProperty("Size", file.Length));
            custom.Properties.Add(new PSNoteProperty("Name", file.Name));
            custom.Properties.Add(new PSNoteProperty("Extension", file.Extension));
            return custom;
        }
        private PSObject GetDirectoryCustomObject(DirectoryInfo dir)
        {
            // this message will be shown if the -verbose switch is given
            WriteVerbose("GetDirectoryCustomObject " + dir);
            // create a custom object with a few properties
            PSObject custom = new PSObject();
            int files = dir.GetFiles().Length;
            int subdirs = dir.GetDirectories().Length;
            custom.Properties.Add(new PSNoteProperty("Files", files));
            custom.Properties.Add(new PSNoteProperty("Subdirectories", subdirs));
            custom.Properties.Add(new PSNoteProperty("Name", dir.Name));
            return custom;
        }
        private bool IsFileSystemPath(ProviderInfo provider, string path)
        {
            bool isFileSystem = true;
            // check that this provider is the filesystem
            if (provider.ImplementingType != typeof(FileSystemProvider))
            {
                // create a .NET exception wrapping our error text
                ArgumentException ex = new ArgumentException(path +
                    " does not resolve to a path on the FileSystem provider.");
                // wrap this in a powershell errorrecord
                ErrorRecord error = new ErrorRecord(ex, "InvalidProvider",
                    ErrorCategory.InvalidArgument, path);
                // write a non-terminating error to pipeline
                this.WriteError(error);
                // tell our caller that the item was not on the filesystem
                isFileSystem = false;
            }
            return isFileSystem;
        }
    }
}
posted on Wednesday, November 19, 2008 5:58:18 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Thursday, November 13, 2008

Here’s just a quick rundown of the changes in PowerShell 2.0 in comparison to the last public release, CTP2. This may be helpful to those of you who don’t have access to the Windows 7 PDC bits. Remember, things are still subject to change - it’s not in beta yet.

Unchanged cmdlets are not listed here.

Added (or renamed*) Cmdlets

Enter-PSSession
Remove-PSSession
Get-PSSession
Exit-PSSession
Get-PSSessionConfiguration
Unregister-PSSessionConfiguration
Register-PSSessionConfiguration
Debug-Process
Test-ModuleManifest
New-ModuleManifest
New-WSManSessionOption
New-PSSession
Import-PSSession
Export-PSSession
Set-PSSessionConfiguration
Get-WSManInstance
Get-WSManCredSSP
Enable-WSManCredSSP
Set-WSManInstance
Set-WSManQuickConfig
New-WSManInstance
Remove-WSManInstance
Ping-WSMan
New-WebServiceProxy
Get-PSTransaction
Connect-WSMan
Disable-WSManCredSSP
Invoke-WSManAction
Disconnect-WSMan
Get-ComputerRestorePoint
Disable-ComputerRestore
Enable-ComputerRestore
Get-Counter
Clear-EventLog
Export-Counter
Import-Counter
Checkpoint-Computer
Restart-Computer
Stop-Computer
Ping-Computer
Resume-BitsTransfer
Set-BitsTransfer
Suspend-BitsTransfer
Add-BitsFile
Clear-BitsTransfer
Remove-EventLog
Restore-Computer
New-Module
Get-HotFix
Complete-BitsTransfer
Write-EventLog
Show-EventLog
Limit-EventLog
Get-BitsTransfer
New-BitsTransfer
New-EventLog

Changed Cmdlets (parameter or parameterset differences)

Invoke-Command
Receive-PSJob
Set-WmiInstance
Stop-PSJob
Wait-PSJob
Remove-PSJob
Export-ModuleMember
Start-PSJob
Get-PSJob
Add-Module
Set-Service

   

Removed (or renamed*) Cmdlets

Pop-Runspace
New-Runspace
Push-Runspace
Get-Runspace
Remove-Runspace

   

* e.g. The *-Runspace Cmdlets have been renamed to *-PSSession

posted on Thursday, November 13, 2008 11:15:00 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Monday, November 10, 2008

Fellow MVP Hal Rottenberg asked me to highlight this short and painless survey we’re trying to get out to y’all.

Think of the following in terms of impact on the greatest number of people at a tech convention such as Microsoft TechEd or MMS. Your opinions will help shape our plans for future shows.

Thanks!

Hal Rottenberg
Community Director, PowershellCommunity.org

It shouldn’t take you too long ;-)

posted on Monday, November 10, 2008 10:29:41 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Thursday, November 06, 2008

So it looks like the High King of PowerShell has spoken over at TechED Europe today and answered the question that sits on every child’s lips in the back of a moving car: “Are we there yet?”

“Nope. Not Yet.”

“So when, dad?”

Dmitry Sotnikov (who’s  not my dad, btw) of PowerGUI fame broke the news to the web at large, and I’m just shamelessly grabbing the HTML directly from his post:

  • December 2008 - CTP 3 (Community Technology Preview) or Beta 1 if it meets the internal criteria and all names/features are finalized.
  • RTM - end of 2009/early 2010 as part of Windows 7 and Windows Server 2008 R2.
  • RTM for XP, 2003, Vista, and 2008 - as a downloadable package (with new WinRM bundled in there) only a few months later.

Yay!

posted on Thursday, November 06, 2008 1:23:50 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback