Tuesday, October 28, 2008 11:46:47 PM UTC #
I couldn't hold out.

It's okay though, because today we're strictly business. In the course of developing a bunch of SharePoint timer jobs recently, I've learned several things, most of which aren't obvious from the get-go:
  1. Storing and retrieving configuration data is a problem. Because I don't have a farm-wide configuration list (yet—the temptation grows every day), I was forced to do some ugliness in order to store and retrieve configuration data. I don't necessarily recommend my approach; instead I'll just say I'm using a custom SPPersistedObject as my Timer Job's config store and I'll further say that it works, roughly, though I'd now prefer a better way. Consider setting up a farm-wide config list, it's a relatively big time investment but is probably worth it. Other traditional config storage options (such as the web.config, or a site-local config list, or your site-local property bag) aren't accessible to Timer Jobs without some sort of…configuration's configuration…hmm, yes…without something extra pointing the way. Anyway, it's a problem.
  2. ANYTIME YOU UPDATE YOUR TIMER JOB CLASS (or custom assembly), YOU MUST BUMP ALL SHAREPOINT TIMER SERVICES ON THE FARM! I learned this lesson the hard way. If you fail to bump the timer service, it will blissfully run the old copy of your timer job class; I don't know how or why it caches your assembly, but it does, and bumping the SharePoint timer service is the only way to clear the assembly cache(?) and force it to use your minty fresh assembly.
    1. Side-note: can we report this as a bug in the Solution framework? Because this is a big enough gotcha that the Solution framework needs to include an option to -bumptimersvc …or something. Maybe a custom stsadm command (stsadm -o resetadmsvc) or maybe tack something onto the Solution deployment API and associated stsadm commands…we need something.
  3. There are differences between the context of a test harness (i.e. something like an NUnit integration test running under the NUnit test runner) and a timer job running in the Timer service. This may sound obvious, but when you're troubleshooting something that "only breaks on the test farm," this little bit of trivia is important. If you need to troubleshoot your timer job as it runs on the timer service, specifically, this can get tricky.
Also, in the course of troubleshooting just such an issue (as outlined in #3) I've created a little script to speed up the code-compile-test loop; instead of scheduling a timer job for "0100 hours" and waiting until tomorrow to see the results, why not reschedule the timer job by your own self? And that's exactly what I did.

My script below will reschedule your timer job to run 10 seconds in the future
(read: instantaneously). All you have to do to get this script working for you is customize four variables to match your own timer job, then follow the quick "usage instructions" at the bottom of the script. Below is a rundown of the four variables:
  • $siteUrl - the site collection root URL. We need this to get a reference to the SPWebApplication that holds your Timer Job.
  • $customAssemblyName - the partial name of your custom assembly. This is necessary because we're going to new up an instance of your timer job, and thus we'll need to first load the containing assembly.
  • $jobName - this need only be a rough equivalent of your job name. I'm usually lazy and say something like "*custom profile job*" or the minimum necessary to identify my job from all the rest. Messy is good; we're running a one-off script, right? Or you can go ahead and type the perfect exact case-sensitive job name in there, that's fine too.
  • $timerJobClassName - again, we need this because we're going to new up a rescheduled timer job.
Assumptions:
  • Your original schedule doesn't matter, and may be destroyed. Because that's exactly what this script does, by the way—it destroys the original schedule and sets a "10 seconds from now" schedule. Incidentally, whatever it did before, your job now runs on a daily schedule :)
  • You're only concerned about the timer job running on one web application's context. Because in truth that's all that mattered to me when I wrote this script, I didn't consider the possibility of multiple jobs.
  • You're running a single-server farm (i.e. a developer VM). My script only stops the service on the local server.
  • No one else cares if you bump the SPTimerV3 service, including any other timer jobs that may be running presently. Note in the script below, PowerShell has some cmdlets to work with Windows Services. I was totally unaware of them until I had to bump this service; neat.
While these assumptions sound scary, trust me—you won't care. On a single developer VM, you won't care about all these things. Even on a multi-server test farm, you won't care—because this script is going to save you hours of troubleshooting.

The PowerShell script is as follows:

$siteUrl = "http://dev"
$customAssemblyName = "Corp.SharePoint.Assembly"
$jobName = "*your job name*wildcards*work*"
$timerJobClassName = "Corp.SharePoint.Namespace.TimerJob"

[void][reflection.assembly]::LoadWithPartialName("Microsoft.SharePoint")
[void][reflection.assembly]::LoadwithPartialName("Microsoft.Office.Server")
[void][reflection.assembly]::LoadwithPartialName($customAssemblyName)

function Run-Init
{
    $global:s = [Microsoft.SharePoint.SPSite]$siteUrl
    $global:webApplication = $s.WebApplication
    $global:job = $webApplication.JobDefinitions | ? { $_.Name -like $jobName }
}

function Create-NewJob
{
    Stop-Service "SPTimerV3"
    Start-Service "SPTimerV3"
    $global:job.Delete()
    $global:job = new-object $timerJobClassName -arg $webApplication
    $sched = new-object Microsoft.SharePoint.SPDailySchedule
    $now = [datetime]::now.AddSeconds(10)
    $sched.BeginHour = $now.Hour
    $sched.EndHour = $now.Hour
    $sched.BeginMinute = $now.Minute
    $sched.EndMinute = $now.Minute
    $sched.beginsecond = $now.Second
    $sched.endsecond = $now.Second
    $global:job.Schedule = $sched
    $global:job.Update()
}

#Usage: paste this script directly into a PowerShell console; the quickest
#way is to right-mouse-button click. Then when you're ready,
#run the following commands (minus the # of course):
#
#Run-Init
#Create-NewJob
#
#Anytime you update your custom assembly "Corp.SharePoint.Assembly", you will need to
#DESTROY your open PowerShell console/session and create a new one. This is the cleanest way
#to unload your old custom assembly.

That's pretty much it. Change the variables to whatever you need, open a PowerShell console, right-click, then type "Run-Init; Create-NewJob". You're done! Step 3: Profit!

Tiny footnote: if you don't care about "context", this script also allows you to execute the timer job immediately. First run the "Run-Init" function, then just type $job.Execute([guid]::Empty) in PowerShell. You can also attach to the PowerShell.exe process and do "remote debugging" of your timer job, if desired. Though if you're going to go that far, you should probably just write an NUnit test that performs the same task, and debug THAT. I'm very pro-unit testing frameworks, really, they're great. Anything that closes the code-compile-test loop, in any way, is a good thing.


Tuesday, October 28, 2008 11:46:47 PM UTC  #     |  Comments [0]  |  Trackback
Monday, June 09, 2008 8:00:05 PM UTC #

When trying to convince others why I think PowerShell is hot hot hot, I find myself lacking examples. So, dear reader, I'm writing this list as a sort of "memo to self: don't choke next time and remember one or two of these things." Here goes.

Before the mega-list

If anyone is interested in a particular topic in more detail, let me know. To me, most of what I list below is either a) too broad a topic to cover properly, b) too narrow for anyone else to find it of use, and/or c) just plain obvious.

I can't tell what may be interesting to others; it's all a "solved problem" at this point to me.

Of all these things, since the following is a jumbled, unprioritized, stream-of-consciousness type of list, I'd like to point out that the things I find are most useful are:

a) Object model spelunking - during development, figuring out which function to call, testing out little 5-line scripts to see what happens—little things to help me gain confidence that what I'm writing will work. This is also the most difficult thing to express properly.

b) Visual Studio post-build task - it's just so easy to add and remove bits of script to help me do … whatever it is I need doing. Note this is not necessarily a replacement for NAnt or MSBuild—instead it's something of an informal post-build scratchpad.

c) Deep (focused) administration tasks - like getting the crawl logs programmatically, or getting a list of sites or features into an Excel-friendly format for further analysis. SharePoint allows deep access to its inner workings with APIs that it uses itself—this is unique among enterprisey-type systems.

Memo to self: Powershell + SharePoint

Ways I've used PowerShell + SharePoint:

  • Object model spelunking. This includes
    • The most commonly used SharePoint objects, e.g. SPSite, SPWeb, SPList, SPListItem. PowerShell's unique (to the .NET space) REPL environment is the best for exploring an object model. You get the full power of an object browser with all the benefits of an immediate window. I don't know how to explain this; you'll just have to try it sometime.
      • Neat trick to help you write CAML queries. 1) Make a view that does exactly what you want. 2) Use PowerShell to dig into the view's SchemaXml property. Others have written Windows Forms tools to do the types of things I do in PowerShell.
    • Digging into the SPLimitedWebPartManager class.
  • Object model manipulations. This usually means really simple things that I do in a dev/test environment, like
    • Changing the logo on a site to a different gif
    • Changing the default master page property
    • Changing lists to Hidden
    • Changing fields to Hidden - there was a case where (for whatever reason) the ModerationStatus field became visible; it shouldn't have been; quick PowerShell script confirmed the fix.
    • Deleting all items in a list. $site.OpenWeb("/awesomeness").Lists["awesomeness"].Items | % { $_.Delete() }
      • Who cares? you may say. What happens if you have 1000 items in the list, that you're programmatically creating every time you run your integration test? Yeah. Yeah, you may need something like this.
    • Comparing two fields in a list via PowerShell, looking for corruption in one of them. Amusingly, the problem was that one field belonged to a Content Type, thus was not visible on the list edit page, thus causing us MUCH confusion. Oops! Well, PowerShell was able to help us, just not in the way we thought. By the way, I used PowerShell's metaprogramming facilities to do a 50-property comparison using a one-liner.
  • Enterprise Search administration. Includes:
    • Getting a copy of the crawl log. Up until a few weeks ago, until the Enterprise Search team released a stsadm tool to do the same, this was quite novel.
    • Swapping the "file inclusion list" into a "file exclusion list" in order to aid web crawling.
  • Working with wikis - digging into a wiki library and extracting the page titles - it was actually somewhat lame, but hey, it worked.
  • Ad-hoc querying of our farm's various sites; who's the owner, when were they last modified, etc.
  • Listing the farm's current various peoplepicker settings, to help ease my confusion with their application. This was pre-SP1 before we had the really useful settings available to us.
  • Comparing document templates (actual documents) in every library in the farm, comparing pre-migration to post-migration farm. It turned out, during the migration we "lost" two document templates. I'd like to give a big shout-out to SharePoint's WebDAV support, that was definitely a big help :)
  • Copying a list using SharePoint's lists.asmx web service. Presumably I was doing some processing on the data, there's not really an excuse for just copying the list from one site to another.
  • A different solution: used PowerShell as a "touch-free" solution to generating "reports" on a SharePoint server. I'm not particularly proud of technology choice in this case, but the results were good. This script involved a) pulling data from TFS, b) updating a custom list, c) creating InfoPath form data and "uploading" the forms. It wasn't the best choice.
  • Listing all activated Features in the farm (well, actually, close: all sites in a site collection, the site collection itself, the web application, and the farm-level Features). Anyway, listing all activated Features in an easy-to-paste-into-Excel format.
  • PowerShell as a Visual Studio post-build action
    • Use it to deploy my project
    • Use it to do a "fast deploy" - before building, just comment out the sections of the post-build script that we don't need to run.
      • gacutil /i for quick code updates to the GAC.
      • gacutil /u to ensure that when I run the TestDriven.NET test runner, it is running off of the most-recently-built project build, not off of whatever is sitting in the GAC. With this, I can guarantee nothing is in the GAC.
      • Attaching to event receivers.
    • Do an app pool reset.
    • Warm up the app pool by programmatically visiting a page in the site.
    • WANTED BADLY, MANY TIMES: a way to programmatically update a workflow association InfoPath form, WITHOUT having to go through the whole deployment rigmarole. If it's possible at all, I can automate it.
  • PowerShell as a "manual setup" for integration tests. We're talking the "copy 100 files to the document library and see what happens" type of integration test, which is … let's just say difficult to achieve without some kind of artifacts. So, in other words, because I was too lazy to set up my integration tests the proper way, I used PowerShell.
  • Doing name lookups from the people.asmx web service; checked ~400 names against the people list (and got back a rich list including email address, full name, job title, department, etc) in 5 minutes.
  • $solutions = ([xml](stsadm -o enumsolutions)).Solutions
    • You know all that worthless unreadable XML returned by stsadm -o enumsolutions? Well, with this one-liner, we have turned the useless into something powerful.
  • Installing a Fab 40 demo site collection programmatically - it takes a sweet long time to finish, but the script is painless and perfect. Others have posted batch files to install the templates, but no one's attempted to build all the demo sites automatically. This is a good example of what PowerShell can do.
  • Enumerating over customized/unghosted pages—all of them in the entire farm—and getting a list of all pages that are listed as uncustomized. This is more useful than what the PRESCAN.EXE log gives you.
    • On a related note, attempted to run the "RevertAll()" or similarly named function to revert all pages in the farm to default. Again this was on a copy of production data. And no, it didn't work as well as I'd hoped.
  • Use CabLib.dll to create CAB files, bypassing the horrors of MAKECAB.EXE.
  • Tiny development-time administrivia, like getting the four-part assembly name of your project's DLL, and copying it to the clipboard—you need it to declare in your feature.xml file for your Feature event receiver. Here's a bunch of things I did.

Conclusions

  • You don't have to use PowerShell. I'm sure many of you have solved similar problems with PowerShell alternatives, including
    • SQL queries
    • WinForms utility apps (check out CodePlex, you'll see what I mean)
    • Console apps
    • custom stsadm extensions (I'm talking to you, Gary LaPointe)
    • NAnt tasks/MSBuild tasks
    • Telling people "that's not possible"
  • It's hard to nail down for what I use PowerShell specifically, as you may infer from above. Is "lots of stuff" an okay answer? No?
Monday, June 09, 2008 8:00:05 PM UTC  #     |  Comments [4]  |  Trackback
Friday, June 06, 2008 8:00:01 AM UTC #

I recently read Todd Klindt's post on dynamically generating filenames based off of dates, for the greater purpose of making daily SharePoint backups.

USING BATCH FILES.

While his solution serves the #1 purpose of all scripts—it works, and thus is an excellent solution—I'd like to show how to accomplish the same thing using PowerShell.

Task #1: generate a SharePoint backup in YYMMDD format

DOS:

stsadm -o backup -url http://awesomeness.inc/ -filename "AwesomenessInc - %date:~-2%%date:~4,2%%date:~7,2%.backup.cab"

PowerShell:
(line breaks added for horizontal readability)

image

Task #2: delete backups older than seven days

DOS:

forfiles /d 7 /m *.backup.cab /c "cmd /c del @file"

PowerShell:
(line breaks added for horizontal readability)

image

Conclusions?

  • Both get the job done.
  • Date formatting in DOS is disgusting. forfiles is terse, but there's no excuse for the date formatting (substring selection?) syntax. This is almost as nasty as the fabled LDAP bitwise-and operator (look it up, it's not a joke).
  • PowerShell is far more verbose (at least today), but benefits us with improved readability.

Had I the inclination, I could have used standard procedural techniques to further express my intent in the code itself and further improve readability. E.g. instead of calculating the YYMMDD date inline in the string, I could have called a function to do that for me, or stored the value in a variable, i.e. either Get-TodayInYyMmDdFormat or $dateInYyMmDdFormat. So while I did an okay job readability-wise, I could have done better.

As for DOS: this is as good as it gets, readability-wise.

Aside: calling DOS commands from PowerShell

Final quick reminder: instead of all that PowerShell-specific nuttiness, we could have instead satisfied the "run this in PowerShell" requirement by prefixing the existing DOS commands with "cmd /c". So the DOS command

stsadm -o backup -url http://awesomeness.inc/ -filename "AwesomenessInc - %date:~-2%%date:~4,2%%date:~7,2%.backup.cab"

becomes the PowerShell-compatible command

cmd /c "stsadm -o backup -url http://awesomeness.inc/ -filename ""AwesomenessInc - %date:~-2%%date:~4,2%%date:~7,2%.backup.cab"""

Note that we're treating the entire line after cmd /c as a string in PowerShell, so we have to be sure to escape any special characters, e.g. the double-quote. While it's somewhat obvious when I encapsulate the entire DOSO command in the string, it is much less obvious if you fail to do so. PowerShell will attempt to 1) evaluate what is there, then, if it fails, 2) send that value to DOS as text. If you don't specifically tell the interpreter "hey, send this as text", you may be surprised by the results. An example of this oddness is something I've used before.

DOS:

stsadm | find "feature"

PowerShell:

stsadm | find """feature"""

Friday, June 06, 2008 8:00:01 AM UTC  #     |  Comments [0]  |  Trackback
Friday, May 30, 2008 8:00:07 AM UTC #

Before I start, I'd like to acknowledge the fact that having strong opinions about an obscure Windows command-line utility is so not cool. I'm with you.

Things I don't like about using MAKECAB.EXE:

  • If you forget to set an obscure property in your DDF manifest, MAKECAB will silently and happily exclude large files, despite the fact that you explicitly included them in the DDF manifest. Not only is this bad error handling, but it also gives you a false sense of security. This leads to the most awful troubleshooting sessions, where you have to dig into every layer of the stack, before coming back and double-checking every layer. This aggravating troubleshooting session is followed by a brief eureka moment, followed immediately by intense rage.
    Obscure property to set: .Set MaxDiskSize=<<some massive bytecount, I'd go 1 billion plus>>
    • Fun footnote: most online samples fail to set this obscure property, including MSDN and several blogs who obviously copied their DDFs from MSDN, which is why this is such a huge problem. Very few of the online examples get this right!
  • The MAKECAB.EXE SDK document uses Times New Roman as its font. That dates it at pre-1997, when we were all still using either Office 6.0 or Office 95. Awesome. This isn't a complaint as much as it is an observation—OLD!
  • Diamond Directive Files! DDF manifests are awful and, in my limited needs, completely unnecessary! Whenever I need to build a CAB file manually (it's happened a few times), I fired up PowerShell and used a single CabLib Extract/Compress function call to do the same thing that your no-longer-necessary-but-you'd-better-get-all-the-syntax-perfect MAKECAB+DDF file solution did.  And I didn't even have to bust out the ancient Times New Roman-sporting manual! (see below for actual script; it's tiny)

Takeaways

  • Obscure property to set in your DDF manifest: .Set MaxDiskSize=<<some massive bytecount, I'd go 1 billion plus>> 
  • If you're using MAKECAB.EXE as part of your Visual Studio build process, switch to WSPBuilder! You're not losing anything by swapping out one post-build command-line EXE for another, and will gain MUCH more in WSPBuilder's functionality.
  • Use the following PowerShell snippet (or use it as a starting point for your own improved version) to compress cab files:

 image

Copy-pasty version:

function Compress-Directory ($dir, $cabFileFullPathAndFilename)
{
    [void][reflection.assembly]::LoadFile(
        "C:\Program Files\WSPBuilder\CabLib.dll")
    $c = new-object CabLib.Compress
    $c.CompressFolder($dir, $cabFileFullPathAndFilename, $null, 0)
}

Compress-Directory "C:\temp" "C:\sandbox\a.cab"
Friday, May 30, 2008 8:00:07 AM UTC  #     |  Comments [3]  |  Trackback
Monday, October 15, 2007 12:00:13 PM UTC #

I find it difficult to explain why I bother using PowerShell with SharePoint; it isn't easy to find a killer example. What I'm about to present is also not a killer example—I just figure that, after a while, I'll wear you down with enough mediocre examples that you'll be convinced. Or tired enough to give up resisting; either is fine with me.

Today's mediocre example

Introducing your team's new knowledgebase! To help everyone get started, I've already created the site and a wiki library (wiki libraries are not exactly the same as wiki sites; the differences are meaningless in the context of this discussion).

We're going to call this wiki library "Yet another soon-to-be abandoned knowledgebase", in an odd naming coincidence that I'm sure has no basis in reality. Everyone I know has plenty of time, incentive and managerial support to document all critical and common processes in an organized and thorough manner. I'm sure your experience mirrors mine.

In the screenshot below, you'll note we have created our knowledgebase.

Team knowledgebase 2007 edition - yet another soon-to-be abandoned knowledgebase 

 

Looking at the list view of this wiki library, you'll also notice that our team members have done their part, quickly and efficiently creating articles. Whoa there—watch your back, Wikipedia!

Knowledgebase articles list - six articles total

 

Looking at individual entries (like the one below), you'll notice that each entry begins with its own specially formatted title. We have all been so good as to consistently apply this convention across the entire wiki.

[Because I can't leave this alone, I'll note that no, those aren't real keys. Or if you find out that they are real keys, dude! DUDE! Let me know that I'm sitting on a gold mine here!]

one knowledgebase article

 

Only one thing remains: now that everyone is finished with their articles (and let's face it, once the initial push is done, this knowledgebase is as full as it's going to get), you still need to create the wiki home page! Now how would we go about performing this repetitive task against hundreds, or thousands of regex-matchable wiki pages?

Enter SandmanPowerShell

Did someone say PowerShell? For the skeptics, this is where you make the "when all you have is a hammer, everything looks like a nail" joke. Go ahead; I can take it.

Comments before the code

The following code generates an unordered list of all the wiki articles, with the final goal being me pasting this unordered list directly into the home page. This admittedly is not the most exciting or shocking of PowerShell's uses.

The point is: I wasn't typing this data manually; and I wasn't attempting to use some other semi-automated, semi-manual method of get this data into the wiki home page; and I wasn't writing XSL transforms to display the data dynamically. Actually, XSL sounds like a pretty good idea, now that I think about it.

PowerShell code

Without further ado:

PowerShell code

 

The Grand Finale: did it work?

You be the judge.

Emitted HTML (I pasted this directly from the clipboard):

<ul>
    <li>[[How To Use This Wiki Library]]</li>
     <li>[[Home]]</li>
     <li>[[Previous knowledgebases|Links to all of our previous knowledgebases]]</li>
     <li>[[Possibly incriminating information|Software activation keys]]</li>
     <li>[[Copy-Pasted Email 1|Re: Re: Fwd: Fwd: contact info]]</li>
     <li>[[Copy-Pasted Email 2|Re: Reboot the server?]]</li>
</ul>

 

Emitted HTML put to use:

Emitted HTML

Ok, well, it worked. Even for larger than average knowledgebases like my example above, five items aren't enough to justify all this. But, what about for 20 pages?

30?

50?

100?

1000?

etc?

ET CETERA?

At what point does PowerShell become too awesome an option to deny?

I'll wear you down, eventually.

Take-aways

NUMBER ONE: PowerShell is awesome.

NUMBER TWO: Even if you have no intention of using PowerShell, at least realize that through some informally-developed solution, you can work with SharePoint Wiki data programmatically. Whether it's a console application you run on the server, or a Feature you staple to the wiki's Actions menu, or some other Rube Goldberg-esque device, the point is: you can work with SharePoint wikis programmatically.

A short word about wikis (and hackiness)

The example above does not mirror reality; my real-life PowerShell script was much uglier, much hackier, and took almost no time to develop. In this example, I also skipped an intermediate Excel step (yes, Excel). The hackiness of the Excel step was such that it was unfit for publishing.

As for wikis: I'm aware that the wiki home page shouldn't necessarily point to every page in the wiki. I will say this definitively: there is no harm in linking to as much as possible off of the main page, in whatever form, whether it's an unordered list or otherwise.

Monday, October 15, 2007 12:00:13 PM UTC  #     |  Comments [2]  |  Trackback
Friday, September 14, 2007 6:08:22 AM UTC #

On the most recent DotNetRocks TV episode, Carl and Scott demonstrate PowerShell: both its concepts and features, and a lot of how generally sweet it is. Unfortunately Scott ran out of time before he got to the good parts (he was just getting to object piping). Clearly there's room for more PowerShell.

One positive thing about the show is that after watching it, I was inspired to write this post.

Outline

I'm going to cover as many random useful things I've found inside PowerShell as humanly possible. Many of these things will be stupid simple; the common pattern you should notice is that, one way or the other, these scripts save me time, sanity, or time and sanity.

Let's begin!

ONE: Calling into DOS

One of the first things I tried to do in PowerShell was a recursive directory listing. In the CMD shell (the shell formerly known as DOS), the appropriate way to do this is dir /s.

It was quite a shock to find out PowerShell's dir command doesn't support a "/s" switch. Had I known back then what I know now, I would have felt better—you can run any DOS command (including those defined in the command shell itself) from PowerShell simply by prefixing the command with "cmd /c".

I'm not saying that you should be typing "cmd /c dir /s" to get a directory listing—you should be learning PowerShell's superior dir command. But I think it's also important to note that you can get the job done today, immediately, inside the PowerShell prompt, without learning any PowerShell. Just prefix your DOS command with "cmd /c".

I'll enumerate some useful things I've found, even after learning PowerShell:

cmd /c start .

(this opens the current directory in an Explorer window. The "start" command also opens documents and URLs)

cmd /c pause

(this runs the DOS pause command. This is a better solution than a "echo 'Press any key to continue…'; read-host;")

TWO: I'm too lazy to cut and paste—PowerShell is my laziness enabler

I can't say enough good things about the Set-Clipboard cmdlet, and how useful I find it. Here are some of the different things I've run:

(dir)[-1].Fullname | set-clipboard

(dir)[-2].Fullname | set-clipboard

(The above lines are just one act in a five-act play: "How do I copy the full path and name of this file?" 1. Run dir on the entire directory. 2. Eyeball the file you want. If it's close to the bottom, you can use PowerShell's "negative index" functionality to easily count backwards. 3. Get the FullName property of the file, 4. Run the command to copy this to the clipboard, 5. Paste full path and filename elsewhere. I'll note that this hasn't necessarily saved me time.)

(history)[-1] | foreach { $_.CommandLine } | out-string | set-clipboard
(First let me explain what this line does. This pulls the last command from PowerShell's command history, and copies it to the clipboard. in theory, I could have used some other way to get this command into the clipboard—but this is so much easier.)

history | foreach { $_.CommandLine } | out-string | set-clipboard

(This takes the entire history and pastes it to the clipboard. I intend to paste the PowerShell history directly into my Notepad equivalent for pruning into a real script.)

([Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")).FullName | set-clipboard

(This gets the 4-part .NET assembly name, which you'd otherwise have to manually transcribe from your Explorer window's representation of the GAC.  I have used this with SharePoint several times already.)

$a = (sn -T MySharePointWorkflow.dll)
$a[-1].Split(" ")[-1] | set-clipboard

(Assuming the above lines are errorr-free, this gets the PublicKeyToken from "MySharePointWorkflow.dll". If I'm wrong, well, let's all pretend I'm right anyway. Something looking vaguely like this has worked for me once or twice. You should be able to guess by now, but in case you haven't: yes, it's for SharePoint development.)

THREE: Automating SharePoint deployment and administration tasks

stsadm | find """feature"""

First, I'll note that the "stsadm" command is SharePoint's command-line administration tool. I'm running the "find" command to parse the text output and display any lines containing the word "feature". In other words: I'm doing a poor man's grep.

What you may notice above is the odd arrangement of double-quote marks. This gotcha is a side effect of PowerShell's pseudo-hands off approach to interpreting DOS commands. The triple-double quotes above (""") look awkward, but will evaluate down to a single set of quotes once the command is interpreted by DOS. The specifics aren't important. What's important is that once you understand how to escape characters properly like I've done above, you can easily run DOS commands from inside PowerShell. DO IT NATIVE!

SharePoint deployment scripts - I can't tell you how many times I've seen 7-line C# console utilities that are built to supplement missing SharePoint administration functionality. While a small C# console application gets the job done, PowerShell is built for this sort of one-time use console utility. If I could get away with it (and I might yet), I would love love love to abstract away all the SharePoint deployment/development/general ugliness that makes me angry, and replace it all with a post-deployment task/small script/everything script.

Another example I've seen recently: we needed to wire up a custom 404 handler and a custom 404 redirect page. "Deployment" of this solution required both updating a property on a SPWebApplication object and copying files in a very specific manner. I'm proud to say I was able to abstract away this ugliness with PowerShell!

FOUR: REPL in .NET!

EXAMPLE: I need to figure out how the System.Uri class works. Aside: if you're working on web apps and you're unaware of the Uri class, you will want to take a look at it. Moving on.

So in doing quick research on the Uri class (or any class really), there are a few options: MSDN (ha), Google (ok), in-process debug-time experimentation with the Visual Studio Immediate Window (ok), Lutz' Reflector (often excellent), or PowerShell (yessssss). I don't know if you've guessed by now, but we're going to inspect and try things out on the Uri class with PowerShell.

$u = [Uri]"http://test.site.com/testing?query=1&query2=a&query3isempty=&query4=something"
$u
$u | gm
$newUri = new-object "System.Uri" -arg $u, ([Uri]"http://some.other.site/letstry/relative/").PathAndQuery
$newUri

What's awesome about a read-eval-print loop (REPL) is that it reduces experimentation time from minutes (or hours!) to seconds. Actually, anyone who has used any scripting language will be able to tell you why REPL is awesome; feel free to read THEM instead of ME. I hear LISP has been around for a while; maybe you could ask your grandparents.

Friday, September 14, 2007 6:08:22 AM UTC  #     |  Comments [0]  |  Trackback
Wednesday, August 29, 2007 4:13:26 AM UTC #

The magic representative line

Line 17: stsadm -o uninstallfeature -id $f.DefinitionId.ToString() -force

The single line (#17) in the image above calls stsadm.exe (a console command) and passes several arguments to it: 1) a bunch of SharePoint stsadm junk/raw text, and 2) the GUID of the feature I was trying to remove.

What is certainly most uninteresting is the SharePoint details; this was development-VM administrivia. What is certainly interesting is that I have run the PowerShell interpreter where needed to grab the Feature's GUID, but sent the rest of the text directly to the console application without surrounding anything in quotes. Try and point out, on line #17, where DOS ends and PowerShell begins.

Thus, magic!

Aside from all the interpreter nuttiness, this was super useful and saved me heaps of time*, so there was some practicality involved as well.

*heaps of time => probably 20 seconds or less

Wednesday, August 29, 2007 4:13:26 AM UTC  #     |  Comments [0]  |  Trackback
Syndication

Search
Posts on this page
Categories
Sites I visit regularly
About

Powered by: newtelligence dasBlog 2.2.8279.16125

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2010, Peter Seale

Send mail to the author(s) E-mail



Sign In