Quickstart #1 - A Cmdlet That Processes Files and Directories

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;
        }
    }
}

Changes from CTP2 in PowerShell 2.0 in the Windows 7 M3 “PDC” Build

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

PowerShellCommunity.org Convention Survey

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 ;-)

PowerShell v2.0 Timeline Announced

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!

Sapien PrimalForms GUI Editor for PowerShell Released

I’d say a lot of folks will be chomping at the bit for this. It’s the Tk to PowerShell’s Tcl and is really the final piece of the puzzle where the shell’s position as the de-facto Windows Shell stands anyway. Well, maybe Remoting support for XP and Windows Server 2003 is ahead there just a bit, but a forms designer must be a close second.

Check it out: http://blog.sapien.com/index.php/2008/11/03/free-primalforms-tool-for-powershell-released/

Congratulations guys on shipping such a fine tool, for FREE. All it costs you is an email address. Yah, sell your soul, it’ll be worth it.

Managed Code in InfoPath Workflow Task Forms - Why isn’t this categorized as a bug?

I’ve been performing the “resolution” listed in this article since I first discovered the problem, but never ever did I think it was a “feature?” I was sure that I was just the unluckiest person alive and was always working with some dodgy permissions configuration of the 12 hive, but lo and behold, no. This is just the way it’s meant to be.

Anyone else think this is just pure madness? By default any forms you create in Visual Studio have a managed component. Hitting F5 to run deploy/debug won’t ever do the work for you either. Why isn’t this mentioned anywhere in any sort of official capacity? Anybody?


Managed code may not be executed if an InfoPath 2007 workflow form is deployed as a feature for a workflow

SYMPTOMS

Consider the following scenario. A Microsoft Office InfoPath 2007 workflow form contains managed code. The form is deployed as a feature for a workflow. In this scenario, the managed code may not be executed.

CAUSE

This issue occurs because a compiled DLL becomes part of the InfoPath form template (XSN) file when you create managed code behind an InfoPath form. Microsoft Office Forms Services does not extract a DLL by expanding the XSN file when the XSN file is installed by using the features functionality.

RESOLUTION

To resolve this issue, copy the compiled DLL to the same folder to which your feature XSN file was deployed.

http://support.microsoft.com/kb/930894/en-us

Tips on SharePoint Workflow, SharePoint Activities and MethodInvoking callbacks

One thing I’ve noticed about the SharePoint callback-based Activities in Workflow is that they are extremely flakey if you try to reference the member variable reference for the owning Activity directly instead of casting the “sender” argument in the event handler itself. You have a SharePoint SetState Activity, for exmaple, along with the corresponding MethodInvoking hook and I don’t mean the State Machine transition Activity, but rather the SharePoint-specific Activity that lets you override the Workflow’s overall state string using the <ExtendedStatusColumnValues> metadata in Workflow.xml. In my case, I use the MySetState_MethodInvoking hook to set the state at runtime depending on several conditions. If I try to set properties on the workflow's MySetState member variable directly like this:

private void MySetState_MethodInvoking(object sender, EventArgs e) {
    this.MySetState.State = SPWorkflowStatus.Max + 2;
}

This just plainly doesn’t work reliably. It doesn’t throw any exceptions, in fact it looks to work just fine, except the state is not changed. The same seems to go for any other of the SharePoint interfaces that involve callbacks. In my experience, they just don’t work most of the time especially if the workflow dehydrates before the callback is invoked. This leads me to think that there are some quirky interactions going on internally that somehow cause the workflow to ignore your attempts to change anything, perhaps due to the workflow getting rehydrated after you have made the changes, or who knows. All I know is that if you want this stuff to work reliably, ALWAYS CAST THE SENDER to your target Activity before attempting to manipulate the owning Activity. Using the member variable is just flakey. If anyone has any information on this, let me know. This works reliably:

private vid MySetState_MethodInvoking(object sender, EventArgs e) {
    ((SetState)sender).State = SPWorkflowStatus.Max + 2;
}

Anyway, have fun.

About the author

Irish, PowerShell MVP, .NET/ASP.NET/SharePoint Developer, Budding Architect. Developer. Montrealer. Opinionated. Montreal, Quebec.

Month List

Page List