PowerShell Cookbook

Search

Categories

 

On this page

Command-line shortcut for repetitive operations
"May I please test Longhorn Beta 1?"
Regular Expressions in Monad
Param statement, and new-object
Monad Hosting Part II -- Stop acting like cmd.exe!
A History Browsing Prompt
** WARNING ** MSH may make you _too_ efficient
Working with the new Monad beta -- getting settled in
Jeffrey Snover's Monad TechEd Presentation ... now by Webcast
Monad, RSS, XML, and other cool tricks

Archive

Blogroll

Disclaimer
I work for Microsoft.

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

RSS 2.0 | Atom 1.0 | CDF

Send mail to the author(s) E-mail

Total Posts: 218
This Year: 18
This Month: 0
This Week: 0
Comments: 529

Sign In

 Thursday, July 28, 2005
Thursday, July 28, 2005 10:25:25 PM (Pacific Daylight Time, UTC-07:00) ( )

There are times when you might want to do the same thing many times at the command line.  You normally would use a counted for loop:

MSH:19 C:\temp\monad > for($x = 0; $x -lt 5; $x++) { do-something }

But here's a neat little trick to save some typing, if you don't care which iteration of the loop you're in:

MSH:19 C:\temp\monad > 1..5 | foreach { do-something }

This is a bloated and slow way to do a for loop, though, so don't use it in scripts.

The 1..5 expression creates an array of 5 elements, using the numbers 1 through 5.  Then, we pipe it to foreach-object -- which then performs "do-something" for each element in the array.

For more neat things you can do with arrays, type 
   get-help about_Array
at your prompt.

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

Comments [3] | | # 
 Thursday, July 21, 2005
Friday, July 22, 2005 12:50:55 AM (Pacific Daylight Time, UTC-07:00) ( )

You've got to love the enthusiasm surrounding Longhorn.  In the words of one of its beta coordinators:

"This is the first time in 10+ years involvement with customer beta programs that I’ve received an unsolicited bribe to get on the program– two pounds of inscribed chocolate showed up at my office via Fedex today."

The chocolate was shared far and wide -- I'll bet it satisfied at least 1 or 2 mid-afternoon sugar cravings :)

(To be clear, this was not from the manufacturer of said sugar-buzz-inducing-decadence.)

[ Edit: Adam also blogged about this here ]

Comments [0] | | # 
 Tuesday, July 19, 2005
Tuesday, July 19, 2005 7:36:25 AM (Pacific Daylight Time, UTC-07:00) ( )

SteveX has a Monad forum on his site, and one of the first topics posted (by Ayende) was an example script on using regular expressions in Monad.

This is helpful example of how well Monad integrates with the .Net framework.  You can call out to [System.Text.RegularExpressions.Regex], and port your C# regular expression almost effortlessly.

But wait, it only gets better.

Regular Expressions are a glorious beast of burden in scripting languages, as you might have noticed if you've read much Perl.  Because of that, we've given them first-class language support via the "-match" expression.

Let's start with a simple example:

MSH:40 C:\Temp >"Hello World" -match "hello"
True

The match evaluates to $true if the match was successful, $false otherwise.  Notice that regular expressions (via the -match expression) are case insensitive by default.  If you want case sensitive operation, the -cmatch expression is the one you want to look toward:

MSH:41 C:\Temp >"Hello World" -cmatch "hello"
False

Once the match expression evaluates your regular expression, it places the resulting matches (called groups) in an appropriately named variable, called $matches:

MSH:43 C:\Temp >$matches

Key                            Value
---                            -----
0                              Hello

MSH:46 C:\Temp >"Hello World" -match "hello.*or"
True
MSH:47 C:\Temp >$matches

Key                            Value
---                            -----
0                              Hello Wor

As with the .Net method, $matches[0] always holds the entire match of your regular expression.  Regular expression matches can get much more complex -- such as when they contain multiple capture groups, or even named ones:

MSH:50 C:\Temp >"Hello World" -match "h(ell)o(?<named1>.*)(?<named2>or)"
True
MSH:51 C:\Temp >$matches

Key                            Value
---                            -----
named2                         or
named1                          W
1                              ell
0                              Hello Wor

This is how Monad (via .Net) chose those groups:

  • Match '0', as always, is the largest substring match of the regular expression.
  • Match '1' is the un-named capture at the beginning of the regular expression.
  • Match 'named1' is the first named capture in the middle of the regular expression.
  • Match 'named2' is the second named capture in the middle of the regular expression.

Monad uses .Net's regular expression facilities, so keep this great MSDN Regex Reference handy.  From there, memorize obscure minutea such as the following to dazzle family and soon-to-be-ex-friends:

Named captures are numbered sequentially, based on the left-to-right order of the opening parenthesis (like unnamed captures), but numbering of named captures starts after all unnamed captures have been counted.

This becomes a pretty powerful mechanism for slicing and dicing text content.  How about a command-line address book?

MSH:79 C:\Temp >get-content address_book.csv
Joe,555-1212
Frank,555-1234
Ella,555-314159265359

MSH:80 C:\Temp >$outputBlock = { "Name: " + $matches["name"] + ", Number: " + $matches["number"] }
MSH:81 C:\Temp >get-content address_book.csv | `
>> foreach-object { if ($_ -match "(?<name>Ella),(?<number>.*)") `
>> { & $outputBlock } }
>>
Name: Ella, Number: 555-314159265359

So, to close the loop -- we can write the original script:

if($args.Count -lt 2)
{
   "Arguments are: "
   break
}

$file = [string]$args[0]
$pattern = [string]$args[1]
$re = new-object System.Text.RegularExpressions.Regex($pattern)
foreach ($line in $(Get-Content $file))
{
   $match = $re.Match($line);
   if($match.Success)
   {
      $match.Value
   }
}

like this, using Monad's built-in language support.

param(
   [string] $filename = $(throw "Please specify a filename."),
   [string] $regex = $(throw "Please specify a regular expression.")
     )

foreach ($line in (get-content $filename))
{
   if($line -match $regex)
   {
      $matches[0]
   }
}

Pretty nice!

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

Comments [0] | | # 
 Monday, July 11, 2005
Tuesday, July 12, 2005 5:12:11 AM (Pacific Daylight Time, UTC-07:00) ( )

Vivek recently posted a helpful script to get the contents of a web page.

 

As is common with .Net and Monad, there is more than one way to do it.  In addition, there are also two Monad facilities we can take advantage of to make the script more readable: new-object, and the param statement.

 

First of all, the easy one.  The new-object cmdlet creates a new object.  In the current drop, it only creates .Net objects, but COM support is coming soon.  This cmdlet replaces the calls to [Activator]::CreateInstance in Vivek's script.

 

Next, the param statement.  I used it in my new-calendarItem script, and it looks like this:

 

## test-params.msh

## Test the param facility

 

param(

   [string] $importantParameter = $(throw "Please specify the important parameter!"),

   [int] $countOfThings,

   [DateTime] $recordDate = [DateTime]::MaxValue

   )

  

"ImportantParameter was $importantParameter"

"CountOfThings was $countOfThings"

"RecordDate was $recordDate"

 

It looks similar to a function definition in many languages, but offers more benefits.  The param statement allows you to define the parameters to your script.  In the simplest case, your user supplies values for all of your parameters.  When they do this, Monad interprets them positionally for you.  That is, each parameter gets assigned from the value passed at that position on the command line.  For example:

 

MSH:130 C:\Temp >test-params First 123 "Tuesday, July 12, 2005"

ImportantParameter was First

CountOfThings was 123

RecordDate was 7/12/2005 12:00:00 AM

 

Notice how Monad coerces the data types, if it can -- as with the DateTime value in the third position.

 

In your script, you simply refer to your parameters by the names you gave them: $importantParameter, $countOfThings, and $recordDate.

 

For some parameters, you might want to supply your user with sensible defaults.  In that case, Monad allows you to specify a default value for your parameter, as with $recordDate above:

 

MSH:135 C:\Temp >test-params First 123

ImportantParameter was First

CountOfThings was 123

RecordDate was 12/31/9999 11:59:59 PM

 

However, your users don't always know how to interact with your script.  You might have parameters that should always be populated, such as $importantParameter in the example above.  In that case, we specify a special default value for that parameter -- an exception that tells them how to resolve the problem:

 

MSH:136 C:\Temp >test-params

 : Please specify the important parameter!

At d:\lee\tools\test-params.msh:5 char:42

+    [string] $importantParameter = $(throw  <<<< "Please specify the important parameter!"),

 

The param statement also gives you support of named parameters for free.  Named parameters let your user specify the parameters by name, rather than position.  Your parameter's variable name becomes the parameter name:

 

MSH:141 C:\Temp >test-params -RecordDate:$([DateTime]::MinValue) `

>>    -ImportantParameter:"Important!" `

>>    -CountOfThings:123

>> 

ImportantParameter was Important!

CountOfThings was 123

RecordDate was 1/1/0001 12:00:00 AM

 

As with regular cmdlets, you need only to specify enough of the parameter to differentiate it from the others:

 

MSH:152 C:\Temp >test-params -r:$([DateTime]::MinValue) -i:"Important!" -c:10

ImportantParameter was Important!

CountOfThings was 10

RecordDate was 1/1/0001 12:00:00 AM

 

This also helps Monad disambiguate between multiple optional parameters:

 

MSH:153 C:\Temp >test-params "Important!" -r:$([DateTime]::MinValue)

ImportantParameter was Important!

CountOfThings was 0

RecordDate was 1/1/0001 12:00:00 AM

 

MSH:154 C:\Temp >test-params "Important!" -c:123

ImportantParameter was Important!

CountOfThings was 123

RecordDate was 12/31/9999 11:59:59 PM

 

So, given this new-found knowledge, this is how we might rewrite Vivek's example.  First, the System.Net.WebClient way, as newly implemented in V2 of the framework:

 

## get-uri.msh

## Get a web page

 

param(

   [string] $uri = $(throw "Please specify an URI to retieve"),

   [string] $userAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"

   )

 

$wc = new-object System.Net.WebClient

$wc.Headers.Add("user-agent", $userAgent)

$wc.DownloadString($uri)

 

and the System.Net.HttpWebRequest way:

 

## get-uri.msh

## Get a web page

 

param(

   [string] $uri = $(throw "Please specify an URI to retieve"),

   [string] $userAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"

   )

 

$request = [System.Net.WebRequest]::Create($uri)

$request.Headers.Add("user-agent", $userAgent)

$response = $request.GetResponse()

$requestStream = $response.GetResponseStream()

$readStream = new-object System.IO.StreamReader $requestStream

$readStream.ReadToEnd()

$readStream.Close()

$response.Close()

 

If we need the content split into an array (as with Vivek's script,) we can write:

 

MSH:158 C:\Temp >$lines = (get-uri http://www.microsoft.com).Split("`n")

MSH:159 C:\Temp >$lines[0]

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >

 

For the purpose we need it, I much prefer the System.Net.WebClient way.

 

[Edit: Thanks, Toby, for pointing out the WebClient / HttpWebRequest switch]

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

Comments [5] | | # 
 Saturday, July 09, 2005
Saturday, July 09, 2005 7:35:25 AM (Pacific Daylight Time, UTC-07:00) ( )

Travis recently posted a good comment:

... I would *love* to be able to configure Monad's tab completion behavior! ...

So would we.

Tab completion is just one of the many things that would be great to change about the default Monad console host.  Other things that drive us crazy are the lack of standard CUA bindings (ie: Copy, Paste, keyboard-based selection,) and full Unicode support, to name a few.  It's comes down to a matter of priority.

As I mentioned in an earlier post, Monad can be hosted by any managed application that wishes to do so.  Hosting applications need only to implement a fairly simple interface in order to host the engine.  Our default host, msh.exe, owes nearly all of its functionality to the fact that we use the same infrastructure that cmd.exe does.  With relatively little code, we get passable tab completion, history cycling, and a bevy of function keys that are pretty useful.  Changing any of this means writing huge gobs of code.

We did write some special code to tab-complete cmdlet names, but that was a fairly isolated change.

For this release, we've focused our effort on really fleshing out the underlying Monad engine, APIs, and infrastructure.  These are the things that hurt the most when they are missing, and ultimately empower many, many more of you.  Writing a more functional host is on our list of things to do in a future version.

That said, this is the type of thing that the Monad community could easily astound us with.  Several BetaPlace members started writing a GUI host during our much earlier betas, but I haven't seen much activity on it lately.  A Microsoft empoyee wrote a neat WinForms application that hosts Monad in a tabbed interface.  Do you want transparency?  Tabs?  Background images? Awesome tab completion?  Go for it!  Our team (via the BetaPlace newsgroups) loves to help, should you want it or need it.

 

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

Comments [6] | | # 
 Friday, July 08, 2005
Friday, July 08, 2005 7:23:07 AM (Pacific Daylight Time, UTC-07:00) ( )

Now that we've come up to speed with the new beta, it's time to continue our journey into customizing the prompt. It’s been almost a month since I promised it, and a lot of you are already doing great things with your custom profile.

For reference sake, this is our current prompt function, as defined in profile-custom.msh:

function prompt
{
   $currentDirectory = get-location
   "MSH $currentDirectory >"
}

That prompt looks like:

MSH C:\Documents and Settings\Lee >

Ideally, we want it to act more like this:

MSH:1 C:\Documents and Settings\Lee ># some command
MSH:2 C:\Documents and Settings\Lee ># some other command
MSH:3 C:\Documents and Settings\Lee >"Hello World"
Hello World
MSH:4 C:\Documents and Settings\Lee ># yet another command
MSH:5 C:\Documents and Settings\Lee >invoke-history 3
"Hello World"
Hello World

By giving the history ID in the prompt, you can re-invoke those commands without having to use your keyboard’s arrow keys to cycle through the history buffer. Notice how I was able to use the "invoke-history" cmdlet to execute the command I saw on that line.

So how do we go about updating the prompt to include the line number?

Largely, the secret lays in the "get-history" cmdlet. Try it now, and see what it shows:

MSH:6 C:\Documents and Settings\Lee >get-history
Id $_ CommandLine
-- -- -----------
1 C # some command
2 C # some other command
3 C "Hello World"
4 C # yet another command
5 C "Hello World"

At first blush, it seems as though the simplest way to get your history count would be something like this, knowing that get-history returns a list of history items. We already know how to get the count of items in a list:

MSH:7 C:\Documents and Settings\Lee >(get-history).Count
6

That’s a great first guess, but it unfortunately suffers from two major flaws, and a minor flaw.

Major Flaw 1: get-history’s -count parameter.

By default, get-history only returns the last 30 items in your history. You could work around that problem by specifying some huge value (ie: 9999) for the count parameter, but there is a cleaner solution: leverage the Id of the last command executed.

Major Flaw 2: get-history doesn’t always return an array.

Since cmdlets can write any number of objects to the pipeline, the infrastructure decides how to output the results on a case-by-case basis. The first time you call get-history from a clean shell, you have no history. In that situation, the cmdlet returns no items – so the shell doesn’t write anything out. The second time you call get-history (from the clean shell,) your history contains only one item. In that case, the shell writes the individual object out. From the third time on, your history contains two or more items. The get-history cmdlet dutifully writes them out, and the shell finally returns them as an array:

MSH:1 C:\Documents and Settings\Lee >get-history
MSH:2 C:\Documents and Settings\Lee >get-history
Id $_ CommandLine
-- -- -----------
1 C get-history

MSH:3 C:\Documents and Settings\Lee >get-history
Id $_ CommandLine
-- -- -----------
1 C get-history
2 C get-history

Of course, we have a way of working around this as well, or I wouldn’t bring it up!

Minor Flaw 3: We need to display a history Id from the future.

The Id of the last command (or total history count) isn’t exactly what you want. If you use the Id of the last command (or total history count,) the number will not represent the command you are about to execute. In that case, you won’t be able to use the numbers as parameters to invoke-history.

Here’s the final solution that works around these problems, heavily commented for clarity:

function prompt
{
   ## Get the history. Since the history may be either empty,
   ## a single item or an array, the @() syntax ensures
   ## that Monad treats it as an array
   $history = @(get-history)
  

   ## If there are any items in the history, find out the
   ## Id of the final one.
   ## Monad defaults the $lastId variable to '0' if this
   ## code doesn't execute.
   if($history.Count -gt 0)
   {
      $lastItem = $history[$history.Count - 1]
      $lastId = $lastItem.Id
   }

   ## The command that we're currently entering on the prompt
   ## will be next in the history. Because of that, we'll
   ## take the last history Id and add one to it.
   $nextCommand = $lastId + 1

   ## Get the current location
   $currentDirectory = get-location

   ## And create a prompt that shows the command number,
   ## and current location
   "MSH:$nextCommand $currentDirectory >"
}

That gets you a very functional history-browsing prompt, and is just about the one that I use on all of my machines. In an upcoming post, I’ll talk about some other things you might want to do with your prompt.

And for those of you who are becoming more familiar with the shell, one of my older questions still stands:

What idioms from other languages / shells do you find the hardest to un-train yourself from? For example, "cd.." is a valid command in DOS, but not MSH. What have you found the hardest to discover? What did you do to try and discover it? I'm looking for things like,

"I couldn't figure out how to use a hash table, so I typed get-command *hashtable*"

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

Comments [9] | | # 
 Friday, July 01, 2005
Friday, July 01, 2005 11:53:20 PM (Pacific Daylight Time, UTC-07:00) ( )

Warning: New research suggests that using Monad (MSH) may make you too efficient.  In a recent study, animals that were provided chronic access to MSH worked harder, and ate less.

It's a fun shell, and a fun language -- but please, everything in moderation.

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

Comments [0] | | # 
Friday, July 01, 2005 11:43:36 PM (Pacific Daylight Time, UTC-07:00) ( )

As I mentioned earlier, we posted a new MSH beta to BetaPlace.  It's a lot more polished than the one we posted 9 months ago, so I'll be working with that version from now on.  The articles so far haven't delved very deeply into code just yet (how fortunate!) so there aren't many breaking changes we need to comply with.  However, there have been  few -- so let's go over them.

 

First, you'll notice that we've significantly timmed down the default profile.  Much of the functionality was rolled into the engine, but a lot of it was simply code that very few people use.  Our journey so far has used a few of them, though.  For example, we used the "$MyDocuments" variable to help us load (dot-source) our custom profile.  However, we're in good shape.  Even if you put tons of code in your custom profile, it's in a separate file and will not be affected by changes to the default profile.  So, let's fix that. 

 

First, verify that you have the current profile.  It should be about 66 lines.  

MSH D:\Lee\MSH >(get-content profile.msh).Count

66

If you see over 300, then that's the old profile.  If you've still got the old profile, then you should really start with a clean slate.  Uninstall "Microsoft Command Shell Preview," uninstall "Windows Command Shell Preview," and delete the old profile.  Then, reinstall "Microsoft Command Shell Preview."

 

Now, open the profile with your text editor of choice.  Re-add the $MyDocuments variable, and re-add the custom profile loading:

...

    }

}

 

$myDocuments = [Environment]::GetFolderPath("MyDocuments")

. (combine-path $myDocuments "MSH\profile-custom.msh")

Save it, and reload your shell. 

 

If you liked the $profile variable, you might want to add that to your profile-custom.msh:

$profile = combine-path $myDocuments "MSH\profile-custom.msh"

If you aliased 'cls' to 'clear-host,' the default profile does that for you now.  Feel free to remove it from profile-custom.msh.  All told, this is my profile-custom.msh, as it stands right now. 

## profile-custom.msh

## Stores customizations to the default profile.

 

$profile = combine-path $myDocuments "MSH\profile-custom.msh"

$tools = combine-path $myDocuments "tools"

 

function prompt

{

   $currentDirectory = get-location

   "MSH $currentDirectory >"

}

Notice that I made the prompt function a little more readable, by temporarily storing the current location in a variable.  It will soon get ungainly should we try to stuff it all in a single line.  Also, I've added a variable to hold my tools directory.  My tools directory holds scripts that I've written, and other generally-useful utility programs.  You'll want to add it to your path, so you have 2 options:

 

1) Use System Properties | Advanced | Environment Variables.  Create Path if it doesn't exist.  If it does exist, append the value of $tools to it.

2) Do it from a MSH prompt:

MSH C:\Temp >$newPath = $tools

MSH C:\Temp >if((get-property HKCU:\Environment).Path)

>> { $newPath = (get-property HKCU:\Environment).Path + ";" + $newPath }

>> set-property -Path HKCU:\Environment -Property Path -Value $newPath

>> 

MSH C:\Temp >(get-property HKCU:\Environment).Path

d:\lee\tools

Now, restart your MSH shell.  Your $tools directory is now part of your path.  If you haven't created the directory already, now is a good time to do so:

MSH C:\Temp >md $tools

As we create custom scripts, we'll place them in $tools to allow us to run them from wherever we like.

 

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

Comments [4] | | # 
 Monday, June 27, 2005
Tuesday, June 28, 2005 6:35:45 AM (Pacific Daylight Time, UTC-07:00) ( )

If you were one of the many that missed Jeffrey's standing-room-only presentations at TechEd, the kind folks at TechNet feel your pain. They've decided to host a series of "Best of TechEd" webcasts, and our two presentations are among them. If you want to learn more about the capabilities of Monad, be sure to check these out.

Session 1: Next Generation Command Line Scripting with Monad (Part 1 of 2) (Level 300)
This session provides an architectural overview of Monad, and begins to introduce many of its powerful features.
http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032277850&EventCategory=4&culture=en-US&CountryCode=US

Session 2: Next Generation Command Line Scripting with Monad (Part 2 of 2) (Level 300)
This session discusses some of the advanced scripting constructs, such as script blocks, dedicated streams, ubiquitous parameters, errors, exceptions, debugging, and tracing.
http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032277852&EventCategory=4&culture=en-US&CountryCode=US

 

Now, I would be remiss without including something for the hard-core geeks that already have Monad installed:

MSH C:\Temp >$organizer = "http://go.microsoft.com/fwlink/?linkid=41781"
MSH C:\Temp >$summary = "Monad TechNet Webcast"
MSH C:\Temp >$description = "Next Generation Command Line Scripting with Monad (Part 1 of 2)"
MSH C:\Temp >$location = "
http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032277850&EventCategory=4&culture=en-US&CountryCode=US"
MSH C:\Temp >$start = [DateTime]::Parse("Wednesday, August 03, 2005 09:30 AM")
MSH C:\Temp >$end = $start.AddHours(1).AddMinutes(30)
MSH C:\Temp >.\new-calendaritem $organizer $start $end $summary $description $location -out:TechEd_1.ics
MSH C:\Temp >
MSH C:\Temp >$description = "Next Generation Command Line Scripting with Monad (Part 2 of 2)"
MSH C:\Temp >$start = [DateTime]::Parse("Wednesday, August 10, 2005 9:30 AM ")
MSH C:\Temp >$end = $start.AddHours(1).AddMinutes(30)
MSH C:\Temp >$location = "
http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032277852&EventCategory=4&culture=en-US&CountryCode=US"
MSH C:\Temp >.\new-calendaritem $organizer $start $end $summary $description $location -out:TechEd_2.ics
MSH C:\Temp >
MSH C:\Temp >.\TechEd_1.ics; .\TechEd_2.ics


## new-calendaritem.msh
## Create a new calendar item using the iCal (.ics) calendar format
## new-calendaritem -organizer:"
http://go.microsoft.com/fwlink/?linkid=41781" -start:$([DateTime]::Now) -summary:"Monad TechNet Webcast" -description:"Monad TechNet Webcast: Next Generation Command Line Scripting with Monad (Part 1 of 2) (Level 300)" -location:"http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032277850&EventCategory=4&culture=en-US&CountryCode=US" -out:output.ics

param(
 [string] $organizer = "",
 [DateTime] $start = $(throw "Please specify a start time."),
 [DateTime] $end = $start.AddMinutes(30),
 [string] $summary = $(throw "Please provide a meeting summary."),
 [string] $description = "",
 [string] $location = $(throw "Please provide a meeting location."),
 [string] $out
     )


## We want to convert the times to Universal, so that we can be unequivocal about
## when they start.  The RFC supports appointments in the local time zone (ie: lunch,)
## but that's not very helpful when we're sharing most appointments.
## All input times will be assumed to be local times
$utcStart = $start.ToUniversalTime()
$utcEnd = $end.ToUniversalTime()

## Convert them to the ICS format
$dtStart = $utcStart.ToString("yyyyMMddTHHmmssZ")
$dtEnd = $utcEnd.ToString("yyyyMMddTHHmmssZ")

## A very simple calendar item
$template = @"
BEGIN:VCALENDAR
BEGIN:VEVENT
ORGANIZER:$organizer
DTSTART:$dtStart
DTEND:$dtEnd
SUMMARY:$summary
DESCRIPTION:$description
LOCATION:$location
END:VEVENT
END:VCALENDAR
"@

## Dump it out to the file or the screen
if($out)
{
   $template | out-file -encoding:ascii $out
}
else
{
   $template
}

 

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

Comments [0] | | # 
Tuesday, June 28, 2005 3:33:46 AM (Pacific Daylight Time, UTC-07:00) ( )

Adam has been fleshing out a great use of Monad -- by creating a mini command-line RSS reader!  It's touched on registry modification, XML parsing, internet access, and more.

Check it out, if any of those buzz words interest you :)  All we need to do is throw AJAX and Web 2.0 into the mix, and we'll have a home-run IPO!

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

Comments [0] | | #