Wednesday, January 18, 2012 4:48:35 AM UTC #

This may be good general advice, but today I just mean it in the context of using PowerShell's call operator (the glorious &, AKA "The Ampersand").

I could spend a lot of time building up to the good stuff, but I'll just get to the point. I'm going to run "echoargs" which most recently helped me troubleshoot calls from PowerShell to MSBuild.exe. You'll see why I need this utility soon enough:

image

Okay. That was the easy part. So far, when calling commands from PowerShell using the call operator, everything is pretty much working as expected. Now let's try something…different…:

image

I'm not exactly sure what to say here. The first example is a thinly-obfuscated real-world head-scratcher I've stumbled into over and over and over. The second line, I wrote to try and make some sense out of PowerShell's parsing rules. And when I got the output for the second line I can only make sense of by using parsing rules like "throw away some of the quotes, then start parsing" and "if the quote-marks are on the left side of the word, move them to the right". You won't find these parsing rules in an example in the dragon book.

So I kind of gave up.

You see, I had a longer, well-reasoned blog post planned out. In my pretend fairy land, I'd spend a few minutes doing research, master PowerShell's parsing rules, and write a helper method to encapsulate the weirdness so you and the rest of the world could live out your sheltered hobbit lives in the Shire, never understanding the service I provided for you. I'd be the Aragorn of this story, and would be pretty rad compared to you lame-os.

I even had a "reasonable explanation" for the weird behavior to link to here. And don't get me wrong, that's good information.

But nothing explains "   1 2 3 4", followed by "5", followed by "6    7 8 9" as your argument list.

Lesson learned: don't be me

There's probably a better lesson to be learned, like

a) trust PowerShell's call operator syntax about as far as you can throw it, and

b) when you throw it, watch the skies carefully, or the moment you turn away PowerShell will boomerang back at you and aim for your throat.

Okay.

Furthermore, echoargs.exe, which ships with the PowerShell Community Extensions, is built for the sole purpose of troubleshooting this kind of weirdness. It's useful, it's small, and it's safer than taking a boomerang to the throat every time you test.

Furthermore, when using the call operator (&), use the more explicit, longhand form. Even though it makes most calls unreadable to humans, for those of us who matter (the parser), it is clear as day. See screenshot + gaudy green text below:

image_thumb21

Furthermore, if you're writing a generic script that accepts input you can't control, and some of that input may or may not include quote-marks…find whoever is responsible for assigning you such a doomed task and punch THEM in the throat*. They deserve it**.
* don't do this
** they probably don't

By the way, if you know why these rules are the way they are, by all means answer the question here and I'll give you the appropriate whuffie or whatever they call it these days. And no, spell checker, 'whuffie' is not a misspelling.

Hope for the future

Just so you know, we may see a fix for this class of problem in PowerShell v3.

Categories: .NET | PowerShell
Technorati:  | 
Wednesday, January 18, 2012 4:48:35 AM UTC  #     |  Comments [0]  |  Trackback
Tuesday, January 10, 2012 7:06:55 AM UTC #

tl;dr

MSBuild’s OutDir parameter must be of the form:
     /p:OutDir=C:\folder\with\no\spaces\must\end\with\trailing\slash\
…or of the form:
     /p:OutDir=”C:\folder w spaces\must end w 2 trailing slashes\PS\this makes no sense\\”

I have written a self-contained PowerShell function to handle OutDir’s mini-language that exists because…I don’t know why, because they hate us? Anyway, the script is all the way at the bottom. PS “backwards compatibility” is code for “we hate you,” in case you get “backwards compatibility” as the reason OutDir’s syntax is so hostile on your Connect issue you filed so diligently. That’s also a trick, because you’re not supposed to file Connect issues.

I hate you, OutDir parameter

Okay, so the post title is unhelpful. Deal with it. I’m in pain, and a suffering man should be afforded some liberties. I’m like Doc Holiday—minus tuberculosis, plus build script duties. Or the whooping cough. I didn’t pay much attention during Tombstone, but he did cough a lot. Could be parasites.

Build script duties are some of the worst, alongside SSRS reporting duties, SharePoint integration duties, auditor-friendly deployment documentation duties, or any combination of those three. I don’t know what IT auditors do for fun—I simply can’t imagine. I don’t know if they can either. Think about it.

…back to build scripts. A bad build script will kill your chances of getting any kind of an automated deployment working, and if you can’t do builds or deployments well, you end up editing your production web.config in production and writing Stored Procedures because deploying code is just so painful. And then no one wants to deploy because it takes about three weeks and seventeen tries before you get it right, and no one’s writing any sort of automated tests around your stored procedures (except that one guy who’s waaay to excited about T-SQL, but he writes try/catch blocks in T-SQL and is pushing for Service Broker, so…can’t trust him), and this has all kinds of implications, and then all of a sudden exclusive checkouts sound like a good idea, and you wake up one morning and you’re doing Access development. Again(!!!). Except less productive. And your customers don’t trust you, and then one day you’re just fired outright, and the next day you’re on the street, and then finally, out of options, you reach the lowest low—you develop and release an app on the iTunes app store. Lowest of the low. Can’t possibly get worse, unless you’re forced to write code in Ruby, which requires you join the Communist Party, as is clearly written in the AGPL (yes, this is why Microsoft wrote their own GPL—they’re fighting both terror and communism, and socialism—one license agreement at a time). This is why you read the EULA. Communism is why.

Anyway, MSBuild’s OutDir parameter isn’t making my build script duties any easier.

Regarding OutputPath

I tried researching OutputPath, but it looks like a different metaphorical universalist path up the same mountain named “appending 1 or more slashes to the end of everything for no reason”, so I gave up. When it comes to doing in-depth research on any framework, including, and today featuring MSBuild and its wonderfulness, you either find out that a) you were woefully ignorant all along and just needed that one tidbit of knowledge, with which you can SUCCEED, or b) you were unfortunately justified in distrusting your framework because your framework has FAILED you. After a few extremely painful episodes, I started giving up early and looking for a workaround, which turns out is what most people do anyway.

OutputPath smells like it has the same problems that OutDir has, so I just gave up on it and went with the workaround (below). I could be wrong about OutputPath. Blame SharePoint for my wariness.

But I’m not only here to complain

I’m here to complain, don’t get me wrong. Like a wounded Rambo provided with only fire, kerosene and his trusty serrated knife, I’m writing this post as a kind of Rambo shout before I pass out from the pain after cauterizing my wound the Rambo way. Life sucks*.
*not actually true

But I’m also here to let you know, hey, if you’re in the Cambodian jungle* with a bullet wound and you’ve got to do something, here’s what you do. Maybe you won’t bleed all over the flora and fauna** with your bullet wound in the Cambodian jungle as long as I did, maybe this post will help you along in your journey…whatever that journey is. It’s a journey of some kind. Let’s not stretch the metaphor too far. Wait, aren’t we talking about build scripts?
*I am not going to do any research, do not question or fact-check my Rambo knowledge. Just assume I got it right.
**it seemed like the right thing to say at the time

Why: A brief explanation why OutDir exists

Now, onto something resembling a technical blog post.

OutDir exists so that, when compiling a Project (e.g. “msbuild MyProject.csproj”) or Solution (e.g. “msbuild MyManyProjects.sln”), you can tell MSBuild where to put all the files. Or if you like fancy words, “compilation artifacts for your ALM as part of your SDLC”. You’re welcome. I’m SDLC certified 7-9 years experience, ALM 8.5 years, MS Word 13 years. Hire me, I’ve got an edge on the other candidate by 2.5 years SDLC and a whopping 9 years MS Word. Numbers can’t lie! Plus I’ve got 5 years OOP, 3 years OOA, 4.5 years OOD. You can’t argue with numbers.

Where were we? Ah, putting compilation artifacts in folders. Without OutDir, you don’t have that control.

Let’s take the simple example. “msbuild MyProject.csproj” will put MyProject.dll in the bin\Debug subfolder, just like compiling from Visual Studio. If you set the configuration to Release, ala “msbuild MyProject.csproj /p:Configuration=Release”, everything will be dumped into bin\Release. If you have no idea what’s going on and you make a third build configuration, e.g. “msbuild MyProject.csproj /p:Configuration=Towelie”, the files will be dumped in bin\Towelie.

You get the idea. By default, files go in build\$Configuration, whatever $Configuration happens to be at the time.

So here comes OutDir to shake things up. Let’s try a simple example:

msbuild MyProject.csproj /p:OutDir=C:\temp\MyProject

image

Haha! Tricked you! This simple example doesn’t work! You forgot the trailing slash!*
*serious aside: would it have taken more effort to write and localize an error message in seven hundred languages including Bushman from Gods Must Be Crazy 2, or just accept the path without a trailing slash and fix it for us? I can’t imagine it would be harder to just scrub the input. I’m serious. I’m Batman voice serious. Seriously.

Okay, let’s try this again, but after paying the syntax tax:
msbuild MyProject.csproj /p:OutDir=C:\temp\MyProject\

You get exactly one guess what happens. Okay, who cares, I’ll just show you.

image

So you get the idea.

A second example, this time illustrating the use of path names with spaces

Okay, first off, MSBuild’s OutDir parameter is only one of the many, many reasons that I dislike spaces in filenames, path names, even passwords. I mean passphrases. Of course I mean passphrases. Passwords are crackable. Passphrases are the way to go.

Don’t even get me started about Uñicode support.

Second, let me point out that I can work perfectly fine without setting OutDir. I know where my files go, and I know how to reliably copy files from bin\debug folders directly into production as part of my nightly build process (PS for the humorless, don’t try that). But, I need OutDir, because TFS’s default build definition uses OutDir whether you like it or not. And, in the course of setting up a working TFS 2010 build, at the time I needed to a) understand, and b) simulate TFS’s compilation process.

Anyway, some of our TFS build names have spaces in them, which means that some of the folder names have spaces in them, which means that my script that calls OutDir needs to handle folder names with spaces in them. Let’s try vanilla latte half-chop burned-choco cream soda vento rico suave way of calling OutDir and see what happens:

image

Okay, we cheated somewhat, because we didn’t even bother to surround our long path name with quotes. Rookie! Let’s try again:

image

Okay. Surrounding your long path name with quotes, along with the trailing slash isn’t cutting it.

This “Illegal characters in path.” error message is where I’ve lost probably…let’s not estimate, my professionalism will be called into question. Anyway, let’s just say “a lot of time” was lost on this problem.

So here’s the solution:

image

I don’t know why, and at this point, I’ve lost the fighting spirit. It’s setting an output folder in MSBuild after all, I’m not exactly writing a new OS scheduler, though I have a vague idea that OS scheduling is not like Outlook scheduling, and my resume says I have 3.5 years of OS Scheduler experience, so I can speak to it.

Someone in the comments of this blog post suggested the double trailing slash solution, and what you do know it worked, and here I am much later writing a blog post that is way too long to justify this much effort.

Wrapping up what we’ve learned today, in bullet point form

  • Doc Holiday has either TB or the whooping cough. Or parasites.
  • They hate us:
    • MSBuild’s OutDir parameter must be of the form:
      /p:OutDir=C:\folder\with\no\spaces\must\end\with\trailing\slash\
    • …or of the form:
      /p:OutDir=”C:\folder w spaces\must end w 2 trailing slashes\makes no sense\\”

Wrapping up what we’ve learned today, in PowerShell function form

Enjoy. There’s almost nothing special about this. The value Run-MSBuild gives you is that it hides (or if we’re using the fancy words, encapsulates) the horrible rules OutDir imposes on us, freeing the caller to worry about, oh, I don’t know, writing an OS Scheduler.

Feel free to cut-and-paste. I’m not going to force you to join the Communist Party like the AGPL does.

And do note the commented-out psake-friendly line. Psake’s Exec function exists to encapsulate the weirdness with executing DOS commands from PowerShell. I figure, if you’re calling MSBuild, chances are good you’re calling it from psake, but if not, here’s a script that will bubble up a reasonable error message to the user.

Psake or not, if you’re calling this PowerShell script from TeamCity, the error message will bubble up to the top. If you’re using TFS, follow these instructions to experience the joy that is visual programming (and yes, you’ll also get good error messages bubbled up to the top).

Also, this isn’t one of those bulletproof, general-purpose functions, what with proper types and default values for each argument, logging via write-verbose, a –whatif switch, documentation, and whatever else I’m ignorant of. Of. I don’t do that day-to-day for my PowerShell scripts. I just write what I need today, and maybe generalize what I have if I use the same function twice in a script. It’s not like sharing functions between PowerShell scripts is desirable. Like sharing needles. A discussion of the merits of needle sharing is a good way to wrap up a blog post. And on that note, here’s the script:

 

 

$msbuildPath = 'C:\windows\Microsoft.NET\Framework\v4.0.30319\msbuild.exe'
function Compile-Project($project, $targets, $configuration, $outdir) {
  if (-not ($outdir.EndsWith("\"))) {
    $outdir += '\' #MSBuild requires OutDir end with a trailing slash #awesome
  }
 
  if ($outdir.Contains(" ")) {
    $outdir="""$($outdir)\""" #read comment from Johannes Rudolph here:
http://www.markhneedham.com/blog/2008/08/14/msbuild-use-outputpath-instead-of-outdir/
  }
 
  #if you're calling this from psake, save yourself the trouble and use their "exec" command.
  #psake:
  #exec { & $msBuildPath """$project"" /t:$($targets) /p:Configuration=$configuration /p:OutDir=$outdir" }

  #Vanilla PowerShell, non-psake:
  & $msBuildPath """$project"" /t:$($targets) /p:Configuration=$configuration /p:OutDir=$outdir" 2>$msbuildErrOutput
  if ($lastExitCode -ne 0) {
    write-error "Error while running MSBuild. Details:`n$msbuildErrorOutput"
    exit 1
  }
}

 

Categories: .NET | PowerShell
Technorati:  | 
Tuesday, January 10, 2012 7:06:55 AM UTC  #     |  Comments [2]  |  Trackback
Friday, November 11, 2011 11:07:42 PM UTC #

Every post I write should come with this standard disclaimer. If I ever re-do my blog, I’ll link to this standard disclaimer from the top of every blog post.

This Stuff Just Doesn’t Matter

This stuff, this software development stuff in and of itself, just doesn’t matter. It isn’t the end goal. There are bigger things in life.

All things being equal, it is better to be a competent software developer than an incompetent software developer. This is why I write posts about how to invest my limited time.

All things being equal, it is better to be learned rather than ignorant about software development practices. This is why from time to time I feel the urge to linkblog posts I find on twitter that I believe my blog audience (i.e. you) haven’t seen, and may benefit from. My linkblog posts are gold, I tell you, gold.

All things being equal, it is better to be intentional about your career path and career goals, especially when it comes to dealing with Microsoft’s endless framework lahar. I see a lot of time wasted on studying for exams, and attention given to half-baked frameworks that subsequently under deliver. And I don’t know why, but I have the urge to fix this problem. For those of you who could not care less about helping others make wiser choices with their learning investments, sorry, but it’s who I am, and it bothers me enough to blog about the topic…frequently.

All things being equal, it is better to go to work and experience less unnecessary pain. This is where a lot of my “written for the search engine” and “suriving TFS” posts come from, and where I hope most people find value. I write many of my blog posts with the singular goal of reducing pain. Pain isn’t the ultimate evil. (There’s a great discussion about pain in A Canticle For Leibowitz, which by the way is the first post-apocalyptic book, but I’m too lazy to find the exact quote. PS—dork alert)

All things being equal, it’s more productive for me to blog here than to sit on the couch on a Saturday and take a nap while watching college football. Though there’s nothing wrong with any combination of naps and college football. It’s also better for me to blog than to play video games; or browse the gaming subreddits; or watch someone on twitch.tv live streaming while they play video games; or best of all watch someone on twitch.tv live streaming while they browse the gaming subreddits, which frees you from the chore of browsing the internet yourself. You should probably visit that hyperlink, because it’s just perfect. It’s like watching Inception if Inception featured laziness as its major theme. It just makes sense. Go watch Inception, and go click that link.

I’m not a super expert genius ninja samurai ZeroCool hacker

If it appears that I’m presenting myself as an authority on any topic, make sure I back it up with personal experience. If I don’t have the personal experience to back up my claims, take my argument for what it is: an unsupported opinion. I know that I’m not an expert, and when writing blog posts my self-image doesn’t change—but maybe here on the internet, where they don’t know you’re not a dog, you don’t read my posts the way I intend for you to.

I’m not an expert, but if it so happens I am, I’ll tell you why.

This is a good rule in general. Given blog posts aren’t built off of months of investigative journalism or academic research, the best blog posts are harvested from personal experience (as opposed to blog posts written by pundits with no experience). And let me draw one more point from this: a lot of .NET experts aren’t experts either on the subjects they write about. They are no more an expert, no more experienced, no more capable and have no better software development experience than you or me. They’re just people like you or me with better communication skills. With that said, some of them are true experts. The difference between a good blog post and a great blog post is, in my opinion, the great blog posts are harvested from years of painful experience. Compare this great blog post to my post on the same subject, but clearly written from a newbie’s perspective for an example of this in action.

One additional point I’d like to make is that I feel like I’ve crested the hill and I get it now. Software development is a known problem for me. I’m comfortable with the things I know, and I’m comfortable not knowing the things I’m fuzzy about and still working on (see: estimation; finding out what the customer wants), and I’m comfortable with the fact that I may never learn Haskell, or SmallTalk, or BizTalk, or Joomla. This greater sense of perspective wasn’t always how I was, and I get the idea that most of the working world is full of people who don’t get it yet. So yet another of my part-time crusades is to get everyone up to speed, at least to the point where they get it. I’ve met people who (without some help) will remain forever behind, forever…for lack of a better word: incompetent. And I don’t see my “getting-it-ness” as unique expertise but simply what all software developers should have. I look around and I don’t see that…getting-it-ness. Find me a better word. I can’t write more explanatory text right now without repeating myself.

I will make every effort not to blog work arguments, or be passive-aggressive in general

My theory is most blog posts spring forth from blog arguments or work frustrations, as I feel this urge to blog work arguments from time to time. If I won’t say it in person, I shouldn’t say it on the blog. And even if I say it in person, some work arguments should be kept in the family.

Every now and then I step out of bounds.

And finally, this stuff just doesn’t matter

Software development is not important in the grand scheme of things. Being a bad software developer in and of itself does not make you morally inferior. To pick on something specifically: software craftsmanship is not a new morality, whereby you are righteous (professional?) if you write clean code and unrighteous (unprofessional?) if you don’t. Depending on the bigger picture, and I place emphasis on the phrase bigger picture, you may be doing serious harm by e.g. overdosing radiation therapy patients via your software, or more likely, putting your company out of business because of your incompetence—but in and of itself, being a bad (worse than average?) software developer isn’t evil.

This stuff just doesn’t matter.

Every post I write, no matter how passionate I may sound, no matter if in truth I get carried away and lose perspective and start believing it, this stuff just doesn’t matter.

Friday, November 11, 2011 11:07:42 PM UTC  #     |  Comments [1]  |  Trackback
Thursday, August 11, 2011 4:59:54 AM UTC #

A warning

This post in its entirety isn’t readable by humans. I’m sorry. I started by picking out a few psake scripts here and there, figuring hey. I’ll pick one or two examples and talk about what they’re doing.

The problem with writing a blog post about build scripts is it’s pretty boring. No one idly browsing their feed reader makes it through an entire post without being knocked unconscious. Ooh, that reminds me: if you’re currently operating heavy machinery or piloting a jet plane, for your safety please stop reading this blog post. Thanks.

But. But, even though it’s well known that this kind of stuff is boring to read about, I still want to collect all the knowledge on this earth related to psake and how people are using it. And I’ve done that below (at least as of 2011-08-10).

Unfortunately for you, my dear reader, I’ve made no attempt to process my raw data collection into something readable, what with sentences, paragraphs, code samples and topical grouping. That takes way too long. I’m too lazy for that.

Instead, I’m linkblogging a clump of psake scripts and mentioning what pieces you may want to steal for your own build script.

As a bonus (and because it’s part of what I’m researching), I’ve included a bunch of links to deployment-related blog posts and deployment scripts. These things are gold, and despite their seeming tinyness and insignificance, represent hours of sweat and toil.

Don’t Read This Blog Post – Search It

So I don’t expect anyone to, you know, read this post. But, if you’re like me, you’ll find that when it comes time to, say, add a NUnit test runner to your build script, or say, deploy to a remote IIS server, you’ll fire up your handy browser search (CTRL+F) and go looking for a script.

Well, maybe go ahead and read when I tell you to pay attention

A few places where I think a build script has done something novel, I’ll put a small note telling you to pay attention. It’s not meant to be insulting, but a way to un-zombify your brain so that you actually read that bullet point—so that it stands out from the endless sea of text and bullet points. I know, I could take the time to blog an entire post about each one of these points, and maybe I will. But for now, bet on my laziness and assume I won’t, and pay a little extra attention to how these folk put together their build scripts.

It’s like the famous quote from Passenger 57: “You ever play roulette? Always bet on Peter being lazy.” –Wesley Snipes, Passenger 57, word-for-word quote

Now that you’re mentally prepared for the hail of bullets that is to follow (bullet points, that is), have at it.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

JP Boodhoo wrote the first* non-trivial publicly-available psake script, and thus you’ll notice all the other scripts have borrowed bits and pieces from his script (particularly the ruby_style_naming_convention which_is_not_camel_case like_PowerShell_should_be):
*that I remember

  • build script
    • He is the only person who doesn’t rely on Solutions/Project files to compile his project, instead relying on aspnet_compiler.exe. Note, for those of you unaware, if you set the OutDir parameter for MSBuild, it will compile web application projects with surprisingly pleasant results.
    • He has written his own miniature database migration tool using only PowerShell. Not bad if I do say so myself.
    • He makes clever use of “dir” to lazily find all files he needs to compile (e.g. “dir * -include *.cs -recurse")

Ayende’s scripts:

  • Rhino ESB – default.ps1 and psake-ext.ps1
    • Compiles by running MSBuild on the .sln file
    • Packages with the NuGet.exe command-line tool
    • Zips files using the 7zip (7za.exe) command-line tool
    • Runs XUnit tests via xunit.console.clr4.exe
    • Generates AssemblyInfo.cs (which, if you’re unaware, is where you get your assembly version number from)
    • Pulls the desired version number from Git source control using the git.exe command-line tool
  • RavenDB – default.ps1 and psake-ext.ps1
    • Neat way to check for installed software (prerequisites)—this checks to ensure you have .NET 4.0 installed (see the “Verify40” task)
    • Runs a complex test scenario in the “TestSilverlight” task—it fires up a local Raven server in RAM, runs Silverlight-related unit tests, then kills the Raven server.
    • Packages files from disparate sources—RavenDB shows how it’s done. Hint: it’s not pretty.
    • Zips files using the zip.exe command-line tool (i.e., not the same tool at 7zip)
    • Builds what appears to be an intense NuGet package
    • Uploads static web content to a live environment using S3Uploader.exe
    • Note the simple build instructions found here
  • Texo (his jokingly/admittedly-NIH PowerShell Continuous Integration server)
    • builder.ps1
      • Sends email
      • Tries to get latest on a git branch via raw git.exe commands

DotLess:

  • default.ps1
    • Compiles by running MSBuild on the .csproj files
    • Runs ILMerge
    • Builds a gem (as in, RubyGems gem)
    • Builds a NuGet package

LINQToEPiServer:

  • default.ps1
    • Compiles by running MSBuild on the .sln file
    • Starts the MSDTC service (SQL Server distributed transactions) using net start
    • Does extreme funkiness with NUnit impersonating MSTest…I have no idea why.
    • Modifies all config files with a simple homebrew templating engine (think string.format’s {0} {1} etc.).

CodeCampServer:

  • psake.bat
    • A pretty good psake launcher that does everything you need to run the build script, plus highlights failed builds.
  • default.ps1
    • Compiles by running MSBuild on the .sln file
    • Includes a large number of helper functions. Pay attention to the fact that in psake, you don’t have to use tasks for everything—by all means write first-class functions that accept arguments! Arguments! They’re awesome! Use them!
    • Runs Tarantino (database migration tool)
    • Runs FXCop and something called “SourceMonitor”
    • Runs NUnit both with and without NCover code coverage metrics
    • Zips whole directories
  • nant.build
    • I know this has nothing to do with psake, but there’s a lot of stuff in there. A lot of the command-line call-outs can be converted to your needs.
  • Deployment helper functions nicely packaged into PowerShell module files (psm1)
    • Database.psm1 - Uses .NET’s SMO objects(?) to interact with SQL Server
      • Creates SQL Server user (an Integrated user, not a native SQL user) on the SQL instance and on the SQL database
      • Does something scary-looking that appears to export an entire database, but not the way you’re thinking—not the normal way of exporting a database.
    • Package.psm1 – Uses a COM object called “shell.application” to Zip a directory
      • Unlike my (and everyone else’s) implementation, this zip function makes use of object piping to receive the list of files. Nice.
    • ScheduledJobs.psm1 – Uses a COM object “schedule.service” to manipulate Windows Scheduled Tasks
      • Creates a new scheduled task.
    • Windows.psm1 – Uses PowerShell’s WMI support to create local (not domain) users and assigns users to groups.
      • Creates a local user on the machine
      • Adds a user to a local group
    • IIS.psm1 – uses the “WebAdministration” IIS cmdlets to manipulate IIS
      • Creates an IIS website object and actually sets the bindings successfully (yessssssssss).

Aaron Weiker’s blog series

  • sample psake script from his blog post
    • Compiles by running MSBuild on the .sln file
    • Configures app.configs with environment-specific modifications using XPath (i.e. a lot more like the NAnt/MSBuild helpers, and less hacky than doing string search & replace)
    • Runs RoboCopy
    • One neat thing I haven’t started doing, but desperately need to start doing, is to start throwing exceptions if script/function parameters are not passed in. Pay attention and see lines #1-4 of his psake script to see what I mean by this. I’ve lost hours of my life I will never get back troubleshooting PowerShell scripts over the years only to find that I passed in a paramter called “-name” when I needed to pass in a parameter called “-fullname”. So, if you don’t do this either, start doing it.

Darrel Mozingo’s blog series

  • sample psake script from his blog post
    • Compiles by running MSBuild on the .sln file
    • Runs NCover and NCoverExplorer
    • Includes helper methods that won’t make any sense to you until you actually use PowerShell and are annoyed by the same things that caused him to write those one-line helper methods. Pay attention to the little things he does in his helper methods that you probably think are fluff. Pop quiz: why did he write a create_directory helper method? I’ve experienced the pain and know the answer. If you haven’t, take my and his word for it and at least attempt to figure out why those helper methods exist.
  • Four-part series on deployment with PowerShell (1, 2, 3, 4)
    • Part 2:
      • Modifies web.config via PowerShell’s built-in [xml] object wrapper (but only making a minor edit)
      • Pre-compiles the ASP.NET site
      • Writes a CPS-style (CPS-style-style? I feel better now.) function that maps a network share, yields to the caller, then unmaps when done.
      • Takes a configuration backup of the live ASP.NET site
    • Part 3:
      • Remotely manages IIS via PowerShell remoting (starting & stopping IIS)
    • Part 4:
      • Rewrites the system hosts file
      • Tests current DNS settings (cool!)
      • Loads Internet Explorer to ping the website to force it to compile itself
      • Verifies emails are being sent (so hot!)

A blog series

  • psake script and run.bat (download demo.zip linked from this site if you want to see the raw psake script)
    • The run.bat sample does something novel—pay attention to how it loads PowerShell as a shell (REPL environment), not as a run-and-exit script. Smooth.
    • IIS adminstration via a mix of IIS (“WebAdministration”) cmdlets and WMI. Smooth. Creates a website and a new AppPool.

Señor Hanselman apparently wrote a whitepaper about deploying with PowerShell

  • Gets latest from a SVN repository via a .NET SVN library
  • Does heap big remoting work pre-PowerShell 2.0 (i.e. ,before PowerShell had any built-in remoting support)

Mikael Lundin (litemedia) blogged

I should mention Derick Bailey’s Albacore project for .NET – it’s a collection of Rake (Ruby) tasks that are the equivalent of a lot of what I’ve listed above. And from what I’ve seen, it has some things I haven’t covered above. Here’s list of things it does, machine-gun-style:

  • csc.exe, docu, FluentMigrator, MSBuild, MSpec, MSTest, NAnt, NChurn, NCover, NDepend, NUnit, NuSpec, Plink, SpecFlow, Sqlcmd, zip/unzip, XBuild, XUnit.
Categories: .NET | PowerShell
Technorati:  | 
Thursday, August 11, 2011 4:59:54 AM UTC  #     |  Comments [2]  |  Trackback
Monday, January 17, 2011 4:00:00 PM UTC #

Welcome to 2011. It smells terrific here!

The problem

You may not know it, but you have a problem. You’re using the standard Windows command shell. This is a problem.

image

The problems are manyfold and boring, so I’ll briefly summarize:

  • Cutting and pasting is a problem.
  • The cmd shell’s default history is 100 lines. This is a problem.
  • DOS’s autocomplete featureset predates the word intellisense. It’s bad.
  • DOS hates double-quotes. A lot.
  • DOS also hates the less-than/greater-than characters. Try this on: runas /user:PC\windersUser /password:”I believe in using long passphrases and good security etc and so forth so I’ll throw in some special characters, like double-quotes (“) and a bunch of other random stuff: <>file1"
    • I waxed a little eloquent on the point above, and could go into futher boring detail, but just take my word for it. DOS doesn’t do windows, and DOS doesn’t escape special characters. Ever.

The solution

junk1

The solution is to launch PowerShell. For the privileged few running Windows 7, it comes pre-installed. For the rest of us, minus the crazy dude still running Windows 2000 for security/paranoia reasons, PowerShell can be downloaded.

A small aside: the Start menu in Windows 7 is excellent. I don’t maintain icons on my desktop, the quick launch, pin programs to the taskbar, clicking through the Start Menu. I just tap the Windows key and tap in a few letters. For PowerShell, WINDOWS, “p” “o” “w”, then ENTER. That’s it.

I rarely use SlickRun nowadays.

Ahem. Onward.

So now I’m running PowerShell…now what?

You get:

  1. Better autocomplete, especially with file and pathnames.
  2. Better default settings, including an output history that stores $HUGE_NUMBER lines.
  3. A shell that doesn’t hate spaces and double-quotes, and by extension, you.
  4. Little neat things, like dynamic vertical and horizontal resizing, and…
  5. Easy cutting and pasting. Allow me to give a full tutorial below:

Cutting to the clipboard

junk1

Pasting to the PowerShell host

junk2 

It’s The Little Things

Tonight I’m working through the Ruby koans. I know, who cares. But I’m here to tell you that, though there’s not all that much difference tonight between using the cmd shell and the PowerShell host, there’s a few little things that add up. Here’s a little thing: just now, I made this simple, tiny improvement that combined the cls command and the “run the koans” command into one line, which made iterating through the Koans that much more easier. Re-running the koans is now as easy as UPARROW, ENTER:

junk3

Footnotes

I haven’t bothered trying Console2 yet.

I know cmd.exe is technically not the DOS shell. Technically it still has all the interpreter problems DOS 3.3 had, so I’m calling it DOS, plus the full name for the built-in shell is probably something like Microsoft Windows Command Shell 2011 for the Microsoft Windows 7 Operating System Administration Pack R2 (KB994112). I just made that up, but if you think the name is a total exaggeration, go research why we call “VSTO” by a four-letter acronym.

Running cmd.exe inside PowerShell (strictly for the lazy)

If you love everything I’ve said, but can’t summon the mental energy to learn remedial PowerShell, that’s okay. You can still gain some benefit from the PowerShell host running the DOS command shell! Just type “cmd<ENTER>” at the prompt, to roll into the land of LEGACY.

junk4

Categories: Awesomeness | PowerShell
Technorati:  | 
Monday, January 17, 2011 4:00:00 PM UTC  #     |  Comments [3]  |  Trackback
Monday, August 24, 2009 10:08:21 PM UTC #

The PowerShell REPL is awesome.

PowerShell is by no means the only REPL. There's the immediate window in Visual Studio, the Snippet Compiler, LINQPad, the Interactive C# shell from Mono, and a REPL environment for most every other scripting language on the planet. Some of the TDD guys refer to "exploratory tests" that they write to learn about a third-party API. On the Regex front, there are scads of web-based and Windows-based tools to help you build and test regular expressions as fast as you can hit the "Run" button. I'll even accept writing a console application as a weak form of a REPL, though I wouldn't encourage it. All these things serve the same goal: give me instantaneous feedback. For those of you already familiar with the REPL, we're good, we're in the know.

But if you're the person who never uses a REPL, allow me to show you, using an example from just 3 minutes ago, how powerful they are.

My burning question

All this began with a burning question: what happens in string.Format() if I place the parameters out of order? What happens if I use a parameter twice?

The question, answered within 1 minute using the PowerShell REPL

Conclusion stated in words

I answered a specific question about .NET's string.Format() library function in less time than it would have taken to search and peruse the search results. Sandboxes such as these reduce the friction and enable me to run a series of experiments as quickly as I can think of them. Good REPLs (like PowerShell) allow me to a) quickly get feedback on my input commands, b) format and parse the resulting objects into a meaningful answer. Bad feedback loops (things that aren't REPLs) require overhead to even run, deliver feedback in over an hour or even as late as the next day or the next week, and deliver meaningless answers (think huge log files). I'm just here to make sure you're all aware: you have a choice: you can choose a REPL, or you can choose awfulness. Your call.

Oh yeah, what's a REPL?

Read-eval-print loop

Categories: PowerShell
Technorati:
Monday, August 24, 2009 10:08:21 PM UTC  #     |  Comments [0]  |  Trackback
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 [3]  |  Trackback
Tuesday, October 28, 2008 1:52:14 AM UTC #

Yes, I'm aware it's late in the year 2008, I'm aware this stuff isn't as fresh as WPF 3D or Ruby Processing.

As I've posted earlier, I've accrued some treasured junk. Now that I have all this junk, what am I to do? Well, um…I didn't really know either.

So I started messing around.

Messing around with System.Drawing: first, infrastructure

The first thing I did was to determine the average color for a single image. I'm not sure exactly where I'm going, but I figure, hey, if you want to get a rough "picture" of what an image looks like, it's not a bad idea to look at the average color value. And we're using the RGB breakdown for color, meaning white is #FFFFFF (256,256,256), black is #000000 (0,0,0), and everything else falls in between.

Note that in my case, performance is not a big deal; I'm doing all these calculations one pixel at a time which, as you might image, is suboptimal. Mostly a straightforward operation:

public static Color Average(Image image)
{
    using (Bitmap bitmap = new Bitmap(image))
    {
        int red, green, blue;
        long redRunningSum = 0, greenRunningSum = 0, blueRunningSum = 0;
        long numPixels = bitmap.Width * bitmap.Height;

        foreach (Color pixelColor in ImageHelper.GetPixelsFor(bitmap))
        {
            redRunningSum += pixelColor.R;
            blueRunningSum += pixelColor.B;
            greenRunningSum += pixelColor.G;
        }

        red = (int)(redRunningSum / numPixels);
        green = (int)(greenRunningSum / numPixels);
        blue = (int)(blueRunningSum / numPixels);

        return Color.FromArgb(red, green, blue);
    }
}

Ok, so why do we care—it's a function, right? Well, okay, yes—but here's a PowerShell function you may also find interesting:

function Average-Images ($filenames)
{
    [void][reflection.assembly]::Loadfile("C:\a\sandbox\ImgTest\bin\Debug\ImgTest.dll")
    $i = 1
    $total = $filenames.count
    $results = @()
    foreach ($filename in $filenames)
    {
        write-host "$i - $($i*100/$total)%- $($filename)"
        $i++
        $img = [System.Drawing.Image]::FromFile($filename)
        $o = new-object PSObject
        $avg = [ImgTest.ImageHelper]::Average($img)
        add-member -inp $o -membertype "NoteProperty" -name "Filename" -value $filename
        add-member -inp $o -membertype "NoteProperty" -name "Image" -value $img
        add-member -inp $o -membertype "NoteProperty" -name "Red" -value $avg.R
        add-member -inp $o -membertype "NoteProperty" -name "Green" -value $avg.G
        add-member -inp $o -membertype "NoteProperty" -name "Blue" -value $avg.B
        $results += $o
    }
    $results
}

So. This is getting interesting. What the "Average-Images" function above does is create a custom object with some useful properties: we've got the original filename, we've got a still-breathing reference to the System.Drawing.Image object, and we're storing the "average pixel's" red, green, blue values as individual properties. The resulting objects look something like this:
image

Maybe it's still not interesting for you. That's fine, 'cause this party's* just getting started!
*despite what I've just written, this is not a party

I have one more piece of "infrastructure" to explain, before we can get cooking: I've created a PowerShell function called "Make-Html," which creates a permanent HTML file listing all the images I want to see, in the order I want to see them. As an added bonus, the function immediately launches the newly-created file in my browser. Here's the code:

$startDir = "C:\a\ps1\scrape\"
function Make-Html ($fullfilenames, $resultingFilename)
{
    $files = $fullFilenames | % { $_.split("\")[-1] }
    $tags = $files | % { "<div style=""float:left;""><img src=""$_""/></div>" }
    $html = @"
<html><head><title>$($resultingFilename)</title>
</head><body>
$($tags)
</body></html>
"@

    $html > "$($startDir)$($resultingFilename).html"
    ii "$($startDir)$($resultingFilename).html"
}

Ok, I know, we're still not doing anything.

Let's warm up

Okay, as I say to everyone, the real power of PowerShell is its object piping. PowerShell pipes objects, not text; this is something best seen, not heard, and hopefully we'll see a little something today. The objects we'll be slinging through the pipeline today are, as mentioned above, custom objects that have a Filename, an Image, and the RGB values representing the image's average (mean?) color.

So, let's count how many items we have:
image

Awesome. Let's count how many items we have that are more red than any other color:
image

Hmm, that was unexpected, 359 red-dominant images out of 503, that's proportionally huge. I'll point out that I did some extra fanciness to get this count to evaluate on one line, but usually (i.e. when I'm not posting to my blog) I'll work my way in parts, not all at once. So the same thing, split out, would be:
image

That's more realistic.

Okay, one more thing before we go. Finding out most of my pictures are red-dominant has me wondering: what about the other two? Let's work with the objects a little* to massage the answer out of them:
*a lot; ugly function that pulls out the dominant color not shown
image 

Weird.

Skipping ahead to the end

This is the pattern: we'll ask a burning question, we'll form this question as a PowerShell pipeline, and we'll see the results.

Question: can we see the images in order of "redness"?

Pipeline:

$a | sort red | % { $_.filename }

Results:

Least red:

20080707024010
200549766 

Most red:

20080524165059
439193020

Summary: okay, that makes sense. We used a naive algorithm that simply counted the red value, meaning that a pure black image or a pure blue image would have the "least redness" and a pure white image would have as much "redness" as a pure red image. Hmm, we can fix this. Onwards!



Question: Okay, so we're looking for redness. Let's call this proportional redness. Hmm, here we go:

Pipeline:

$relativelyRed = $a | select filename, @{Name="redness"; expression={$_.red / ($_.red+$_.green+$_.blue) }}
$relativelyRed | sort redness | % { $_.filename }

Results:

Least red:

883437048
20080327175323

Most red:

899605173
20080418093945
20080524164842

Summary: now that's more like it. Our earlier naive results were instructive, but this is more what I was looking for.



Question: okay, so let's stop messing with redness. Instead, let's find out what images have the most variance between the colors. We're less interested in the white-gray-gray-gray-black spectrum, and are looking for more colorful images. Let's do this:

Pipeline:

$variance = $a | select filename, @{Name="Variance"; Expression={$avg = ($_.red+$_.green+$_.blue)/3; $var = [math]::Abs($_.red-$avg) + [math]::abs($_.green-$avg)+ [math]::abs($_.blue-$avg); $var} }
make-html -fullfilenames ($variance | sort variance | % { $_.filename }) -resultingFilename "variance"

Results:

Most balanced:

783914459
281592264
741444854

Most variance:

20080831161327
20080701085401
787193910
307907780

Summary: most interesting, besides a grouping of the "grayish" and "black and white" images all together, is the smattering of images that have color, but are so perfectly balanced they're nestled right in there with the pure black-and-white images. Neat.

Final bits

This post is already too long. There's not too much else to say, besides a) stuff is awesome, and b) with the aid of either PowerShell functions or .NET library calls, you can do some complex things. If you only remember one thing from this post, try and pick up the impression I'm trying to leave. This is how I see PowerShell: it's an experimental playground where I morph a thought, an idea, slowly into something workable, and in each step along the way, I'm getting feedback and refining, and in the end, I've satisfied my curiousity. Maybe it's something as useless as basic image analysis using System.Drawing.

Incidentally, if you want to see how the professionals do this kind of thing, check out Multicolr - an color search engine indexing 10 million Flickr pictures, which makes the stuff I did above kind of pitiful looking :) When I checked last, the Multicolr site was slow, otherwise it's neat; check it out.

Categories: Awesomeness | PowerShell
Technorati:  | 
Tuesday, October 28, 2008 1:52:14 AM UTC  #     |  Comments [1]  |  Trackback
Friday, May 16, 2008 7:19:58 AM UTC #

I am sometimes asked what PowerShell is. While it's easy enough to give a technical overview, today I'll focus on everything else.

PowerShell is not a fly-by-night scripting tool

It isn't JScript, and won't disappear anytime soon. For the three JScript enthusiasts remaining in the world, I apologize for the cheap shot. Wait, no I don't. It's JScript—no one's using it, no one will be offended.

PowerShell has had a rapid rise since it reached beta status in late 2006, and if you're wondering if this positive trend will continue, let me lay it out for you eloquently:

WHY YES IT WILL, AS A MATTER OF FACT

You may have already heard that Exchange 2007 uses PowerShell for its administration console. Which is great, but I personally couldn't care less—I've got bigger fish to fry.

SQL 2008: PowerShell support. Still don't care, but we're getting warmer.

IIS 7: PowerShell provider. Ok, I like this. Still in beta though, we'll give it some time before any "visible enthusiasm."

administrative duties for Microsoft products going forward - pretend the link to the left is from an authoritative source. Because had I better searching skills, it would be. The point is: major Microsoft server products will begin to support PowerShell in the same way as Exchange 2007 (which again, we couldn't care less about).

If all that hasn't convinced you, maybe the visual aid below will:

image 

In Windows Server 2008, PowerShell is an available Feature! We're beyond installers; PowerShell is now built into the operating system! This is even true of the older Operating Systems—if you don't believe me, try uninstalling PowerShell! Can't find it? That's because (in the older OSes anyway) it's installed as an update to Windows, as a standalone hotfix. Hotfix, hotness! If I was rapping I could make "hotfix" and "hotness" rhyme. I could  pull it off.

PowerShell is not going to replace the command shell (cmd.exe)

I briefly thought this would be the case, then Server 2008 Core dashed my hopes. While I admire the engineering-won-over-marketing victory that allowed Server Core to ship with almost absolutely nothing, including all of the .NET framework, I wish we could have simply declared 2008 as "year of the PowerShell" and Server 2008 as "the painful version for the PowerShell laggards." That would have been awesome.

But, as it turns out, Server Core did indeed ship without PowerShell, and as such, we still have cmd.exe available to us, awful parsing rules and all (e.g. try the following: echo "greater than sign is >" - I assure you that will not produce the desired effect). Anyway.

You are not wasting your time learning PowerShell

First, I'll point out that PowerShell is actually quite easy to pick up, and that you can do so with a single PowerShell book. Furthermore, I'll point out that the currently-in-pre-alpha-V2 version of PowerShell is additive and does not contain any breaking changes. Like C#, PowerShell will grow, only. And grow, awesome!

PowerShell doesn't have to be slow

I've read that the PowerShell process will be slow loading the first few times. Which is terrible, because many of us won't make it past those first few times, especially those of us who are cynical of the whole thing to begin with. I assure you, it gets faster.

PowerShell is useful

I'm not telling you to pick up PowerShell to satisfy your yearly programming language learning quota. I'm also not telling you PowerShell will change the way you code in Java or COBOL or SNOBOL or FORTRAN or RPG or whatever. I will instead say that I have saved myself time (and more importantly, aggravation) with PowerShell, whether by spelunking the SharePoint object model, completely automating tasks, or running scripts as post-build actions on my Visual Studio projects…

PowerShell saves me time. And if it doesn't save me time, then it's saving my sanity. And if it isn't doing either of those two things, then it's probably pulling in some mixture of .NET framework classes, using enhanced filesystem/XML/registry support, AD stuff, COM objects, and yes, even running other console applications—it's pulling any or all of these things into one environment, in a way you won't see anywhere else. PowerShell is the de-facto scripting environment for Windows—in fact I used to call it "Perl for Windows, except Perl for Windows already exists, and actually competes directly with PowerShell." Yes, I can't help myself when describing PowerShell and say the quote in full, all the way to the end of the long rambling sentence (like this one (it doesn't seem to ever end)). Well, let me set the record straight as to PowerShell and Perl:

PowerShell is better than Perl

There, I said it.

Well, for Windows, anyway.

Categories: PowerShell
Technorati:
Friday, May 16, 2008 7:19:58 AM UTC  #     |  Comments [0]  |  Trackback
Wednesday, April 23, 2008 10:56:08 AM UTC #

Update: welcome to the new-and-improved recap! This recap, unlike the last, weighs in at less than a million billion byes of text and code highlighting and will now load in your feed reader.

This is a recap of my entries into the 2008 Winter Scripting games. From, like, the winter. I know, it's April, we're already in summertime mode here in Texas, and it is quite clear that it is no longer 'the winter'. Let's move on, shall we?

I'm going to work through each of the following scripts illustrated in the table below (the counts for "Lines", "Words", and "Characters" mean what you would imagine they mean):

image

Compare the above table to MOW, who, for example, had a 1 line, 8 word, 58 character solution for #6. My solutions are (relatively) HUGE. So my recaps, which contain the full source, will necessarily be HUGE as well.

For each problem, I'm going to quickly sum up the interesting (at least TO ME) bits of each problem, then I'm going to post the full source.

2008 Winter Scripting Game Events: Index

Categories: Awesomeness | PowerShell
Technorati:  | 
Wednesday, April 23, 2008 10:56:08 AM UTC  #     |  Comments [0]  |  Trackback
Wednesday, April 23, 2008 3:47:17 AM UTC #

If you aren't already in the know, these ten problems are from the 2008 Winter Scripting games.

For each problem, I'm going to quickly sum up the interesting (interesting TO ME) bits of each problem, then I'm going to post the full source.

Event 10: Blackjack!

Here we are asked to simulate a text-based Blackjack game. Mine works, I'm certainly not proud of the graphics, not proud of the colors, not even necessarily proud of the structure of my solution.

Please do not look at any of the following code and think "that's how I'm supposed to use objects in PowerShell!" Please don't. It's NOT ideal, I made several design mistakes; I don't even think most of my objects are necessary. Or, thinking from the other side—maybe I didn't go far enough.

Source

#PROBLEM #10
$cardValues = @{
    "Ace" = 11;
    "King" = 10;
    "Queen" = 10;
    "Jack" = 10;
    "Ten" = 10;
    "Nine" = 9;
    "Eight" = 8;
    "Seven" = 7;
    "Six" = 6;
    "Five" = 5;
    "Four" = 4;
    "Three" = 3;
    "Two" = 2;
}


$suits = @("Spades", "Hearts", "Diamonds", "Clubs")


#bad random number generator
$rnd = new-object Random




function Create-DeckObject
{
    $o = new-object PSObject
    $cards = @()
    foreach ($suit in $suits)
    {
        foreach ($cardValue in $cardValues.Keys)
        {
            $cards += Create-CardObject -value $cardValue -suit $suit
        }
    }
   
    $sortedDeck = $cards | sort PositionInDeck
   
    Add-Member -inputObject $o -memberType NoteProperty -name "Cards" -value $sortedDeck
    Add-Member -inputObject $o -memberType ScriptProperty -name "Deal" -value { $nextCard = $this.Cards | select -first 1; $this.Cards = $this.Cards | select -last ($this.Cards.Count - 1); $nextCard }
   
    $o
}


function Create-CardObject ($value, $suit)
{
    $o = new-object PSObject


    Add-Member -inputObject $o -memberType NoteProperty -name "Card" -value ("$value of $suit")
    Add-Member -inputObject $o -memberType NoteProperty -name "BlackjackValue" -value $cardValues[$value]
    Add-Member -inputObject $o -memberType NoteProperty -name "PositionInDeck" -value $rnd.NextDouble()
   
    $o
}


function Create-HandObject ([switch]$isdealer)
{
    $o = new-object PSObject


    $emptyHand = @()
    Add-Member -inputObject $o -memberType NoteProperty -name "Cards" -value $emptyHand
    Add-Member -inputObject $o -memberType NoteProperty -name "IsDealerHand" -value $isdealer


    #this value will change for the dealer hand as the game progresses.
    Add-Member -inputObject $o -memberType NoteProperty -name "AreAllCardsRevealed" -value (-not $isdealer)
    Add-Member -inputObject $o -memberType ScriptProperty -name "BlackjackValue" -value { $total = 0; $this.Cards | % {$total += $_.BlackjackValue}; $total }
   
    $o
}


function Print-VisibleCards ($cards,$allcardsvisible,[switch]$isdealer)
{
    $output = @()
    if ($allcardsvisible -eq $TRUE)
    {
        foreach ($card in $cards)
        {
            $output += $card.Card
        }


        #POSTSCRIPT-OOPS! Looks like I'm calculating the "total value of hand", when
        #I expose that value as a script property! This function would have been
        #IDEAL as a ScriptMethod attached to the Hand object.
        $total = 0; $cards | % { $total += $_.BlackjackValue }
        if (-not $isdealer)
        {
            $output += "You have $($total).`n"
        }
        else
        {
            $output += "Dealer has $($total).`n"
        }
    }
    else
    {
        #if the dealer's hand is invisible, we don't know their total, so
        #don't display it; also we can only see the second card.
        $output += $cards[1].Card
    }
   
    $output | out-string
}


function Show-Status ($my, $dealer)
{
    Write-Host "Your cards:"
    Write-Host (Print-VisibleCards -cards $my.Cards -allcardsvisible $TRUE) -foregroundColor Green
   
    Write-Host "Dealer's cards:"
    Write-Host (Print-VisibleCards -cards $dealer.Cards -isdealer -allcardsvisible $dealer.AreAllCardsRevealed ) -foregroundColor Red
}


function Show-Action ($text)
{
    Write-Host $text -foregroundColor Yellow
    Start-Sleep -seconds 1
}


function Solve-Problem10
{
    $deck = Create-DeckObject
   
    $myHand = Create-HandObject
    $dealerHand = Create-HandObject -isdealer
   
    Show-Action "Dealing starting hands…"
    $myHand.Cards += $deck.Deal
    $dealerHand.Cards += $deck.Deal
    $myHand.Cards += $deck.Deal
    $dealerHand.Cards += $deck.Deal


    #show starting hands
    Show-Status -my $myHand -dealer $dealerHand
   
   
    #give the player the option to hit
    while ($myHand.BlackjackValue -lt 21)
    {
        Write-Host -noNewLine "Stay (s) or Hit (h) ? "
        if ((Read-Host) -match "h")
        {
            Show-Action "You have chosen to Hit."
            $myHand.Cards += $deck.Deal
            Show-Status -my $myHand -dealer $dealerHand
        }
        else
        {
            Show-Action "You have chosen to Stay."
            break
        }
    }
   
    #did we bust? If so, quit.
    if ($myHand.BlackjackValue -gt 21)
    {
        Show-Action "YOU LOSE! You busted!"
        return
    }


    #dealer reveals their cards
    $dealerHand.AreAllCardsRevealed = $TRUE
    Show-Action "Dealer has revealed their cards."


    Show-Status -my $myHand -dealer $dealerHand
    #extra delay so that we can process what is in the dealer's hand.
    Start-Sleep -seconds 3
   
   
    #give the dealer the option to hit
    while ($dealerHand.BlackjackValue -lt $myHand.BlackjackValue)
    {
        Show-Action "Dealer takes a card."
        $dealerHand.Cards += $deck.Deal
        Show-Status -my $myHand -dealer $dealerHand
    }
   
    #did dealer bust? If so, VICTORY!
    if ($dealerHand.BlackjackValue -gt 21)
    {
        Show-Action "YOU WIN! VICTORY! Dealer busts!"
    }
    else
    {
        Show-Action "YOU LOSE! Dealer has $($dealerHand.BlackjackValue), you have $($myHand.BlackjackValue)."
    }
}


Solve-Problem10

2008 Winter Scripting Game Events: Index

Categories: PowerShell
Technorati:
Wednesday, April 23, 2008 3:47:17 AM UTC  #     |  Comments [0]  |  Trackback
Wednesday, April 23, 2008 3:46:56 AM UTC #

If you aren't already in the know, these ten problems are from the 2008 Winter Scripting games.

For each problem, I'm going to quickly sum up the interesting (interesting TO ME) bits of each problem, then I'm going to post the full source.

Event 9: You're Twisting My Words

Simple: reverse letters in each word, WITHOUT reversing the order of the words. It's a simple case of breaking down the huge string into tokens, then reversing the contents of the tokens, then re-combining the tokens. Actually it's even simpler than I described. Moving on.

Source

#PROBLEM #9
function Reverse-LettersInWord ($word)
{
    $reversedChars = @()
    for ($i = $word.Length - 1; $i -ge 0; $i)
    {
        $reversedChars += $word[$i]
    }
   
    [string]::Join("", $reversedChars)
}


function Solve-Problem9
{
    [string]::Join(" ", ( ( (cat "C:\Scripts\alice.txt").Split(" ") ) | % { Reverse-LettersInWord -word $_ } ) )
}



Solve-Problem9

2008 Winter Scripting Game Events: Index

Categories: PowerShell
Technorati:
Wednesday, April 23, 2008 3:46:56 AM UTC  #     |  Comments [0]  |  Trackback
Wednesday, April 23, 2008 3:46:35 AM UTC #

If you aren't already in the know, these ten problems are from the 2008 Winter Scripting games.

For each problem, I'm going to quickly sum up the interesting (interesting TO ME) bits of each problem, then I'm going to post the full source.

Event 8: Making Beautiful Music

In this problem, we are asked to generate a random playlist for our CDs. I'll just say: I cheated. If you look carefully, you'll notice I sorted the songs from longest to shortest, making the "song packing" algorithm more likely to succeed. But it's not perfect and can fail.

So, in summary, I solved the problem like everyone else, but probably not well enough.

Source

#PROBLEM #8
$filename = "C:\Scripts\songlist.csv"
$global:nextId = 0
$minimumSecondsPerCd = (75 * 60)
$maximumSecondsPerCd = (80 * 60)
$maximumSongsPerArtistOnPlaylist = 2


function Create-SongObject ($artist, $title, $durationString)
{
    $o = new-object PSObject


    Add-Member -inputObject $o -memberType NoteProperty -name "Artist" -value $artist
    Add-Member -inputObject $o -memberType NoteProperty -name "Title" -value $title
    Add-Member -inputObject $o -memberType NoteProperty -name "Duration" -value $durationString


    Add-Member -inputObject $o -memberType ScriptProperty -name "GetSeconds" -value { Get-DurationInSeconds $this.Duration }


    Add-Member -inputObject $o -memberType NoteProperty -name "Id" -value $nextId
    $global:nextId++


    Add-Member -inputObject $o -memberType ScriptProperty -name "PrintDetails" -value { "$($this.Artist)`t$($this.Title)`t$($this.Duration)"}
   
    $o
}


#POSTSCRIPT-OOPS: I forgot the TimeSpan class! Argh! Instead of
#using TimeSpan, I implement it again here below!
function Get-DurationInSeconds ($durationString)
{
    $column = $durationString.Split(":")
    $minutes = [int]$column[0]
    $seconds = [int]$column[1]
   
    $minutes * 60 + $seconds
}


function Get-AvailableSongs
{
    foreach ($line in (cat $filename) )
    {
        $column = $line.Split(",")
        Create-SongObject -artist $column[0] -title $column[1] -durationString $column[2]
    }
}


function Sum-PlaylistTime ($playlist)
{
    #initialize to 0 in case of empty playlist
    $totalSeconds = 0
   
    foreach ($song in $playlist)
    {
        $totalSeconds += $song.GetSeconds
    }
   
    $totalSeconds
}


#POSTSCRIPT-OOPS: ok, well, this isn't an oops moment
#necessarily. But I do apologize for the THIRTY ONE
#CHARACTER variable name used below. 31!
function Has-PlaylistReachedArtistSongLimit ($artist, $playlist)
{
    $playlistArtistSongCount = $playlist | group Artist | ? { $_.Name -eq $artist } | % { $_.Count }
   
    $maximumSongsPerArtistOnPlaylist -le $playlistArtistSongCount
}


function Add-NextEligibleSong ($catalog, $existingPlaylist)
{
    $existingPlaylistIds = $existingPlaylist | % { $_.Id }
   
    foreach ($candidateSong in $catalog)
    {
        if ($existingPlaylistIds -contains $candidateSong.Id)
        {
            continue
        }
       
        if ( Has-PlaylistReachedArtistSongLimit -artist $candidateSong.Artist -playlist $existingPlaylist )
        {
            continue
        }
       
        #I should handle the possibility of building a playlist in a combinatorial manner, i.e.
        #I should be attempting to remove songs to make room for others if necessary.
        #this would be the spot for it (would require some restructuring in how I add/remove songs from the list)
        if ( ((Sum-PlaylistTime $existingPlaylist) + $candidateSong.Seconds) -gt $maximumSecondsPerCd)
        {
            continue
        }


        return $candidateSong
    }
   
    Throw "No candidate songs remain."
}


function Solve-Problem8
{
    $songs = Get-AvailableSongs | sort GetSeconds -desc
   
    $myPlaylist = @()
   
    while ((Sum-PlaylistTime $myPlaylist) -lt $minimumSecondsPerCd )
    {
        $myPlaylist += Add-NextEligibleSong -catalog $songs -existingPlaylist $myPlaylist
    }
   
    foreach ($song in $myPlaylist)
    {
        Write-Host $song.PrintDetails
    }
   
    $totalSeconds = Sum-PlaylistTime $myPlaylist
    $totalMusicMinutes = [int]([Math]::Floor($totalSeconds / 60))
    $totalRemainingSeconds = $totalSeconds % 60
    $totalMusicTime = "$($totalMusicMinutes):$($totalRemainingSeconds)"
   
    Write-Host "`nTotal music time: $totalMusicTime"
}




Solve-Problem8

2008 Winter Scripting Game Events: Index

Categories: PowerShell
Technorati:
Wednesday, April 23, 2008 3:46:35 AM UTC  #     |  Comments [0]  |  Trackback
Wednesday, April 23, 2008 3:46:13 AM UTC #

If you aren't already in the know, these ten problems are from the 2008 Winter Scripting games.

For each problem, I'm going to quickly sum up the interesting (interesting TO ME) bits of each problem, then I'm going to post the full source.

Event 7: Play Ball!

Here we are asked to randomly schedule games for six teams in a round robin tournament. Apparently I used Knuth's classic list order randomization algorithm, which I remember attempting to implement, badly, and failing!,  in high school. Yay for progress.

Otherwise the solution is straightforward: two nested loops, generate matches in a "combinatorial" way, then randomize the order. Boom, done.

Source

#PROBLEM #7
$teams = @("A", "B", "C", "D", "E", "F")


#bad random number generator
$rnd = new-object Random


function Create-GameObject ($game)
{
    $o = new-object PSObject
    Add-Member -inputObject $o -memberType NoteProperty -name "Game" -value $game
   
    Add-Member -inputObject $o -memberType NoteProperty -name "RandomOrder" -value $rnd.NextDouble()   
   
    $o
}


function Generate-RoundRobinGames
{
    for ($firstTeamIndex = 0; $firstTeamIndex -lt $teams.Count; $firstTeamIndex++)
    {
        for ($secondTeamIndex = $firstTeamIndex + 1; $secondTeamIndex -lt $teams.Count; $secondTeamIndex++)
        {
            #POSTSCRIPT-OOPS - nitpick mostly. Reading MOW's solution has enlightened me
            #that, instead of creating objects and bolting properties onto them one at a time
            #as I do in the function referenced below, I could achieve the same result with
            #some well-constructed
            #    Select-Object @{Name=""; Expression={"stuff goes here"} }
            #statements.
            Create-GameObject -game "$($teams[$firstTeamIndex]) vs. $($teams[$secondTeamIndex])"
        }
    }
}


function Solve-Problem7
{
    $unsortedGames = Generate-RoundRobinGames


    Write-Host ($unsortedGames | sort RandomOrder | % { $_.Game } | out-string)
}


Solve-Problem7

2008 Winter Scripting Game Events: Index

Categories: PowerShell
Technorati:
Wednesday, April 23, 2008 3:46:13 AM UTC  #     |  Comments [0]  |  Trackback
Wednesday, April 23, 2008 3:45:50 AM UTC #

If you aren't already in the know, these ten problems are from the 2008 Winter Scripting games.

For each problem, I'm going to quickly sum up the interesting (interesting TO ME) bits of each problem, then I'm going to post the full source.

Event 6: Prime Time

Event: calculate primes up to 200. Solution: boring brute-force method.

Source

#PROBLEM #6


#As simple as possible, calculation of primes. This
#routine is inefficient.
function Is-Prime ($n)
{
    if ($n -le 1)
    {
        return $FALSE
    }
   
    $largestPossibleCoefficient = [int]([Math]::Floor($n/2))
    for ($i = 2; $i -le $largestPossibleCoefficient; $i++)
    {
        if ( ($n/$i) -eq ([Math]::Floor($n/$i)) )
        {
            return $FALSE
        }
    }
   
    return $TRUE;
}


function Solve-Problem6
{
    $range = 2..200
    foreach ($candidatePrime in $range)
    {
        if (Is-Prime $candidatePrime)
        {
            Write-Host $candidatePrime
        }
    }
}


Solve-Problem6

2008 Winter Scripting Game Events: Index

Categories: PowerShell
Technorati:
Wednesday, April 23, 2008 3:45:50 AM UTC  #     |  Comments [0]  |  Trackback
Wednesday, April 23, 2008 3:45:30 AM UTC #

If you aren't already in the know, these ten problems are from the 2008 Winter Scripting games.

For each problem, I'm going to quickly sum up the interesting (interesting TO ME) bits of each problem, then I'm going to post the full source.

Event 5: You Call That a Password?

 

First off, before I describe this event, I would like to protest most all of these rules as bad IT practice. Unnecessary password rules just make your users angrier. ANYWAY.

In this challenge we are told to score passwords based on a set of specific rules. What's awesome about my solution is I use the hotness that is metaprogramming. Okay, so, it's programming hotness. I don't claim that programming hotness in any way translates to actual, real-life hotness, I could be wrong.

Source

#PROBLEM #5


#POSTSCRIPT— problem #5 should be run as its own file
#If you attempt to "run" this batch of scripts, this
#part will have errors.
param($password)


$filename = "C:\scripts\wordlist.txt"
$words = cat $filename


function Create-PasswordObject ($passwordString)
{
    $o = new-object PSObject
    Add-Member -inputObject $o -memberType NoteProperty -name "Password" -value $passwordString
   
    Add-Member -inputObject $o -memberType ScriptMethod -name "IsNotAnActualWord" -value { -not (Is-InWordsList $this.Password) }       
    Add-Member -inputObject $o -memberType ScriptMethod -name "MinusFirstLetterIsNotAnActualWord" -value { -not (Is-InWordsList ( $this.Password.Substring(1))) }
    Add-Member -inputObject $o -memberType ScriptMethod -name "MinusLastLetterIsNotAnActualWord" -value { -not (Is-InWordsList ( $this.Password.Substring(0,$this.Password.Length-1))) }
    Add-Member -inputObject $o -memberType ScriptMethod -name "SubstitutedZeroIsNotAnActualWord" -value { -not (Is-InWordsList ( $this.Password.Replace("0","o"))) }
    Add-Member -inputObject $o -memberType ScriptMethod -name "SubstitutedOneIsNotAnActualWord" -value { -not (Is-InWordsList ( $this.Password.Replace("1","l"))) }
    Add-Member -inputObject $o -memberType ScriptMethod -name "IsPasswordLengthOptimal" -value { (10 -le $this.Password.Length) -and ($this.Password.Length -le 20) }
    Add-Member -inputObject $o -memberType ScriptMethod -name "ContainsDigit" -value { $this.Password -match "[0-9]" }
    Add-Member -inputObject $o -memberType ScriptMethod -name "ContainsUppercaseLetter" -value { $this.Password -cmatch "[A-Z]" }
    Add-Member -inputObject $o -memberType ScriptMethod -name "ContainsLowerCaseLetter" -value { $this.Password -cmatch "[a-z]" }
    Add-Member -inputObject $o -memberType ScriptMethod -name "ContainsSymbol" -value { $this.Password -cmatch "[^a-zA-Z0-9]" }
    Add-Member -inputObject $o -memberType ScriptMethod -name "NotContainsFourUppercaseInSuccession" -value { $this.Password -cnotmatch "[A-Z]{4}" }
    Add-Member -inputObject $o -memberType ScriptMethod -name "NotContainsFourLowercaseInSuccession" -value { $this.Password -cnotmatch "[a-z]{4}" }
    Add-Member -inputObject $o -memberType ScriptMethod -name "NotContainsDuplicates" -value { -not ( Contains-DuplicateLetters $this.Password ) }


    $o
}


function Is-InWordsList ($w)
{
    $words -contains $w
}


#POSTSCRIPT-OOPS: MOW did this way better by doing:
#    $passString.ToCharArray() | group | ? { $_.Count -ge 2 }
#So much more elegant! Done in "THE POWERSHELL WAY!"
#Meanwhile, until I saw his code, I was proud of using
#a hash table approach (below).
function Contains-DuplicateLetters ($passString)
{
    $charCounts = @{}
    foreach ($char in $passString.ToCharArray())
    {
        $charCounts[$char] += 1;
    }
   
    $charCounts.Values | ? { $_ -ge 2 }
}


function Calculate-PasswordScore ($passwordObject)
{
    #POSTSCRIPT: Metaprogramming alert! Yeah, check it out, I'm looping through
    #all the scriptmethods on the object and invoking them.
    $passwordCheckMethods = $passwordObject.PSObject.Methods | ? { $_ -is [System.Management.Automation.PSScriptMethod] }
    $results = $passwordCheckMethods | % { $_.Invoke() }
   
    ( $results | ? { $_ -eq $TRUE } ).Count
}


function Calculate-PasswordStrength ($passwordObject)
{
    $score = Calculate-PasswordScore $passwordObject
    if ($score -lt 6)
    {
        "weak"
    }
    elseif ($score -lt 11)    
    {
        "moderately-strong"
    }
    else
    {
        "strong"
    }
}


$weaknessMessages = @{
    "IsNotAnActualWord" = "Password is an actual word.";
    "MinusFirstLetterIsNotAnActualWord" = "Password, minus the first letter, is an actual word.";
    "MinusLastLetterIsNotAnActualWord" = "Password, minus the last letter, is an actual word.";
    "SubstitutedZeroIsNotAnActualWord" = "Password, substituting the letter O for 0 (zero), is an actual word.";
    "SubstitutedOneIsNotAnActualWord" = "Password, substituting the letter L for 1 (one), is an actual word.";
    "IsPasswordLengthOptimal" = "Password should be between 10 and 20 characters long.";
    "ContainsDigit" = "Password does not contain a digit.";
    "ContainsUppercaseLetter" = "Password does not contain an uppercase letter.";
    "ContainsLowerCaseLetter" = "Password does not contain a lowercase letter.";
    "ContainsSymbol" = "Password does not contain a symbol (i.e. a non-alphanumeric character).";
    "NotContainsFourUppercaseInSuccession" = "Password should not contain four uppercase letters in succession.";
    "NotContainsFourLowercaseInSuccession" = "Password should not contain four lowercase letters in succession.";
    "NotContainsDuplicates" = "Password should not contain duplicate characters.";
}


function Show-PasswordWeaknesses ($passwordObject)
{
    $passwordCheckMethods = $passwordObject.PSObject.Methods | ? { $_ -is [System.Management.Automation.PSScriptMethod] }
   
    foreach ($checkMethod in $passwordCheckMethods)
    {
        if ($checkMethod.Invoke() -eq $FALSE)
        {
            $weaknessMessages[$checkMethod.Name]
        }
    }
}


#POSTSCRIPT—I used this function to test out my password checks on
#a bunch of different passwords. Well, fine. It's dead code though; don't go looking
#for references to it.
function Test-Problem5 ($testPw)
{
    $passwordObject = create-passwordobject $testPw
    $passwordCheckMethods = $passwordObject.PSObject.Methods | ? { $_ -is [System.Management.Automation.PSScriptMethod] }


    foreach ($checkMethod in $passwordCheckMethods)
    {
        "'$testPw' - $($checkMethod.Name) - $($checkMethod.Invoke())"
    }
}


function Solve-Problem5
{
    $passwordObject = Create-PasswordObject $password
    $output = @()
   
    $output += Show-PasswordWeaknesses $passwordObject
    $output += ""
    $output += "A password score of $(Calculate-PasswordScore $passwordObject) indicates a $(Calculate-PasswordStrength $passwordObject) password."
   
    Write-Host ($output | out-string)
}


#POSTSCRIPT - again, this will cause problems if you attempt to
#run this problem #5 as part of the larger "allsolutions" script.
# number 5 must be its own file.
Solve-Problem5

2008 Winter Scripting Game Events: Index

Categories: PowerShell
Technorati:
Wednesday, April 23, 2008 3:45:30 AM UTC  #     |  Comments [0]  |  Trackback
Wednesday, April 23, 2008 3:44:58 AM UTC #

If you aren't already in the know, these ten problems are from the 2008 Winter Scripting games.

For each problem, I'm going to quickly sum up the interesting (interesting TO ME) bits of each problem, then I'm going to post the full source.

Event 4: Image is Everything

 

In this challenge, we are asked to pretty-print a calendar from text.

My solution is ugly. I don't make excuses, it's ugly. I don't know if I could have cleaned it up any without adding a lot of complexity/structure, so, this is honestly as pretty as I could make it in PowerShell.

Source

#PROBLEM #4
function Parse-Input ($monthYear)
{
    $split = $monthYear.Split("/");
    $month = [int][string]$split[0]
    $year = [int][string]$split[1]
   
    [datetime]"$year-$month-1"
}


$weekdayLabels = @("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat")
$weekdayToIndexMap = @{}
foreach ($index in 0..6) { $weekdayToIndexMap[$weekdayLabels[$index]] = $index }


function PrettyPrint-Calendar ($date)
{
    $output = @()
    $output += ""
    $output += $date.ToString("MMMM yyyy")
    $output += ""
    $output += [string]::Join("`t", $weekdayLabels)
    $output += PrettyPrint-DaysGrid -month $date
    Write-Host ($output | Out-String)
}


function Pad-Cell ($cell)
{
    $cell = $cell.ToString()
    if ($cell.length -eq 1)
    {
        " $cell"
    } elseif ($cell.length -eq 2) {
        " $cell"
    } else {
        "$cell"
    }
}


#POSTSCRIPT - I think everyone's solution for this was ugly, or maybe
#I'm just projecting. Projecting my opinion that "this code sucks and is ugly"
#out to everyone else's solution. Clearly it's not "elegant".
function PrettyPrint-DaysGrid ($month)
{
    #gets first day through last day of this month
    $days = 1..($month.AddMonths(1).AddDays(-1).Day)
   
    $daysOutput = @()
   
    $firstWeekdayIndex = $weekdayToIndexMap[$month.DayOfWeek.ToString().Substring(0,3)]
    $weekdayOffset = ($firstWeekdayIndex + 7 - 1) % 7
   
    foreach ($emptyDay in 0..($firstWeekdayIndex - 1))
    {
        $daysOutput += "`t"
    }
    foreach ($day in $days)
    {
        $weekday = ($day + $weekdayOffset) % 7
       
        if ($weekday -le 5)
        {
            $daysOutput += "$(Pad-Cell $day)`t"
        }
        else
        {
            $daysOutput += "$(Pad-Cell $day)`n"
        }
    }
   
    [string]::Join("", $daysOutput)
}


function Solve-Problem4
{
    #POSTSCRIPT-OOPS - Apparently I forgot that Read-Host takes in a "prompt" argument,
    #so I "innovated" and found an unnecessary workaround using Write-Host below! Go me!
    Write-Host -noNewLine "Please enter a date in M/YYYY format: "
   
    PrettyPrint-Calendar -date ( Parse-Input -monthYear (Read-Host) )
}




Solve-Problem4

2008 Winter Scripting Game Events: Index

Categories: PowerShell
Technorati:
Wednesday, April 23, 2008 3:44:58 AM UTC  #     |  Comments [0]  |  Trackback
Wednesday, April 23, 2008 3:44:34 AM UTC #

If you aren't already in the know, these ten problems are from the 2008 Winter Scripting games.

For each problem, I'm going to quickly sum up the interesting (interesting TO ME) bits of each problem, then I'm going to post the full source.

Event 3: Instant (Runoff) Winner

 

This is not as straightforward as it looks. We are presented with a list of votes, each person voting for their preferred candidates, in order of preference. For each vote, you need to first count them as if there are 4 candidates, then count as if there are 3, then 2, until a candidate gets the majority vote. I'm actually proud of my solution. Some of the awesomeness is embodied in the following representative line:

$runoffCandidates = $results | Sort Percent -desc | select -first ($results.Count - 1) | % { $_.Name }

This is the magic that removes the candidate who just got booted off the island by reconstructing the list of the surviving candidates.

Source

#PROBLEM #3

#global, will change as the runoff eliminates candidates.
$runoffCandidates = @("Ken Myer","Jonathan Haas","Pilar Ackerman","Syed Abbas")

#POSTSCRIPT - this is one of the few times I think I'm justified in making a scriptmethod,
#because I think this is a nice solution to the problem.
function Create-VoteObject ($rawData)
{
    $o = new-object PSObject
    Add-Member -inputObject $o -memberType NoteProperty -name "Votes" -value $rawData.Split(",")
    Add-Member -inputObject $o -memberType ScriptMethod -name "GetValidVote" -value { Get-FirstValidCandidate $this.Votes }


    $o
}


function Get-FirstValidCandidate ($votes)
{
    foreach ($candidate in $votes)
    {
        if ($runoffCandidates -contains $candidate)
        {
            return $candidate
        }
    }
   
    #impossible to get here, throw error
    Throw "ERROR: Get-FirstValidCandidate should have already returned a value before reaching this point."
}

function Read-Votes ($filename)
{
    $rawVotes = cat $filename

    $rawVotes | % { Create-VoteObject -rawData $_ }
}

function Solve-Problem3
{
    $votes = Read-Votes "C:\Scripts\votes.txt"
   
    while ($runoffCandidates.Count -ge 2) {
        #POSTSCRIPT: to be honest, I think I originally wrote the following line as a function, then "shrunk
        #it down" to the following line. I'll break it up into each element in the pipeline:
        #    $results = $votes |
        #        Select @{ Name="Candidate"; Expression={ $_.GetValidVote() } } |
        #        group Candidate |
        #        select Name,
        #            @{ Name="Percent"; Expression={($_.Count/$votes.Count) * 100 } },
        #            @{ Name="Victory"; Expression={($_.Count/$votes.Count) -ge .5 } }
        $results = $votes | Select @{ Name="Candidate"; Expression={ $_.GetValidVote() } } | group Candidate | select Name, @{ Name="Percent"; Expression={($_.Count/$votes.Count) * 100 } }, @{ Name="Victory"; Expression={($_.Count/$votes.Count) -ge .5 } }
        $victoriousCandidate = $results | ? { $_.Victory }
        if ($victoriousCandidate)
        {
            write-host "The winner is $($victoriousCandidate.Name) with $($victoriousCandidate.Percent)% of the vote."
            break
        } else
        {
            $runoffCandidates = $results | Sort Percent -desc | select -first ($results.Count - 1) | % { $_.Name }
        }
    }
}


Solve-Problem3

2008 Winter Scripting Game Events: Index

Categories: PowerShell
Technorati:
Wednesday, April 23, 2008 3:44:34 AM UTC  #     |  Comments [0]  |  Trackback
Wednesday, April 23, 2008 3:44:08 AM UTC #

If you aren't already in the know, these ten problems are from the 2008 Winter Scripting games.

For each problem, I'm going to quickly sum up the interesting (interesting TO ME) bits of each problem, then I'm going to post the full source.

Event 2: Skating on Thin Ice

Here we do some very basic number crunching: read in scores, cut off the small and the large score, calculate the average. Then sort the results and print the top three.

Actually, in describing this I realize that this is a mostly boring problem. Check out how I use PowerShell's support of collections to chop off parts of them, sort, etc. Otherwise, there's not much here.

I will also point out that many of my solutions are excessively Object Happy. I.e., whenever possible, I construct an object with properties. Most of these times (with the exception of the blackjack game, #10, and the election runoff, #3 immediately below), objects were unnecessary. So, let this be your warning.

Source

#PROBLEM #2
function Create-SkaterObject ($skaterName, $skaterScore)
{
    $o = new-object PSObject
    Add-Member -inputObject $o -memberType NoteProperty -name "Name" -value $skaterName
    Add-Member -inputObject $o -memberType NoteProperty -name "Score" -value $skaterScore
   
    #POSTSCRIPT - ok, this scriptmethod is clearly gratuitous
    Add-Member -inputObject $o -memberType ScriptMethod -name "NameAndScore" -value { "$($this.Name), $($this.Score)" }


    $o
}


$totalScores = 7
function Calculate-Score ($scores)
{
        $thisSkatersTrimmedScores = $scores | sort | select -first ($totalScores - 1) | select -last ($totalScores - 2)
       
        $total = 0
        foreach ($score in $thisSkatersTrimmedScores)
        {
            $total += $score
        }


        $total / ($totalScores - 2 )
}


function Get-SkaterScores ($filename)
{
    $skaterRawData = cat $filename
   
    $scores = @()
    foreach ($skater in $skaterRawData)
    {
        $column = $skater.Split(",")
       
        $thisSkaterName = $column[0]


        $thisSkatersScores = @()
        for ($i = 1; $i -lt $column.Count; $i++)
        {
            $thisSkatersScores += $column[$i]
        }


        $finalScore = Calculate-Score -scores $thisSkatersScores
       
        $scores += Create-SkaterObject -skaterName $thisSkaterName -skaterScore $finalScore
    }
   
    $scores
}


$medals = @( "Gold", "Silver", "Bronze" )
function Solve-Problem2
{
    $allScores = Get-SkaterScores -filename "C:\Scripts\skaters.txt"
    $topScores = $allScores | sort Score -desc | select -first 3
   
    $i = 0
    foreach ($medal in $medals)
    {
        write-host "$medal medal: $($topScores[$i].NameAndScore())"
        $i++
    }
}


Solve-Problem2


2008 Winter Scripting Game Events: Index

Categories: PowerShell
Technorati:
Wednesday, April 23, 2008 3:44:08 AM UTC  #     |  Comments [0]  |  Trackback
Wednesday, April 23, 2008 3:43:42 AM UTC #

If you aren't already in the know, these ten problems are from the 2008 Winter Scripting games.

For each problem, I'm going to quickly sum up the interesting (interesting TO ME) bits of each problem, then I'm going to post the full source.

Event 1: Could I Get Your Phone Number?

This builds a hash table that translates phone numbers to dictionary words (if they exist). That's all it does.

To solve this problem, I reduced it to its very essence: a hash table. The keys of the hash table are the phone numbers. The values are dictionary word(s) that correspond to a phone number.

Representative line:

$ns[$phoneNumber]

Now wasn't that easy?

Source

#PROBLEM #1
function Get-SevenLetterWords ($filename)
{
    $allWords = cat $filename
    $allWords | ? { $_.Length -eq 7 } | % { $_.ToUpper() }
}


#POSTSCRIPT - ok, this was gratuitous. I didn't want to build a letter->number
#mapping "the hard way", so, um, I did it the, um, let's call it
#"the easy way", which is below.
function Build-LettersToNumbersMapping
{
    $mapping = @{}
    $digit = 2;
    $counter = 0;
    for ($i = 65; $i -lt (65+26); $i++) {
        if ([char]$i -ne "Q" -and [char]$i -ne "Z") { $counter++ }
        if ($counter -gt 3) {
            $digit++
            $counter = 1
        }
       
        $mapping[ [char]$i ] = [string]$digit
    }
   
    $mapping
}


function Build-NumberSpeller
{   
    $words = Get-SevenLetterWords "C:\Scripts\wordlist.txt"
    $lettersToNumbersMapping = Build-LettersToNumbersMapping
   
    #$i = 1; $max = $words.count
   
    $numberSpeller = @{}
    foreach ($word in $words)
    {
        #write-host "$i of $max"; $i++
       
        $numericTranslation = @()
        foreach ($letter in $word.ToCharArray())
        {
            $numericTranslation += $lettersToNumbersMapping[ $letter ]
        }
       
        $numberSpeller[ ([string]::Join("", $numericTranslation)) ] = $word
    }
   
    $numberSpeller
}


function Solve-Problem1
{
    "Building the number speller; please be patient while it loads…"
    $ns = Build-NumberSpeller
    write-host -noNewLine "Please enter the phone number (without hyphens) and press ENTER: "
    $phoneNumber = read-host
   
    #POSTSCRIPT - I read the discussion of how long each person's solution takes to
    #a) build the number lookup table, b) do the lookup.
    #While my solution is REALLY slow building the table, the lookups are, how you say, INSTANT.
    #Thus is the magic of hash tables.
    $ns[$phoneNumber]
}


Solve-Problem1

2008 Winter Scripting Game Events: Index

Categories: PowerShell
Technorati:
Wednesday, April 23, 2008 3:43:42 AM UTC  #     |  Comments [0]  |  Trackback
Tuesday, February 26, 2008 2:00:54 AM UTC #

One feature of which I was reminded recently by my non-blogging friend John is PowerShell's support for exotic variable names. Let's try this out by example:

PowerShell code -

Yes, that was a single-quote mark, and yes, I did just name a variable using three exclamation points. That just happened!

I'm not telling you to go out there and start naming your variables with exclamation points because you're feeling chipper that day, even though, by the way, that's an excellent way to express yourself. I'm not telling you to do that.

What I am trying to say is that this "exotic variable name" feature is useful dealing with PowerShell's support for things like CSV import. Importing data using PowerShell's Import-CSV cmdlet automatically assigns the first "header row" as its property names. Sure, you're saying: who cares. Right. We name our header "CustomersFirstName", it comes out the other end $line.CustomersFirstName. YAWN!

 

While the collective "we" name our table headers in such a way that they won't break anything, just because we've been burned so many times, this defensive way of thinking does not always extend to whoever is supplying you with your CSV files. Or, let's just admit it, they're actually exports from someone's Excel spreadsheet. We'll definitely talk about working with Excel some other time; for now, it's back to the variable naming magic. Whereas before you'd say

foreach ($line in $importedCsvFile) {

    Write-Host $line.CustomersFirstName
}

now you can do::

foreach ($line in $importedCsvFile) {

    Write-Host $line.{Customer's First Name} #magic!
}

It's a subtle difference, but when you don't have control over the other guy's data, this sort of subtle issue can be a dealbreaker. Or, thinking positively, a deal-maker! Or, thinking positively but subversively, positively, definitely a dealbreaker! Or thinking non-positively, dealmaker buzzkill! There we go; I'm glad we all made it to the tail end of this paragraph. It's been a rough ride, but we persevered. Congratulations everyone on pushing through! You're all champions to me.

Metaprogramming

I'll mention in passing that, in addition to the ${exotic variable name} and $someVariable.{some exotic property name!!} syntax, we're also granted direct access to the object's methods, properties and other metadata via the $someVariable.PSObject property, which is bolted to every object. Check it out if you're interested; I've found real uses for it a few times. Moving along.

String syntax

We're also granted an odd syntax that allows strings to be used to look up a property. For example:

$worthlessExample = "This is a worthless example, sorry."

$worthessExample."Length"

As you might imagine, $worthlessExample."Length" gets the Length property of the string. That's all well and good, but let's spice it up, shall we?

$dynamicProperty = "Length"

$worthlessExample."$dynamicProperty"

Boom! There's some inline evaluation magic.

The PowerShell book

I would be remiss if I failed to mention that I learned all this from a single source: Bruce Payette's PowerShell book. This book has literally everything you need to know about working with PowerShell, which is quite a statement, especially when you compare it to my library of SharePoint books. So many SharePoint books, and yet so many holes in my SharePoint knowledge! I'm not complaining about SharePoint books so much as I'm trying to express how rare this situation is: you can master this technology with one book!

Categories: PowerShell
Technorati:
Tuesday, February 26, 2008 2:00:54 AM UTC  #     |  Comments [0]  |  Trackback
Monday, February 18, 2008 2:00:51 AM UTC #

image I recently finished tearing through the Winter Scripting Games 2008 challenges. I don't remember having this much fun programming at any time in memory; for whatever reason, working through these small exercises was a blast. The only thing that came close to this much programming fun was working through the first few exercises from The Python Challenge (also implemented in PowerShell) several weekends ago.

Nothing else, at work, at school, at home, over the many years, even comes close.

Is it a bad thing, where making an (awful, I assure you, awful) console BlackJack game was more fun than any other programming experience I can remember? I'm going to go with: yes.

2008 Winter Scripting Game Events: Index

Categories: PowerShell
Technorati:
Monday, February 18, 2008 2:00:51 AM UTC  #     |  Comments [2]  |  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 [2]  |  Trackback
Thursday, September 13, 2007 5:23:38 AM UTC #

The title above subtly implies I'm some kind of professional SlickRun enthusiast. Which I am. I'm running SlickRun on three physical computers, two functioning virtual machines, and until recently, one virtual machine I lost in an accident (RIP SharePoint VM, E 2007 Eternal). SlickRun is one of the first things I install on any new machine I encounter.

As I find SlickRun's default settings intolerable, one of the first things I do post-install is click-click-click uncheck uncheck delete key delete key tap tap tappity. (All this creative onomatopoeia is the sound of me at work configuring SlickRun). Since I've installed SlickRun fresh 6+ times recently, I've pruned my configuring down to the Best of the Best—both the literal best of the best efficient configuration, and in a metaphorical sense, the Best of the Best karate movie. That was an awesome karate movie.

Oops! I forgot: Introduction to SlickRun

SlickRun, for those of you who are not aware, is a productivity-enhancing unobtrusive program launcher that enables you to:

  • Throw away that cluttered Start Menu!*
  • Throw away that cluttered Bookmarks/Favorites folder in your browser!**
  • Throw away that other productivity tool you use for Google Searches!***
  • Throw away all your shortcuts on the Desktop!****

* metaphorically
** metaphorically
*** metaphorically
**** metaphorically

The SlickRun manual, in ten (10) words or less

  • WINDOWS KEY + Q  opens SlickRun
  • At SlickRun prompt:
    • HELP
    • SETUP
  • ESC closes

Essential Post-Install Configuration Steps

If nothing else, at the least make these two changes:

ESSENTIAL #1: Make SlickRun invisible

I actually didn't like SlickRun when I first installed it. Why? Because SlickRun's out-of-the-box experience is an annoying one—by default, SlickRun adds itself to the system tray and throws an obnoxious blue bar in front of all other windows.

Thankfully we can hide SlickRun, hide the system tray icon, and restore balance to the world. I don't want to overstate the importance of this, but you may just save someone's life—and that life may be your very own! Proper SlickRun configurations save lives! But I don't want to overstate this.

Anyway: In the Setup screen, configure the Options tab:
SlickRun Options - check -Start at Windows Startup check -AutoHide SlickRun uncheck -Show Icon when hidden

TIME ELAPSED: +10 SECONDS.

ESSENTIAL #2: Get rid of the awful preset MagicWords

SlickRun comes bundled with one (1) useful MagicWord: the Google one. Everything else is useless and needs to go.

You can use the mouse and click your way through the deletions, but I've found the magic keystrokes to be:

  • DOWNARROW to highlight the next MagicWord
  • ALT-D to perform a Delete operation
  • Y to confirm deletion.

Repeat rapidfire DOWNARROW, ALT-D, Y until you've deleted everything. Except the "google" MagicWord; that one's golden.

TIME ELAPSED: +20 SECONDS.

Useful Configurations

I don't want to present the following as MUST HAVES; instead, I'll just say that I have found the following to be personally useful, and I believe you will as well.

USEFUL #1: Make the font massive

This picture expresses everything I want to say on the subject:

SlickRun - massive equals better

Citation: props to Ben for this tip. Detailed instructions are posted immediately below:

SlickRun Options->Appearance - click the Font button

TIME ELAPSED: +10 SECONDS.

USEFUL #2: Google search at the promptSlickRun - Google search MagicWord settings

POP QUIZ, HOTSHOT: how few keystrokes do you need to run a search?

With my SlickRun "g" MagicWord, three (3).

(I am aware Vista has improved the search experience such that it is reduced to literally one keystroke. That's totally awesome, and I'm sure we'll all be enjoying this "one-keystroke search" feature when Vista R2 SP1 arrives sometime in 2009).

I will also note here that SlickRun has a form of autocomplete that is similar to typing an address into your browser—if you've typed your phrase before, SlickRun will "guess ahead" at what you intend. What I mean to say is that SlickRun finds more ways to make itself useful!

SlickRun - google search MagicWord in action

TIME ELAPSED: +20 SECONDS.

USEFUL #3: Browser-targeted favorites

Let's assume your default browser is Firefox and not Internet Explorer. Let's just say. How do you open up an IE-only site in the browser?

  • The Firefox plugin IETab works wonders, but IETab does consistently crash with some of our more "creative" legacy intranet sites. IETab, for those of you who don't know, allows you to visit specific sites within an IE browser hosted inside of the Firefox window. So it looks like Firefox, but it renders (and behaves) like IE. It's awesome for 90% of the site that require IE.
  • Set up a SlickRun MagicWord for this browser-targeted favorite in 10 seconds. SET IT AND FORGET IT!

We'll focus on SlickRun today. Let's try doing this by practical example (this is something I have set up today):

EXAMPLE: You need to connect to your Virtual Server administration website. This site runs a heavy-duty ActiveX control and thus should only be opened inside of IE. Your default browser is (by accident I'm sure) set to Firefox. What is a quick, painless way to access the Virtual Server site?

Enter SlickRun:

SlickRun virtual-server MagicWord

SlickRun - virtual-server

TIME ELAPSED: +30 SECONDS per URL

 USEFUL #4: Program launching

It should be obvious to note that we use SlickRun to launch programs.  Duh. What is not obvious is that you probably shouldn't set up all your programs in a "SlickRun big bang go-live"; instead, add new programs to the SlickRun launcher only when you realize, for example, "I use Paint.NET a lot." If you add too many programs at once, you won't remember your SlickRun aliases for them. As Nacho Libre says, "Take it easy!"

SlickRun - paint.net

TIME ELAPSED: +30 SECONDS per program

USEFUL #5: Document shortcuts

I read eBooks, and I'm not ashamed. Go ahead, poke fun.

I'm also unashamed to say that I haven't memorized all 1100+ pages of this SharePoint administration book. Yet. In the interim, I use the very handy eBook provided on CD (aside: did you know Microsoft Press provides the full PDF eBook of this SharePoint administration book at no additional charge? Use it!).

I use this eBook often—and what is most telling about how much I rely on SlickRun is the fact that I have no idea where on my hard drive I copied the PDF—only SlickRun knows. I "set it and forget it" in the purest sense.

SlickRun - book-moss

TIME ELAPSED: +30 SECONDS per document shortcut

Extreme SlickRun

I've been experimenting with running small PowerShell scripts whose sole purpose is to  copy something to the clipboard. To accomplish this, I'm using the fully-functional Set-Clipboard cmdlet (thanks Keith Hill) that is bundled in the PowerShell Community Extensions.

These "clipboard scriptlets," while useful when I type them into the PowerShell  interpreter, are so much more useful because I can launch them directly from SlickRun. One option of running scripts is the traditional way:  create a script file, and set up a command to run the script file (e.g. "powershell.exe C:\a\get-something.ps1"). A new option we're presented with is to use powershell.exe's "-command" parameter—sometimes I embed all of the PowerShell code inside the -command parameter, meaning these little clipboard scriptlets reside entirely inside of SlickRun.

One example of the simple little things I did was create a random GUID inside of PowerShell and copy the new GUID onto the clipboard. Because (for whatever reason) the GUID tool in Visual Studio was not available, this was my best option. And yes, SharePoint is at times GUID-hungry, (SharePoint is the hungry hungry hippo of GUIDs, and yes, you can quote me on that) so I know I've used this a dozen times at least.

powershell.exe -command { "$([Guid]::NewGuid().ToString()| set-clipboard)" }

Anyway, while this [Guid]::NewGuid().ToString()| set-clipboard is easy enough to run in an open PowerShell prompt, it literally took 15 seconds to set this alias up in SlickRun. SET IT AND FORGET IT!

SlickRun - guid

TIME ELAPSED: TOO EXTREME TO ACCURATELY CALCULATE (OUTSIDE OF TOLERANCES)

Download SlickRun

Download SlickRun!

Check out Bayden Systems, maker of SlickRun!

Categories: PowerShell
Technorati:
Thursday, September 13, 2007 5:23:38 AM UTC  #     |  Comments [4]  |  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
Thursday, July 19, 2007 6:50:39 AM UTC #

I recently read Simon's post about using Reflector with SharePoint. Inspired, I set about to use Reflector as a sort of "MSDN 2" to help me work through holes in MSDN's (and to be fair, the rest of the world's) documentation. Unfortunately for me, I had to track down DLLs and ASMX files from different subfolders of the 12 hive and even from the GAC.

I'm writing this post to help me (and you) find the SharePoint 2007 DLLs and EXE's and everything else we might need to reverse eng…um, let me rephrase that, "embrace a lifestyle of self-service SharePoint documentation." That last bit sounds nicer, yes.

For various non-technical reasons, I can't just post the code directly here—instead, we must each go to our MOSS (or WSS) farms and copy out the DLLs. Thus far I've found three directory trees SharePoint is storing its code:

Name Directory SKUs Fun Facts!
The 12 Hive C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\ WSS, MOSS What's sad is that I typed the full directory from memory—including the awkward capitalization.
The "Office Server" directory C:\Program Files\Microsoft Office Servers\ (can be customized) MOSS Post your fun fact in the comments, otherwise I'll have to stick with "Fun fact: Excel Services .dllis hosted here!" See, you can do better.
The GAC %WINDIR%\Assembly\ WSS, MOSS Windows Explorer hides files in the GAC from you (it's a special folder), so you'll have to work a little to get at them.


The added value I'm here to present today is, well, I automated all of the copying—I wrote a PowerShell script to grab all the files. It's way overkill (especially the Vista-style nuttiness when I spend half of the script calculating the estimated time the other half will take to complete), but hey, why not?
Here we are:


Find-SharePointDllsVistaEdition.ps1 (2.06 KB)
#Freely distribute and use. Absolutely no warranty is implied.
$dirTwelveHive = "C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\"
$dirMossServers = "C:\Program Files\Microsoft Office Servers"

$subDirectory = read-host "Full pathname to write files?"
mkdir $subDirectory -ErrorAction SilentlyContinue | out-null
mkdir ([System.IO.Path]::Combine($subDirectory, "12-hive")) -ErrorAction SilentlyContinue  | out-null
mkdir ([System.IO.Path]::Combine($subDirectory, "MOSS")) -ErrorAction SilentlyContinue  | out-null
mkdir ([System.IO.Path]::Combine($subDirectory, "GAC")) -ErrorAction SilentlyContinue  | out-null

echo "Calculating time required to perform this operation…"
$excludedFileExtensions = dir -path "$([System.IO.Path]::Combine($dirTwelveHive, ""template\images""))
" | foreach { $_.Extension.ToUpper() } | group | foreach { $_.Name }
$excludedFileExtensions += ("MDF","LDF")
$exclusionCriteria = $excludedFileExtensions | foreach { "*$_"}

$allFiles = dir -path $dirTwelveHive -recurse -exclude $exclusionCriteria -ErrorAction SilentlyContinue
$allFiles += dir -path $dirMossServers -recurse -exclude $exclusionCriteria -ErrorAction SilentlyContinue
$allFiles += dir -path ([System.IO.Path]::Combine($subDirectory, "GAC")) -recurse -include "*SharePoint*.dll" -ErrorAction SilentlyContinue

$allFiles | Select Extension | group Extension | select Name, Count | sort Count -desc | select -first 10

$sum = 0
$allFiles | foreach {
    $sum += (dir $_).Length
}

echo "`n`n Total size in MB: $($sum/1MB)"

cp -recurse -force -path $dirTwelveHive -destination ([System.IO.Path]::Combine($subDirectory, "12-hive")) -exclude $exclusionCriteria
cp -recurse -force -path $dirMossServers -destination ([System.IO.Path]::Combine($subDirectory, "MOSS")) -exclude $exclusionCriteria

#does not preserve directory structure
$filesFromGac = dir -recurse -path "$([System.IO.Path]::Combine($env:WINDIR,""assembly""))" -include "*SharePoint*.dll"
$filesFromGac | foreach { cp -force -path $_ -destination ([System.IO.Path]::Combine($subDirectory, "GAC")) }


I'd better hit the "Post" button before my laptop battery runs out.
Categories: PowerShell | SharePoint
Technorati:  | 
Thursday, July 19, 2007 6:50:39 AM UTC  #     |  Comments [0]  |  Trackback
Thursday, July 19, 2007 4:42:28 AM UTC #

In my other, somewhat-less-happy post about CAML, I shared my frustration of working with the MSDN (and other) CAML documentation. What I failed to do in that other post was share the results, i.e. tell you how I eventually got the job done.

This will not bring you all the way from start to finish, but should help you hurdle some of the less obvious…let's call them 'hurdles'. I'm sticking with the hurdling metaphor—there's no point in switching metaphors midsentence, even if the word 'hurdle' is starting to lose all meaning.

Step #1: Go visit the U2U Community Tools page and download both of their excellent CAML query tools. If you stop reading now and click on the U2U links, it's probably for the best.

The two tools are:
  • "CAML Builder" - this Windows app that will actually use SharePoint's web services to pull list metadata, meaning that you can actually build your CAML query almost as easily as every other query tool on the planet. The one gotcha is that (in this case) it mingles the three XML chunks together (see code below for an explanation of what I mean by "chunks").
  • "DevFeaturesPackage" - yes, that's what it's called. This is a v3-only Feature that attaches itself all lists' Actions menu in your browser. And, it works like every other query tool on the planet.

My "works like every other query tool on the planet" quips above might sound sarcastic, but trust me: if you've spent any amount of time working with this stuff, finding things familiar and useful is a great relief.

Step #2: Find your lists.asmx web service URL and add it to your project as a web reference. In my case, and for probably every other case in the entire world, it will be http://servername/site/site/site/_vti_bin/lists.asmx - unless you've done something crazy, like install SharePoint on a subdirectory of the web application. Note that the URL does include the site heirarchy. This paragraph is a longwinded way of saying "server name, plus site heirarchy, plus_vti_bin, plus lists.asmx".

You can be absolutely certain by running your lists.asmx URL through the U2U CAML Builder and verifying you're connecting to the correct site.

Step #3: Build your CAML query. I'm not going to attempt to help with this; instead, I'll refer you to the U2U CAML query tools again.

Step #4: Write code to build up the six (6) parameters this web service takes. I've posted the PowerShell function I used to do this, and annotated where necessary (which is everywhere).

Step #5: You're pretty much through! Now all you have to do is bask in the glory of a job well done! Don't even test! You can't test—you're too busy partying and you got it right the first time! Woo!

Well, you might want to test.

Footnote: My PowerShell code, doctored some—if you plan on copy/pasting this, you'll probably want your code…cleaner. Without further ado:

#—
#Tested only against WSS v2. Presumably also works with v3 sites.
function Get-ListItems ($webService)
{
   
    $listname = "YOUR_LIST_NAME_GOES_HERE"

    #Empty string indicates we use the default view. Otherwise, we'll have to provide the view GUID. I think.
    #Presumably using a custom view is an excellent way to set a filter on your data set.
    $viewname = ""

    #I don't know what the rowlimit means, and though I can GUESS, I don't want to ASSUME.
    $rowlimit = "100"

    #I created an XmlDocument object ONLY so I could create valid XmlElements for the 3 web service arguments.
    #$x is not used directly.
    $x = new-object "System.Xml.XmlDocument"

    #<Query> - presumably you can set attributes or inner XML if necessary.
    $q = $x.CreateElement("Query")

    #<QueryOptions> - presumably you can set attributes or inner XML if necessary.
    $qo = $x.CreateElement("QueryOptions")

    #I'm posting my "ViewFields" element as-is. The goal of my query was to pull the ID of every
    #item visible in the default view. I will assume that your ViewFields element will have more content.
    $v = $x.CreateElement("ViewFields")
    #Don't be thrown by the odd PowerShell string syntax. "" inside a quote is like \" in C#.
    # the .set_InnerXml is just one of PowerShell's ways of representing .NET's property set operations.
    $v.set_InnerXml("<FieldRef Name=""ID"" />")

    #I can't remember why this was necessary anymore, but is probably related to my ignorance of the XmlDocument object.
    #If I'm guessing correctly, SharePoint requires XML submitted as <Query></Query> instead of <Query />. But that's guessing.
    #Either way, feel free to ignore these two lines and see if you can get your lists web service working without them.
    $q.set_InnerXml("")
    $qo.set_InnerXml("")
  

    $result = $webService.GetListItems($listname, $viewname, $q, $v, $rowlimit, $qo)

    #this is awkward PowerShell syntax that could probably be replaced with some proper XPath.
    $trimmedResult = $result.data.row | foreach { $_.ows_ID }
  
    #this line means "return trimmedResult"
    $trimmedResult
}
#—
Categories: PowerShell | SharePoint
Technorati:  | 
Thursday, July 19, 2007 4:42:28 AM UTC  #     |  Comments [4]  |  Trackback
Wednesday, March 28, 2007 5:31:53 AM UTC #

This little gem may come in handy when you think you may need to do some performance tuning on your PowerShell script. I'll just let my brilliant script do all the talking:


function Help-PerformanceTuning
{
      #this function helps me when I feel the urge to
      #optimize for performance
      $max = 4000

      $
regexObjects = @()
     
for($i=0; $i -lt ($max-1); $i++)
      {
            $
regexObjects += [regex](Get-TwelveRandomCharacters)
      }
     
      echo
"Congratulations! You've created $max Regex objects! That's totally, ridiculously wasteful! Now get back to work!"     
}

It sure cures what's ailing me!

Astute readers may have noted that I call a "Get-TwelveRandomCharacters" function in there. Well, we should all be pleased to note that I further waste resources by instantiating twelve Random objects for each Regex object created! Let's do this:


function Get-TwelveRandomCharacters
{
      $chars = @()
     
for ($i=0; $i -lt 12; $i++)
      {
            $r = new-object Random
           
$
chars += [string][char][byte]($r.Next(1,26)+64)
      }

      [string]::Join("", $
chars)
}

Awesome!

Categories: PowerShell
Technorati:
Wednesday, March 28, 2007 5:31:53 AM UTC  #     |  Comments [0]  |  Trackback
Wednesday, February 28, 2007 3:38:31 AM UTC #

Nuttiness enumerated below:

  • UPDATED: Misinformation removed—the PowerShell Book has set me straight. Inside a PowerShell function, all method calls returning a value must be prefixed with [void]. This means mostly .NET method calls and COM object methods. If you don't use the [void] prefix, your $xls.ActiveWorkbook.Save() operation returns a COM object to the calling function, not exactly "expected behavior." The behavior isn't a problem now that I know.
  • The file redirection operators (in my case, the >> append operator) output in Unicode by default. This is fine, except that I was appending to an ASCII file. Whoops! The (working) code ended up as:
    #not exactly as quick and easy as "string" >> $outfilename, but at least it exists
    "string" | out-file -filepath $outfilename -append -encoding "ASCII"


    Footnote: the PowerShell Book also covered this specific scenario.
  • mp3info is what I'm using to catalog my podcasts (I'm really just looking for the total run time, and maybe encoding bitrate), and it looks like it's going to work most excellently. What's awesome about PowerShell is that it can easily import CSV or XML (both formats may be outputted by mp3info), but I could have also used a .NET library that reads MP3 files. In this case, (i.e., today,) it was slightly easier to run the console application and parse its text output in PowerShell. Tomorrow, I may use an available .NET (or COM) library. The brilliance of PowerShell is, either way is quite easy.

    Footnote: the PowerShell Book also covered text file parsing and handling XML documents. Yeah, definitely recommended.

Update: looks like I've got ~372 hours of podcasts in the queue, weighing in at 12.5GB at an (unweighted) average bitrate of 84kbps in 679 files. Yeah. A new Great Podcast Roundup to follow soon.

Categories: PowerShell
Technorati:
Wednesday, February 28, 2007 3:38:31 AM UTC  #     |  Comments [0]  |  Trackback
Thursday, November 23, 2006 3:10:57 AM UTC #

Introduction

I'll try to summarize as succintly as possible:

PowerShell is the first true Windows scripting language. It's not VBScript. I'm sorry that in the past, I've compared PowerShell directly with VBScript as if they were equals; they're totally not. PowerShell can use COM objects like VBScript, but that's about it comparison-wise.

PowerShell is a real language, closer to Perl and Python (and Ruby) than it is to any flavor of VB (excluding VBx—the future, possibly mythical and possibly vaporous, scriptable version).

PowerShell uses the .NET framework with gusto!

PowerShell is also an excellent place to let a quick two-line script rip, or to explore the SharePoint object model in a sort of immediate window (or what the Lisp, Ruby, Smalltalk and Python folk like to call the "interpreter"). It's inline, is the point.

PowerShell pipes objects, not text (though it does pretty well with text too). This "object piping" nonsense is best explained by example (i.e. not here).

UPDATE 2007-08-06: despite my beefed-up introduction above, it's probably wise to go read someone else's introduction.

Step 1: Disable Security

"Secure by default" security measures have proven to be effective, and this next section is in no way disrespectful towards the important strides the industry has made yada yada yada now how do you turn off security so I can run some scripts already? It's easy! Load up the PowerShell prompt and type in:

Set-ExecutionPolicy Unrestricted

This will give you (and potentially, hackers like Boris) the ability to run scripts just like all the viruses used to do with Word macros and HTML application files. To further open security holes, associate .PS1 files with the powershell executable, found in "%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe".

Boris. When you cripple PowerShell's remote execution policy, he's once again invincible!

Step 2: Install an IDE

UPDATED 2007-08-06: PowerShell Analyzer is the only PowerShell IDE of which I know. I am not an heavy scripter, so I can't personally justify a money-costing IDE, but you may be interested. As for myself, I'm using a text editor with PowerShell syntax highlighting.  I'm not strongly recommending either one; pick something—anything but notepad!—and go.

Step 3: Wait NaN Months for Documentation

The best online source for information about PowerShell is a tie between a someone calling himself /\/\o\/\/ and the rest of the internet. That's both a tribute to how excellent MOW's information is, and simultaneously an omen warning you how completely lacking everything else is. Also, there aren't any books—

UPDATE 2007-08-06: //\/\O\\/\/ is still the best source of online information, but at least now there's a PowerShell book, including an excellent "just a PDF" eBook I highly recommend buying (available only direct from the publisher). Though the eBook concept may not sound as awesome as you'd think, note that you can navigate with both search and internal hyperlinks—this is why we invented computers in the first place, right? So we can type "CTRL+F, type "[switch] parameter", hit ENTER" and find out what a switch parameter does? Answer: yes, and to answer the question a second time for emphasis: yes, emphatically.

Step 4: Get the PSCX (Community Extensions)

The PowerShell Community Extensions are an excellent collection of useful cmdlets (i.e., "PowerShell libraries" (i.e., "programming stuff" (i.e., "oh forget it—let's just say 'stuff'"))). One of my favorites is the "Set-Clipboard" cmdlet (it outputs directly to the Windows Clipboard)—not that you particularly care about my favorite; discover your own. You'll find at least one indispensable cmdlet among the bunch.

BONUS DOWNLOAD: Try the  world-famous tab expansion engine (read: PowerShell intellisense, at the prompt), developed by none other than /\/\o\/\/, "the better portion of PowerShell content on the internet".

Footnote: Active Directory (ADSI) support is weird

UPDATE 2007-08-06: I've found an excellent summary of PowerShell's ADSI (or Active Directory) support. This page was not the first result on Google when I searched for "PowerShell ADSI", which is nothing less than a crime—a search engine results felony.

The really, really quick summary is that all of the useful methods for working with Active Directory objects are hidden from you. What should be obvious, isn't. What should be discoverable, isn't.

Let's do this by hypothetical example. Not that I've tried this myself! I'm smarter than that. Let's just say that I wanted to find all members of an Active Directory security group (let's just say). Assuming you already have the group object, how would you walk the object hierarchy to pull all members of the group? I'll make it easy and give you three options:

#option 1
$group.member
#option 2
$group.psbase.get("member")
#option 3
$group.psbase.invokeget("member")

If you guessed "oh no it can't be #3," guess what! And if you're not angry enough, let me tell you that the super bonus of this story is that all three options are legal syntax.

Guess how I spent my time today?

UPDATE 2007-08-06: Again, read that excellent PowerShell ADSI article I referenced above. It explains all this and more.

Categories: PowerShell
Technorati:
Thursday, November 23, 2006 3:10:57 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 2013, Peter Seale

Send mail to the author(s) E-mail



Sign In