Wednesday, July 19, 2006

Edit IE Address Bar with PowerShell

Lately, I've been doing a lot of testing of our web app. And for the last month or so, its all been on the same page. Being a shortcut key addict, I would type the url to the page in IE's address bar and then use F4 and the arrow keys to call it. As long as I kept going to that page, the url would always be at the top of the list. However, if I started going to other sites, the address bar's list would get long and my test url would get lost somewhere inside. It's a real pain the rump to go searching for it.

So I put together this little PowerShell script. It clears the address list and puts in the test urls that I want.

#1
$regpath = "HKCU:\Software\Microsoft\Internet Explorer\TypedURLs"
$xmlpath = "C:\Allen\PSScripts\AddressBarLinkList.xml"
cd $regpath
#2
(Get-Item . ).GetValueNames() |
Where-Object {$_ -match "^url\d+"} |
ForEach-Object { Remove-ItemProperty . $_ }

#3
$xml = New-Object xml
$xml.Load($xmlpath)

#4
$xml.urls.url |
ForEach-object {
New-ItemProperty -Path $path -Name $_.name -type string -value $_.link
}
cd c:

Let's look at each step and see what's going on. In step #1, I create 2 variables, $regpath and $xmlpath, and load them with some paths. Note that the regpath uses the registry key HKCU as a drive. In PowerShell the registry keys are treated equivalent to folders in the File System and registry values are treated equivalent to files in the File System. All you have to do is change directory to HK whatever and your in.

In step #2, we see a string of commands piped into each other. First we get the names of all the values in the key. Next we use Where-Object to pull only those values that match a regular expression. Then I delete all the values in the result set.

Step #3 starts with the creation of a new variable, $xml, of type xml (of the .NET xml's). Pretty much, what I'm doing here is creating a new Xml.Document object. On the next line I call the xml object's Load method to load an xml file from a filepath. The xml file contains a list of urls that I want loaded into the address bar. Take a look:

<urls>
<url>
<name>url1</name>
<link>http://localhost/Application/test.aspx?ItemId=70946</link>
</url>
<url>
<name>url2</name>
<link>http://DevServer/Application/test.aspx?ItemId=70946</link>
</url>
</urls>

In step #4, I get a list of all the url nodes. What is really interesting is the notation I used to get them, "$xml.urls.url". It reads like an XPath statement, just with dots instead of slashes. Next I use the New-ItemProperty command to create a key value for each of the urls in my xml file.

So this script edits the registry, reads an external file and parses an xml document. That's a lot when you consider that it took what amounts to 6 lines of code. I put in some line breaks and formatting to make it easier to read. Also note, that I didn't use any aliases in the script. It could be rewritten as:

cd HKCU:\Software\Microsoft\Internet Explorer\TypedURLs
(gi . ).GetValueNames() | ? {$_ -match "^url\d+"} | % { rp . $_ }
$xml = New-Object xml
$xml.Load("C:\Allen\PSScripts\AddressBarLinkList.xml")
$xml.urls.url | % { New-ItemProperty -P HKCU:\Software\Microsoft\Internet Explorer\TypedURLs -N $_.name -t string -v $_.link }
cd c:

Friday, July 14, 2006

Access your classes in PowerShell

In this example, we will build a small class library and "shell out" to it in a PowerShell script. I know that it is dinky and lame, but bear with me. We will build on this knowledge in a later post. Here are the steps to reproduce the example.

Fire up your favorite text editor and paste in the following code. I'm using Notepad2 http://www.flos-freeware.ch/notepad2.html. It has c# syntax highlighting. Save your file as "Person.cs". For me, the full path look like "C:\Allen\Spikes\PSClassAccess\Person.cs".

using System;
using System.Text;

namespace Person
{
public class Phrases
{
public Phrases()
{
}

public static string Hello(string Name)
{
return "Hello " + Name;
}

public string GoodBye(string Name)
{
return "Goodbye " + Name;
}
}
}

Open up an instance of the Visual Studio command prompt. Then browse to your folder and enter the following command. If all goes well, you will now have a Person.dll file.

csc /target:library Person.cs
Open another instance of your text editor and paste in this code.

# This loads the dll into memory
[System.Reflection.Assembly]::loadfrom("C:\Allen\Spikes\PSClassAccess\Person.dll")

# Write a blank line for aesthetic reasons
echo("")

# Here we call the static method directly from the class.
[Person.Phrases]::Hello("Allen")

# Now lets create an instance of the class.
[Person.Phrases] $Greeter = New-Object -TypeName Person.Phrases

# Here we call the non-static method GoodBye.
$Greeter.GoodBye("Max")

Save your file as "HelloGoodbye.ps1". The "PS1" extension is PowerShells default extenstion for scripts. The first thing that we do in the script is load our dll into the shells memory. A couple of lines down we call the static Hello method. Notice that the type is enclosed in square brackets. Next we use the New-Object commandlet to create a new instance fo the Phrases class as $Greeter. In Powershell, all variables begin with the $ character. Finally we use the new object to call the Goodbye method.

So lets see what we get. Open up a PowerShell console and browse to the directory where you saved your script. Run the script by typing "./HelloGoodbye.ps1". Your results should look like the following:

PS> ./HelloGoodbye.ps1

GAC Version Location
--- ------- --------
False v2.0.50727 C:\Allen\Spikes\PSClassAccess\Person.dll

Hello Allen
Goodbye Max

Sweet! The first thing we see is the result from loading the dll. You'll quickly determine that it is not in the gac and was compiled with version 2 of the .NET framework. Then we see the results of our script. Its a beautiful thing.

This really shows the power and versatility of PowerShell. How many uses just popped into your head?

Thursday, July 13, 2006

Just Upgraded StarTeam

I just upgraded to StarTeam 2005 and here are my initial thoughts. The first thing I noticed is that it doesn't look all that different. That's probably smart. No need to burden the users with a learning curve. Aesthetically, they prettied up the icons in the toolbar and sported a new splash screen.

They must have read my earlier blog post, because the All Descendants functionality is now available in the Change Request and File Menus. There still isn't a dedicated shortcut for it, but you access it quickly using "Alt+L, A" when in the file view and "Alt+C, A" when in the Change Request view.

The biggest item on my StarTeam wish list is the ability to easily extend the application. Maybe I'm spoiled from using Visual Studio, where I can assign keyboard mappings and add plugins. One thing that gets me is Borland's willingness to give us access to the SDK, but not let us use it to extend StarTeam directly. I have functionality that is still missing. I can write it, but I can't run it from inside StarTeam.

Wednesday, July 12, 2006

List files based on number of lines.

1I was working on a javascript error in IE which returned the line number of the error but not the file where the error occured. The page was accessing half a dozen seperate js files. This particular page doesn't do to well in FireFox, so using Venkman was out. However, there was something unusual about the error. The line number was 1202. I thought to myself, "How many js files could we possibly have with more than a thousand lines of code?" It actually isn't that many. But how to find this out? I'll tell you how! Powershell!

I opened up a powershell console, changed the directory to the directory where we keep all the js files, and typed in this little ditty (after several revisions, of course).
ls *.js | where {(gc $_).length -gt 1200}
Which returned:
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 7/11/2006 2:12 PM 53520 JScript.js
-a--- 6/15/2006 1:43 PM 43806 StoryDescription.js
-a--- 7/11/2006 2:12 PM 88027 StoryDescriptionEdit.js

Let's look at what just happened here. At first glance, you'll notice that the line is pretty terse. That's because I used predefined aliases for the functions that I called. In PowerShell, every command that you call is actually a small application known as a commandlet. PowerShell also lets you alias these commandlets, in order to save you some time. There are many predefined aliases. To see a list of the aliases, just type "alias" at the prompt. If you pass in an alias then it will limit the results to that alias name. So if you type in "alias ls", you'll see that you're actually calling Get-ChildItem. It has just been aliased to make it easier for Unix/Linux users. It is also aliased as "dir". In fact, the entire line could be rewritten without any aliases as
Get-ChildItem *.js | Where-Object { (Get-Content $_)
.length -gt 1200 }

So the first little bit of the string returns a list of all the files in the current directory that have a ".js" extension. Then the pipe symbol, "|", passes the results to the next code statement. The where-object commandlet acts like a filter and only returns the items that meet the specified criteria. In this case we're making sure that the number of lines in the file is greater than (-gt) 1200.

We determine the number of lines with the Get-Content commandlet. This commandlet returns an array of all the lines in the file. You can even loop through the array, but that's another blog post. We just need to get the length property off of the array object.

And there you have it. Only three files in the directory have more than 1200 lines of code and my page only uses one of them. Even though I'm just a newbie, I'm loving PowerShell. Unlike most of my peers, I refuse to believe that it is only for system admins.

Tuesday, July 11, 2006

Random Thoughts

It's been a slow week for blogging. I've been pretty busy with the latest push. Here are few of my random thoughts that occured to me as I worked on StarTeam tickets.

9:05 Apparently, there is no "I" in "StarTeam".
10:38 If I were a pirate, my favorite word to say would be "Scurvy".
11:07 Mmmmm... Rosa's
1:43 The new nickel really creeps me out. Stop staring at me!
2:05 Stupid Farpoint! What would the magic eight skull do?
2:06 Walk the plank? ...riiight.
2:10 I just had a great idea for a blog post.1

Friday, July 07, 2006

Translate Time values with Javascript

11 On my current project, I have a need to convert a string to a valid time format. The user wants to be able to enter time in several formats. For example military time(1400), hour followed by a or p(2p), etc... I had a solution that got the job done, but it was getting long, was covered with bandaids and smelled like spaghetti. So I refactored it and came up with this approach. If you take out the comments, it has less than half the lines of code in the previous version. I use a trim string function to trim the spaces off the front and back of the time string, so I'm including it at the bottom.

Let me know if there is a better way to do this or if this might solve a problem you are having.


function fixTime(strTime)
{
var dayHalf = "a";
var Hour = "";
var Minute = "00";

// Drop the case
strTime = strTime.toLowerCase();

// Trim the spaces from the front and back
// of the string
strTime = TrimStr(strTime);

// Check for a|am|p|pm.
var amMatch = /(am|a|pm|p)$/.exec(strTime);
if(amMatch != null)
{
if(/p/.test(amMatch[0]))
dayHalf = "p";
}

// Remove a|am|p|pm
strTime = strTime.replace(/[a|p|m|:|\s]*/g, "");

// Now we should have nothing left but numbers.
// If the length of the string is 1 or 2,
// then we are dealing with hours only.
if(strTime.length < hour =" strTime;" hour =" strTime.slice(0," minute =" strTime.slice(-2);"> 23)
Hour = Hour % 24;

// Convert the hour and dayhalf.
if(Hour > 12)
{
Hour = Hour - 12;
dayHalf = "p";
}
else if(Hour == 0)
{
Hour = 12;
dayHalf = "a";
}

// This line ensures that the Hour variable is
// converted to a number type so the result
// won't have any preceding zeros.
// ex. "02" would loose the first zero.
Hour *= 1;

// Build our return string
strTime = Hour + ":" + Minute + dayHalf;
return strTime;
}

function TrimStr(str)
{
var sResult = new String(str);
var re1 = /(^\s+)/
var re2 = /(\s+$)/
sResult = sResult.replace (re1, "");
sResult = sResult.replace (re2, "");
return sResult.toString();
}

Wednesday, July 05, 2006

SQL Tip: Union vs Union All

A co-worker gave me a SQL tip today.

TIP: Whenever possible, use UNION ALL, instead of UNION, because it is faster.

This is a great tip and here's why. UNION eliminates all duplicate rows and sorting results. This requires that it create a temp table, storing all the records and sorting them before generating the result. So its not the most efficient way of doing things. A potential issue with using UNION is the danger of loading the tempdb database with a huge temptable.

UNION ALL, on the other hand, doesn't eliminate duplicates. It just performs the first select and then appends the second select onto the first result set. There is no temp table or sorting involved. This is much more efficient. If you don't expect duplicate data, then this is the way to go.