# Sunday, December 21, 2008

This time around, I thought I’d show how to work with programmatically generated dynamic parameters, as opposed to the statically defined sets we saw the last time. The context here is a fairly silly Cmdlet, but it’s good enough to demonstrate the concept end to end. It’s a Cmdlet for removing a file. It takes one string parameter which is the path to the file. The dynamic parameter I’m going to add is a –Force parameter. The trick is, this parameter will only be added if the current user is an administrator (XP), or is elevated as one (Vista).

This first portion of the Cmdlet defines the usual stuff like a verb and noun. This time though, I’m using a regular class constructor. It’s not often you see constructors in simple Cmdlets because typically all one would usually do is override one or more of the three processing methods BeginProcessing, ProcessRecord and EndProcessing. In this case, I need to create a instance of a “RuntimeDefinedParameterDictionary,” which does exactly what it says on the tin. It’s a dictionary of parameters, the key being a string (the name of the parameter) and the value being a instance of a RuntimeDefinedParameter class. These classes are all members of the System.Management.Automation namespace.

In the constructor, I’m calling a generic method defined as AddDynamicParameter<T>(string name). This is only called if the current user is an admin. I’ve defined three generic helper methods which you can see a little further down below.

  1. [Cmdlet(VerbsCommon.Remove, NOUN_FILE)]  
  2. public class RemoveFileCommand : PSCmdlet, IDynamicParameters  
  3. {  
  4.     private const string NOUN_FILE      = "File";  
  5.     private const string SWITCH_FORCE   = "Force";  
  6.  
  7.     private readonly RuntimeDefinedParameterDictionary _parameters;  
  8.  
  9.     public RemoveFileCommand()  
  10.     {  
  11.         _parameters = new RuntimeDefinedParameterDictionary();  
  12.  
  13.         // we only want to add the -Force parameter if  
  14.         // the current user is an administrator  
  15.         var principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());  
  16.  
  17.         // if vista and not elevated, this will be false even  
  18.         // if you are a member of administrators.  
  19.         if (principal.IsInRole(WindowsBuiltInRole.Administrator))  
  20.         {  
  21.             this.AddDynamicParameter<SwitchParameter>(SWITCH_FORCE);  
  22.         }  
  23.     }  
  24.  
  25.     [Parameter]  
  26.     public string FilePath  
  27.     {  
  28.         get;  
  29.         set;  
  30.     }   
  31.  
  32.     protected override void EndProcessing()  
  33.     {  
  34.         // does file exist?  
  35.         if (File.Exists(FilePath))  
  36.         {  
  37.             RemoveFile();  
  38.         }  
  39.         else 
  40.         {  
  41.             WriteWarning(FilePath + " does not exist.");  
  42.         }  
  43.     } 

This is a simple piece of code who’s role is to remove the file specified by the user. I’ve omitted the actual code that would delete the file for brevity. I’m using another helper method to see if the –Force parameter has been added to the Cmdlet’s definition, and if so, was it specified by the invoker of the Cmdlet. The idea is here is that the Cmdlet will not remove the file if it’s marked Read-Only, but if –Force is specified it will remove the R/O attribute and continue as planned.

  1. private void RemoveFile()  
  2. {  
  3.     // read file attributes  
  4.     var attribs = File.GetAttributes(FilePath);  
  5.  
  6.     // is read-only attribute set?  
  7.     if ((attribs & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)  
  8.     {  
  9.         bool shouldForce;  
  10.  
  11.         // see if the dynamic switch -Force was added (and specified)  
  12.         if (TryGetSwitchParameter(SWITCH_FORCE, out shouldForce))  
  13.         {  
  14.             WriteVerbose("Force.IsPresent: " + shouldForce);  
  15.         }  
  16.  
  17.         if (shouldForce)  
  18.         {  
  19.             RemoveReadOnlyAttribute();  
  20.         }  
  21.         else 
  22.         {  
  23.             WriteWarning(FilePath + " is marked Read-Only!");  
  24.             return;  
  25.         }  
  26.     }  
  27.  
  28.     // ... code to remove file ...  

And the final piece is here. The first method GetDynamicParameters, belongs to the IDynamicParameters interface and tells PowerShell that the Cmdlet may return extra parameters not defined on the class itself. In this case, we are returning a RuntimeDefinedParameterDictionary instead of a statically defined parameters on a nested class.

  1. public object GetDynamicParameters()  
  2. {  
  3.     return _parameters;  
  4. }  
  5.  
  6. // add a simple parameter of type T to this cmdlet  
  7. private void AddDynamicParameter<T>(string name)  
  8. {  
  9.     // create a parameter of type T.  
  10.     var parameter = new RuntimeDefinedParameter  
  11.                     {  
  12.                         Name = name,  
  13.                         ParameterType = typeof (T),                                  
  14.                     };  
  15.  
  16.     // add the [parameter] attribute  
  17.     var attrib = new ParameterAttribute  
  18.                  {                               
  19.                      ParameterSetName =  
  20.                         ParameterAttribute.AllParameterSets  
  21.                  };  
  22.       
  23.     parameter.Attributes.Add(attrib);  
  24.  
  25.     _parameters.Add(name, parameter);  
  26. }  
  27.  
  28. private bool TryGetSwitchParameter(string name, out bool isPresent)  
  29. {  
  30.     RuntimeDefinedParameter parameter;  
  31.       
  32.     if (TryGetDynamicParameter(name, out parameter))  
  33.     {  
  34.         isPresent = parameter.IsSet;  
  35.         return true;  
  36.     }  
  37.  
  38.     isPresent = false;  
  39.     return false;  
  40. }  
  41.  
  42. // get a parameter of type T.  
  43. private bool TryGetParameter<T>(string name, out T value)  
  44. {  
  45.     RuntimeDefinedParameter parameter;  
  46.       
  47.     if (TryGetDynamicParameter(name, out parameter))  
  48.     {  
  49.         value = (T)parameter.Value;  
  50.         return true;  
  51.     }  
  52.  
  53.     value = default(T);  
  54.       
  55.     return false;  
  56. }  
  57.  
  58. // try to get a dynamically added parameter  
  59. private bool TryGetDynamicParameter(string name, out RuntimeDefinedParameter value)  
  60. {  
  61.     if (_parameters.ContainsKey(name))  
  62.     {  
  63.         value = _parameters[name];  
  64.         return true;  
  65.     }  
  66.  
  67.     // need to set this before leaving the method  
  68.     value = null;  
  69.  
  70.     return false;  
  71. }  

Finally, the three helper methods are revealed. One is used for checking for dynamic SwitchParameters, the next is for ordinary parameters that return type T, the generic argument. The third method, TryGetDynamicParameter is used by the first two methods.

I hope this helps reveal some of the mystery behind dynamic parameters on Cmdlets. There is one more type of dynamic parameter that I will be examining in one of my next few posts, that is the scenario where a provider (like the FileSystemProvider or RegistryProvider) passes a dynamic parameter to a Cmdlet. In this particular case, the provider can only pass dynamic parameters to the built-in Cmdlets that operate on providers, e.g. Get-ChildItem, Get-Item, Get-ItemProperty etc.

Have fun!

posted on Sunday, December 21, 2008 5:28:27 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Monday, December 15, 2008

The dynamic duo / masterminds of PowerShell will be cornered and fiercely grilled by none other than our very own master podcaster, Hal Rottenberg this Thursday.  From the mouth of the suffixed one himself:

Coming up on the PowerScripting Live show this Thursday will be Jeffrey Snover,
the architect for PowerShell as I’m sure you all know, and he’ll be accompanied
by none other than Bruce Payette, author of PowerShell in Action and a core
developer on the PowerShell team.

We’re excited and we hope you can make it this Thursday at 9pm EST!

The live stream address is http://www.ustream.tv/channel/powerscripting-podcast

So if you want to get the lowdown on CTP3 (maybe), join us, the unwashed masses as we clamour to be near our idols. A lock of their hair and a signed discarded printout of directions to building 18 could be yours!

I made that last bit up. Who cares! This is going to be cool! Join us!

posted on Monday, December 15, 2008 4:14:40 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Sunday, December 14, 2008

Now this is cool. I’ve always wanted to try out TDD, but as I’m primarily a SharePoint developer, a lot of the time my code is written on my laptop running XP, so I can’t actually test anything since SharePoint won’t install there. The folks at Bamboo Solutions came up with some clever hacks to get WSS 3.0 and MOSS running on Vista, but it’s a bit of a heavyweight solution, especially when you consider this:

Typemock are offering their new product for unit testing SharePoint called Isolator For SharePoint, for a special introduction price. it is the only tool that allows you to unit test SharePoint without a SharePoint server. To learn more click here.

The first 50 bloggers who blog this text in their blog and tell us about it, will get a Full Isolator license, Free. for rules and info click here.

Unfortunately the competition is actually over, but it’s still worth a peek!

posted on Sunday, December 14, 2008 6:58:36 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# 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