Monday, March 05, 2007
Posted on Monday, March 05, 2007 9:11:47 PM (Mountain Standard Time, UTC-07:00)  Comments [0] | 
Categories: .NET | ArcMap | Powershell
In my last ArcDeveloper talk, I discussed how to automate common ArcMap tasks without actually using ArcMap. The talk was based on a previous blog posting: Automating Maps: Using ArcObjects without ArcMap. I did not have time prior to the talk, but I had wanted to show how you can take the simple MapMaker class in that example, and wire it into Powershell to create a simple command line utility that could spit out maps. I found an our this evening, and whipped this up. It's my first foray into PowerShell, so be kind ;-)

For those who have not heard of it, PowerShell is the new Windows shell. It exposes "korn shell"-like power (you recall all that grepping, awking and piping fun), but mixes in objects.

Here's how the Powershell documentation describes it...

Unlike most shells, which accept and return text, Windows PowerShell is built on top of the .NET common language runtime (CLR) and the .NET Framework, and accepts and returns .NET objects. This fundamental change in the environment brings entirely new tools and methods to the management and configuration of Windows.
If that's not enough to pique your curiosity, I'll also add that pretty much anything that Scott Hanselman says is cool is worth looking at.

I'm not going to walk through all the details of installing powershell and configuring it - I'll leave that to you (resource links at the bottom will help). I will show the basic pieces, how to register the PowerShell Snap-in with PowerShell, and how to use it.

Creating a Powershell cmdlet

Create a new class library project in Visual Studio 2005, create a class and throw this in.

using System;

using System.Text;

using System.Management.Automation;

using System.Management.Automation.Provider;

using System.Diagnostics;

using System.ComponentModel;


 


    [Cmdlet(VerbsCommon.Get, "ParcelMap",DefaultParameterSetName = "")]

    public class GetParcelMapCommand : PSCmdlet

    {

        private string _pid;

        private string _outputFile;

 

        [Parameter(Position = 0)]

        [ValidateNotNullOrEmpty]

        public string PID

        {

            get { return _pid; }

            set { _pid = value; }

        }

 

        [Parameter(Position = 1)]

        [ValidateNotNullOrEmpty]

        public string OutputFile

        {

            get { return _outputFile; }

            set { _outputFile = value; }

        }

 

        /// <summary>

        /// Code here runs the commandline parameters

        /// </summary>

        protected override void BeginProcessing()

        {

            //We are hardcoding the location of the mxd file and the output type

            DaBo.MapMaker.MapMakerService service = new     DaBo.MapMaker.MapMakerService("ParcelMap",@"c:\parcelmap.mxd","Parcels","PIDN");

            DaBo.MapMaker.MapMaker mm = new DaBo.MapMaker.MapMaker();

            //we just pass in the parcel id and the output file

            mm.ExportMap(service, DaBo.MapMaker.MapTypeEnum.PDF, _outputFile, _pid, 10, 10);

        }

 

        /// <summary>

        /// If we implemented code here, it would allow pipleline type operations

        /// </summary>

        protected override void ProcessRecord()

        {}

 

    }



That's the cmdlet itself. But for Powershell to know about it, we need to create a snap-in. This is very simple code that you can look at in the source code, but basically all I did was copy/paste from the sample code at MSDN and change a few names. Here's the top few lines...

 /// We'll use the name of this class with add-pssnapin later

  [RunInstaller(true)]

  public class GetParcelMapPSSnapIn : PSSnapIn

  {

 

    public GetParcelMapPSSnapIn() : base()

    {    }

   ///code continues...



Post-Build Events
Once you have your assembly built, you'll need to put it someplace that powershell can access it. I'd suggest copying the file to a common location for all your powershell snap-ins instead of having these things scattered all over the place. An easy way to do this is via post-build events in visual studio. I chose to put my stuff into c:\powershell.

Thus my post-build event is:

copy $(ProjectDir)$(OutDir)*.dll c:\powershell

I'm copying all the dlls to ensure that any dependancies (i.e. the MapMaker assembly) are brought along for the ride.

Register the Snap-In with PowerShell
Once it's there, it's time to open PowerShell, register the Snap-In and start spitting out maps. I'm sure that this part could be automated (someone likely already automated it using powershell itself). Anyhow - here's how you do it manually.

In Powershell, cd to the folder where you have copied your assembly - c:\powershell for example.
Then issue these commands:

set-alias installutil $env:windir\Microsoft.NET\Framework\v2.0.50727\installutil
installutil dabo.powershell.dll
add-pssnapin GetParcelMapPSSnapin

That's it. You can check that it's registered by running this cmdlet



To use the cmdlet, simply feed it the parcel pin and an output file



And here's the map it cranked out (as you can tell, cartography is not my strong suit)



Summary
Now I know that this is not the most useful example in the world - the command line is much more cryptic than using a simple winforms app. But if we look past the specifics of the example, and recall all the cool things we could do when combining Workstation ArcInfo with the unix shell commands, things start to open up a little. Particularly if you work in a production environment whre you are moving lots of files, transforming them along the way - it's very common to need to string together a series of tasks in rather ad-hoc ways. By creating a library if geospatial cmdlets, you would be able to do this same style of processing. Have fun!

Resources
Source code  and data for MapMaker
Get-ParcelMap cmdlet source code
VS 2005 cmdlet template (from Scott Hanselman's blog)
MSDN: Creating a cmdlet with parameters
MSDN: Registering a cmdlet with PowerShell

Comments are closed.