I knew that the command line tool subst would create virtual drives that map to existing directories but I didn't know that subst lets you name the virtual drives with characters that aren't US-ASCII letters. For instance you can run 'subst 4: C:\windows' and then 'more 4:\win.ini' to dump C:\windows\win.ini. This also works for non-US-ASCII characters like, "C" (aka U+FF23, Fullwidth Latin Capital Letter C), which when displayed by cmd.exe via some best fit style character conversions looks just like the regular US-ASCII 'C'. None of Explorer, IE, or the common file dialogs allow the use of these odd virtual drives -- just cmd.exe, so I'm not sure how this would ever be useful but I thought it was odd and I wanted to share.
PowerShell gives us a real CLI for Windows based around .Net stuff. I don't like the creation of a new shell language but I suppose it makes sense given that they want something C# like but not C# exactly since that's much to verbose and strict for a CLI. One of the functions you can override is the TabExpansion function which is used when you tab complete commands. I really like this and so I've added on to the standard implementation to support replacing a variable name with its value, tab completion of available commands, previous command history, and drive names (there not restricted to just one letter in PS).
Learning the new language was a bit of a chore but MSDN helped. A couple of things to note, a statement that has a return value that you don't do anything with is implicitly the return value for the current function. That's why there's no explicit return's in my TabExpansion function. Also, if you're TabExpansion function fails or returns nothing then the builtin TabExpansion function runs which does just filenames. This is why you can see that the standard TabExpansion function doesn't handle normal filenames: it does extra stuff (like method and property completion on variables that represent .Net objects) but if there's no fancy extra stuff to be done it lets the builtin one take a crack.
Here's my TabExpansion function. Probably has bugs, so watch out!
function EscapePath([string] $path, [string] $original)
{
if ($path.Contains(' ') -and !$original.Contains(' '))
{
'"' $path '"';
}
else
{
$path;
}
}
function PathRelativeTo($pathDest, $pathCurrent)
{
if ($pathDest.PSParentPath.ToString().EndsWith($pathCurrent.Path))
{
'.\' $pathDest.name;
}
else
{
$pathDest.FullName;
}
}
# This is the default function to use for tab expansion. It handles simple
# member expansion on variables, variable name expansion and parameter completion
# on commands. It doesn't understand strings so strings containing ; | ( or { may
# cause expansion to fail.
function TabExpansion($line, $lastWord)
{
switch -regex ($lastWord)
{
# Handle property and method expansion...
'(^.*)(\$(\w|\.) )\.(\w*)$' {
$method = [Management.Automation.PSMemberTypes] `
'Method,CodeMethod,ScriptMethod,ParameterizedProperty'
$base = $matches[1]
$expression = $matches[2]
Invoke-Expression ('$val=' $expression)
$pat = $matches[4] '*'
Get-Member -inputobject $val $pat | sort membertype,name |
where { $_.name -notmatch '^[gs]et_'} |
foreach {
if ($_.MemberType -band $method)
{
# Return a method...
$base $expression '.' $_.name '('
}
else {
# Return a property...
$base $expression '.' $_.name
}
}
break;
}
# Handle variable name expansion...
'(^.*\$)([\w\:]*)$' {
$prefix = $matches[1]
$varName = $matches[2]
foreach ($v in Get-Childitem ('variable:' $varName '*'))
{
if ($v.name -eq $varName)
{
$v.value
}
else
{
$prefix $v.name
}
}
break;
}
# Do completion on parameters...
'^-([\w0-9]*)' {
$pat = $matches[1] '*'
# extract the command name from the string
# first split the string into statements and pipeline elements
# This doesn't handle strings however.
$cmdlet = [regex]::Split($line, '[|;]')[-1]
# Extract the trailing unclosed block e.g. ls | foreach { cp
if ($cmdlet -match '\{([^\{\}]*)$')
{
$cmdlet = $matches[1]
}
# Extract the longest unclosed parenthetical expression...
if ($cmdlet -match '\(([^()]*)$')
{
$cmdlet = $matches[1]
}
# take the first space separated token of the remaining string
# as the command to look up. Trim any leading or trailing spaces
# so you don't get leading empty elements.
$cmdlet = $cmdlet.Trim().Split()[0]
# now get the info object for it...
$cmdlet = @(Get-Command -type 'cmdlet,alias' $cmdlet)[0]
# loop resolving aliases...
while ($cmdlet.CommandType -eq 'alias') {
$cmdlet = @(Get-Command -type 'cmdlet,alias' $cmdlet.Definition)[0]
}
# expand the parameter sets and emit the matching elements
foreach ($n in $cmdlet.ParameterSets | Select-Object -expand parameters)
{
$n = $n.name
if ($n -like $pat) { '-' $n }
}
break;
}
default {
$varNameStar = $lastWord '*';
foreach ($n in @(Get-Childitem $varNameStar))
{
$name = PathRelativeTo ($n) ($PWD);
if ($n.PSIsContainer)
{
EscapePath ($name '\') ($lastWord);
}
else
{
EscapePath ($name) ($lastWord);
}
}
if (!$varNameStar.Contains('\'))
{
foreach ($n in @(Get-Command $varNameStar))
{
if ($n.CommandType.ToString().Equals('Application'))
{
foreach ($ext in @((cat Env:PathExt).Split(';')))
{
if ($n.Path.ToString().ToLower().EndsWith(($ext).ToString().ToLower()))
{
EscapePath($n.Path) ($lastWord);
}
}
}
else
{
EscapePath($n.Name) ($lastWord);
}
}
foreach ($n in @(Get-psdrive $varNameStar))
{
EscapePath($n.name ":") ($lastWord);
}
}
foreach ($n in @(Get-History))
{
if ($n.CommandLine.StartsWith($line) -and $n.CommandLine -ne $line)
{
$lastWord $n.CommandLine.Substring($line.Length);
}
}
# Add the original string to the end of the expansion list.
$lastWord;
break;
}
}
}
The weekend before last Sarah and I went down to Gas Works Park in Seattle. Gas Works Park is a former Seattle Gas Light Company gasification plant now turned into a park with the machinery kept intact and found right on the shore of Lake Union. There's a large hill right next to the plant with an embedded art installation from which you get an excellent view of the park and the lake. Anyway a very cool place. Afer, we ate at Julia's of Wallingford where I stereotypically had the Santa Cruz omelet. Good food, nice place, nice neighborhood.
This past weekend was Halloween weekend. On Halloween at Microsoft parents bring their kids around the office buildings and collect candy from those who have candy in their office. See Matt's photo of one such hallway at Microsoft. The next day Sarah and I went to two birthday parties the second of which required costume. I went as House (from the television show House) by putting on a suit jacket and carrying a cane. Sarah wore scrubs to lend cred. to my lazy costume. Oh yeah and on Sunday Sarah bought a new car.
Windows allows for application protocols in which, through the registry, you specify a URL scheme and a command line to have that URL passed to your application. Its an easy way to hook a webbrowser up to your application. Anyone can read the doc above and then walk through the registry and pick out the application protocols but just from that info you can't tell what the application expects these URLs to look like. I did a bit of research on some of the application protocols I've seen which is listed below. Good places to look for information on URI schemes: Wikipedia URI scheme, and ESW Wiki UriSchemes.
Scheme | Name | Notes |
---|---|---|
search-ms | Windows Search Protocol |
The search-ms application protocol is a convention for querying the Windows Search index. The protocol enables applications, like Microsoft Windows Explorer, to query the index with
parameter-value arguments, including property arguments, previously saved searches, Advanced Query Syntax, Natural Query Syntax, and language code identifiers (LCIDs) for both the Indexer and
the query itself. See the MSDN docs for search-ms for more info. Example: search-ms:query=food |
Explorer.AssocProtocol.search-ms | ||
OneNote | OneNote Protocol |
From the OneNote help: /hyperlink "pagetarget" - Starts OneNote and opens the page specified by the pagetarget parameter. To obtain the hyperlink for any page in a OneNote
notebook, right-click its page tab and then click Copy Hyperlink to this Page.Example: onenote:///\\GUMMO\Users\davris\Documents\OneNote%20Notebooks\OneNote%202007%20Guide\Getting%20Started%20with%20OneNote.one#section-id={692F45F5-A42A-415B-8C0D-39A10E88A30F}&end |
callto | Callto Protocol |
ESW Wiki Info on callto Skype callto info NetMeeting callto info Example: callto://+12125551234 |
itpc | iTunes Podcast |
Tells iTunes to subscribe to an indicated podcast. iTunes documentation. C:\Program Files\iTunes\iTunes.exe /url "%1" Example: itpc:http://www.npr.org/rss/podcast.php?id=35 |
iTunes.AssocProtocol.itpc | ||
pcast | ||
iTunes.AssocProtocol.pcast | ||
Magnet | Magnet URI | Magnet URL scheme described by Wikipedia. Magnet URLs identify a resource by a hash of that resource so that when used in P2P scenarios no central authority is necessary to create URIs for a resource. |
mailto | Mail Protocol |
RFC 2368 - Mailto URL Scheme. Mailto Syntax Opens mail programs with new message with some parameters filled in, such as the to, from, subject, and body. Example: mailto:?to=david.risney@gmail.com&subject=test&body=Test of mailto syntax |
WindowsMail.Url.Mailto | ||
MMS | mms Protocol |
MSDN describes associated protocols. Wikipedia describes MMS. "C:\Program Files\Windows Media Player\wmplayer.exe" "%L" Also appears to be related to MMS cellphone messages: MMS IETF Draft. |
WMP11.AssocProtocol.MMS | ||
secondlife | [SecondLife] |
Opens SecondLife to the specified location, user, etc. SecondLife Wiki description of the URL scheme. "C:\Program Files\SecondLife\SecondLife.exe" -set SystemLanguage en-us -url "%1" Example: secondlife://ahern/128/128/128 |
skype | Skype Protocol |
Open Skype to call a user or phone number. Skype's documentation Wikipedia summary of skype URL scheme "C:\Program Files\Skype\Phone\Skype.exe" "/uri:%l" Example: skype:+14035551111?call |
skype-plugin | Skype Plugin Protocol Handler |
Something to do with adding plugins to skype? Maybe. "C:\Program Files\Skype\Plugin Manager\skypePM.exe" "/uri:%1" |
svn | SVN Protocol |
Opens TortoiseSVN to browse the repository URL specified in the URL. C:\Program Files\TortoiseSVN\bin\TortoiseProc.exe /command:repobrowser /path:"%1" |
svn+ssh | ||
tsvn | ||
webcal | Webcal Protocol |
Wikipedia describes webcal URL scheme. Webcal URL scheme description. A URL that starts with webcal:// points to an Internet location that contains a calendar in iCalendar format. "C:\Program Files\Windows Calendar\wincal.exe" /webcal "%1" Example: webcal://www.lightstalkers.org/LS.ics |
WindowsCalendar.UrlWebcal.1 | ||
zune | Zune Protocol |
Provides access to some Zune operations such as podcast subscription (via Zune Insider). "c:\Program Files\Zune\Zune.exe" -link:"%1" Example: zune://subscribe/?name=http://feeds.feedburner.com/wallstrip. |
feed | Outlook Add RSS Feed |
Identify a resource that is a feed such as Atom or RSS. Implemented by Outlook to add the indicated feed to Outlook. Feed URI scheme pre-draft document "C:\PROGRA~2\MICROS~1\Office12\OUTLOOK.EXE" /share "%1" |
im | IM Protocol |
RFC 3860 IM URI scheme description Like mailto but for instant messaging clients. Registered by Office Communicator but I was unable to get it to work as described in RFC 3860. "C:\Program Files (x86)\Microsoft Office Communicator\Communicator.exe" "%1" |
tel | Tel Protocol |
RFC 5341 - tel URI scheme IANA assignment RFC 3966 - tel URI scheme description Call phone numbers via the tel URI scheme. Implemented by Office Communicator. "C:\Program Files (x86)\Microsoft Office Communicator\Communicator.exe" "%1" |