31 January 2008

Cmd.exe Environment Variables with Colon and Tilde

Some commands in Windows cmd.exe batch files have a leading tilde (~) character or a trailing colon-tilde (:~) pair of characters attached to environment variable names. What's the purpose of these characters?

Trailing Colon-Tilde Pair

You can find more about colon-tilde in the help using set /?. Briefly, you can …

  • Slice a string in a variable: %NAME:~s,n where s is the start position (zero-offset) and n is the number of characters. If s is negative, then the offset starts from the right side minus 1 (i.e. -1 refers to the rightmost character). If n is negative, then length - n characters, are extracted.

  • Replace a substring with another string in a variable: %NAME:s1=s2% where s1 is substring to be replaced and s2 is the replacement.

Leading Tilde

The leading tilde is used to decompose elements in a batch file parameter formatted as a path, such as the parent directory or file extension. The best reference is Frequently Asked Questions Regarding The Windows 2000 Command Processor, "How do I parse a file name parameter into its' constituent parts?" (sic). Note that you can only use a leading tilde for batch file parameters, not environment variables (!).

4-Mar-08. Another version of the reference is Frequently Asked Questions Regarding The Windows 2000 Command Processor. 09-Sep-02.

SciTE Customization

Note for myself: global customization of the SciTE (Scintilla Text Editor) is stored in install\SciTEGlobal.properties file:

font.base=font:Courier New,size:9
indent.size=2
position.height=640
position.width=800
use.tabs=false

30 January 2008

Add Path Environment Variable in PowerShell

Adding a path to the PATH environment variable in the current PowerShell session is simpler than I thought:

> $env:path += ";path"

Note: remember to prepend the semi-colon to the new path make a valid path list.

You can make your PATH variable persistent using the SetEnvironmentVariable() .Net method:

[System.Environment]::SetEnvironmentVariable("PATH", $Env:Path + ";path", "target")

… where target is "Machine", "User" or "Process". Check the .Net documentation for what these values mean.

See also

27 January 2008

Javascript Input Focus Annoyance

Many web sites have forms that move the keyboard cursor into an input field. For example, view your favourite search engine page and note that the cursor is in the search field. Enter a query, press RETURN, wait for the results, move the cursor out of the search field by hitting the TAB key, click on a link (don't open a new tab or window) then return to the search page. You should find that the cursor is back in the search field. If you usually just use the keyboard instead of the mouse, you have to hit TAB to move the cursor out of that field before you can scroll the page up or down. Some web sites have a search field in every page, so it is even more annoying to hit the TAB key in every page. (Why do I even persist in using these web sites?)

Web pages that display this behaviour usually use Javascript's focus() function. If you view the source code of such pages, you should see something like this: document. … .focus().

There's several solutions to this annoyance in Firefox.

The most general method is to disable Javascript by unchecking the Tools / Options / Content / Enable Javascript option, rather like using a sledgehammer to kill an ant.

If you just want to stop web pages from using the focus() method for the text fields (the INPUT tag), you can modify Firefox's security policy by editing your user.js preferences file:

user_pref("capability.policy.default.HTMLInputElement.focus", "noAccess");

Note: The policy named default is applied for all sites.

If you only want to stop certain sites from using the focus() method, create a new policy and specify when it should be applied, for instance:

user_pref("capability.policy.policynames", "noinputfocus");
user_pref("capability.policy.noinputfocus.sites", "<site list>");
user_pref("capability.policy.noinputfocus.HTMLInputElement.focus", "noAccess");

In this example, the noinputfocus policy is applied to the list of sites specified in noinputfocus.sites property.

28-Jan-2008: You have to restart Firefox before your policy is applied.

26 January 2008

Prune Directories with PowerShell

I made a backup of all files with a certain pattern files from one directory to another. If the pattern was, say PostScript (*.ps) files, you can use the following PowerShell statement:

Copy-Item -recurse -filter *.ps <source> <destination>

Now I had a new directory with the same structure as the original one, but Copy-Item made many new empty directories because there were files in the source directories but these files were not copied. Just to be tidy, I wanted to prune the empty directories in the destination path. The Remove-Item cmdlet does not have an option to remove empty directories, so I wrote the following short PowerShell script:

     1  function prune-directory {
     2    param ([string]$path)
     3    if ($path.Length -le 0) {
     4      write-host "Empty path."
     5      return
     6    }
     7    if (-not (test-path -literalPath $path)) {
     8      write-host "Invalid path:", $path
     9      return
    10    }
    11    if (@(get-childitem $path).Count -le 0) {
    12      remove-item $path
    13      return
    14    }
    15    get-childitem $path | where-object { $_.PsIsContainer} | foreach { prune-directory $_.FullName }
    16    if (@(get-childitem $path).Count -le 0) {
    17      remove-item $path
    18    }
    19  }

To use it, just enter:

prune-directory <path>

You should verify that the function works the way you expect before using it. Once your directories or files are deleted, they're GONE.

prune-directory() is a recursive function that walks a directory tree and deletes any empty directory it finds. Lines 3-10 check for invalid parameters, lines 11-14 delete the current directory if it is empty and line 18 calls this function for all children which are containers in the current directory. Lines 19-22 are required in case the current directory has no children because they were all deleted by line 15.

In line 11 and 16, we use @(…) to force the result of get-childitem $path to be an array, otherwise we may not be able to count the number of children in a directory. It's a known - uh - nuance in PowerShell that if a cmdlet finds zero or one object, it returns an scalar value rather than an array.

2008-05-15: This change should fix the problem of escape characters in the path string: test-path -literalPath $path.

Find Rows With Common Property

The table below relates users to groups. Which groups do two specified users have in common?

        ID    USER_ID   GROUP_ID
---------- ---------- ----------
         1          1          1
         2          2          1
         3          1          2
         4          3          2
         5          2          2

Here's a query that works in MS-Access and Oracle Express:

select group_id
from
  user_group where group_id in (select group_id from user_group where user_id = 1) 
  and user_id = 2;

  GROUP_ID
----------
         1
         2

Note: I've used where … in because a user can belong to more than one group. If the sub-query returns more than one row, Oracle Express reports this error:

ORA-01427: single-row subquery returns more than one row

You can look at the problem as one where you're looking for the intersection of two sets. If your DBMS supports it, you can use the intersect set operator. The following query works in Oracle Express:

select group_id from user_group where user_id = 1
intersect
select group_id from user_group where user_id = 2;

  GROUP_ID
----------
         1
         2

20 January 2008

PowerShell File Version Information

Compiled files in your Windows computer, such as executables and libraries (or files with a .exe and .dll suffix in their names), can contain some additional information stored in a FileVersion structure. You can see these properties in Explorer's Properties dialog, Details tab.

Before releasing a Windows-based product, I wanted to check that the Copyright and Product Version fields in all compiled files were correctly updated. We always increment the number in Product Version and if the product was released in the start of the year, we also update the Copyright field. You can use Windows Explorer to view these properties in several files at once (just select all relevant files and show the Properties dialog), but if a file had a different value from the others, the Properties / Details tab shows the unhelpful multiple values text. Which file has a value different from the others? It isn't that hard to check each field individually but why not automate the test?

You can use the following PowerShell one-liner to list the Copyright field of all compiled files:

> get-childitem * -include *.dll,*.exe | foreach-object { "{0}`t{1}" -f $_.Name, [System.Diagnostics.FileVersionInfo]::GetVersionInfo($_).LegalCopyright }

To test on the Windows PowerShell folder:

gpowershell.exe Copyright (c) Microsoft Corporation. All rights reserved.
powershell.exe  © Microsoft Corporation. All rights reserved.
pwrshmsg.dll    © Microsoft Corporation. All rights reserved.
pwrshsip.dll    © Microsoft Corporation. All rights reserved.

The first command get-childitem * -include *.dll,*.exe retrieves a list of files in the current directory that have a *.dll or *.exe suffix. The Get-ChildItem cmdlet has a -filter option but it only accepts one pattern.

The second command outputs the filename and the Copyright information of each file using the format (-f) operator. We use the .Net [System.Diagnostics.FileVersionInfo]::GetVersionInfo() method to obtain the file's LegalCopyright (or Copyright) field.

To check Product Version field, use .ProductVersion instead of .LegalCopyright. If you are interested in other fields, check MSDN for a complete list of field returned by GetVersionInfo.

14 January 2008

Conquest 4X Turn-based Game

Playing Conquest brings back memories of the Amiga 1000. Conquest is a simple turn-based 4X computer game set in a 26-star galaxy (stars are named A to Z), four types of space ships (scout, transport, cruiser and battleship), technology tree (velocity or speed, range and weapons) and a computer opponent. The computer opponent is pretty aggressive so even after many games, you can easily lose if you weren't careful. Each game took about 20 minutes to play, making it possible to play between assignments or while waiting for some computing task to finish (multi-tasking in the 80s was wonderful).

I first discovered the game, written by Bob Shimbo, on Fish Disk 24 then found a Windows version last week on Tim Walker's web site. Thanks Bob and Tim!

See Also

09 January 2008

PowerShell and Cat Extract Lines with Line Numbers

I wanted to extract some lines of text, each line prefixed with its line number, from a text file. Frustratingly, while IDEs such as Visual Studio and NetBeans and text editors such as Vim happily (if software were said to have emotion) show line numbers in their display, you can't select the line numbers with text! In the last century, I would use cat -n | head -n1 | tail -n2 and copy the required lines from the output. Fast forward to yesterday where I found myself using cat -n again in PowerShell. This time, I could use Select-Object to extract only the lines I wanted …

> cat -n <file> | select-object -first n1 | select-object -last n2
Get-Content : A parameter cannot be found that matches parameter name 'n'.
…

It turns out that cat is aliased to Get-Content, which doesn't process the -n parameter. Shay Levi and Richard Siddaway provided me with some solutions (see newsgroup microsoft.public.windows.powershell, topic "Temporarily ignore alias") and I was on my way again:

> cat.exe -n <file> | select-object -first n1 | select-object -last n2

Of course, since the first command creates an array of strings, you can slice it and end up with a much shorter statement:

> (cat.exe -n <file>)[r1..r2]

Note the following relationship: r1 = n1-n2 and r2 = n1.

But if you don't have cat.exe installed, you can reproduce the behaviour of cat -n with this PowerShell solution:

> get-content <file> | foreach-object { $i=1 } { "{0,4} {1}" -f $i, $_; $i++ }

Let's analyze the second command: -f is the PowerShell format operator which formats the right-hand argument ($i, $_) using the left-hand argument ("{0,4} {1}"). The first format control string ({0,4}) means "format input 0 in 4 spaces, right aligned". The second format control string ({1}) just writes each line without any formatting.

08 January 2008

Four Function Calculator using C# and Windows Forms

Wrote an article about converting an simple Java program to C# and Windows Forms and discussed development features in Visual Studio C# 2008 Express.

This blog entry is used for discussion, if any.

06 January 2008

Four Function Calculator using Java and Swing

Finished a longish article, with sample source code, about writing a simple desktop application in Java and Swing. It discusses model-view-controller, Java resource bundles and Swing AbstractAction.

This blog entry is used for discussion, if any.

01 January 2008

PowerShell Group-Object and Anagrams

In an earlier article, we used an associative array to group words with the same property (in this case, the same set of letters) to find anagrams. While that solution worked, it seemed to me that there should be an easier solution using the Group-Object cmdlet.

> "add", "dad", "dam", "mad", "made", "madam", "set" | group { $_.toCharArray() | sort-object }

Count Name                      Group
----- ----                      -----
    2 a d d                     {add, dad}
    2 a d m                     {dam, mad}
    1 a d e m                   {made}
    1 a a d m m                 {madam}
    1 e s t                     {set}

Looking good, so let's try a bigger set of words in a file:

> get-content test.txt | group-object { $_.toCharArray() | sort-object }

Count Name                      Group
----- ----                      -----
    2 a d d                     {test.txt, test.txt}
    2 a d m                     {test.txt, test.txt}
    1 a d e m                   {test.txt}
    1 a a d m m                 {test.txt}
    1 e s t                     {test.txt}

That's mighty weird. For some reason, the group has the name of the file rather than the actual word while the signature in the Name column is computed correctly. Is the problem to do with the expression for the group-object? Let's try a simpler expression:

> get-content test.txt | group-object { $_.length }

Count Name                      Group
----- ----                      -----
    5 3                         {test.txt, test.txt, test.txt, test.txt...}
    1 4                         {test.txt}
    1 5                         {test.txt}

It's very puzzling and it seems like group-object was treating each file rather than each word as an input. But then, why is the expression being computed for each word?

Even stranger is when you assign the contents of a file to a variable and get the same result!

> $l = get-content test.txt
> move-item test.txt test2.txt #Ensure original file is no longer available.
> $l | group-object {$_.length}

Count Name                      Group
----- ----                      -----
    5 3                         {test.txt, test.txt, test.txt, test.txt...}
    1 4                         {test.txt}
    1 5                         {test.txt}

In this case, I would have thought that group-object would operate on a list of words and not refer to the original file.

Later … .Net has a function string[] ReadAllLines() that returns an array of strings, so the following works a treat:

> [System.IO.File]::ReadAllLines("C:\temp\download\doc\language\test.txt") | group-object {$_.ToCharArray() | sort-object}

Count Name                      Group
----- ----                      -----
    2 a d d                     {add, dad}
    2 a d m                     {dam, mad}
    1 a d e m                   {made}
    1 a a d m m                 {madam}
    1 e s t                     {set}

At least PowerShell's integration with the .Net Framework makes it possible to solve a problem if the pre-defined cmdlets don't work as you expect.

2-Jan-2008. If you're using PowerShell 2.0 CTP, the Get-Content version works.