Tuesday, 2 April 2013

Using Powershell to perform automatic Hg Bisect

The process of bisection in the source control system Mercurial or Hg is done with the command Hg Bisect. The Hg Bisect command is usually run manually and consists of manual steps done by the user where the developer running the hg bisect command looks for a certain condition should hold true and mark each revision as either good or bad. This is done in a binary search result manner, which quickly narrows down the number of revisions to look at by half for each iteration. The goal is to find the first revision containing a bad feature. An automatic function for finding such bad features will now be presented. Here is the Powershell cmdlet or function followed by an example of a call to this function:

function Get-HgChangeSet(            
){            
    param(            
    [Parameter(Mandatory=$true)]            
    [scriptblock]             
    $test,             
            
    [Parameter()]            
    $good = 0,            
            
    [Parameter()]            
    $bad = 'tip'             
    )            
            
    hg bisect --reset;            
    hg bisect --bad $bad;             
    (hg bisect --good $good) | out-null;             
            
    while ($output -notmatch 'The first bad revision is'){            
        $result = & $test;             
            
        if ($result){            
            $output = (hg bisect --good ) | out-string;             
        }            
        else {            
            $output = (hg bisect --bad) | out-string;             
        }            
    }            
            
    $output;             
}            
            
Push-Location 'C:\toaurs-he\demorepo\'             
Get-HgChangeSet { !(Test-Path 'test.txt') -or (Get-Content test.txt) -notmatch 'ultrabad' }            
Pop-Location


In this example, a simple demo repository has a file called test.txt. I want to find the first revision where the text ultrabad was inserted. The truth condition then, is that either the file does not exist (yet) in a revision, or that the file exists and does not match ultrabad. This is a simple example, but it shows how one can search for a given text or source code by specifying this in the script block passed to the function or cmdlet Get-HgChangeSet. When the script is run, it finds the first occurence of the text ultrabad (which is bad) at revision number 8.

PS C:\toaurs-he\demorepo[ default ]> 
Hit Line breakpoint on 'C:\Users\Tore Aurstad\Documents\WindowsPowerShell\Scripts\Hg\BisectTool.ps1:22'
PS C:\toaurs-he\demorepo[ default ]> 
The first bad revision is:
changeset:   8:25be1d61e90d
user:        Tore Aurstad 
date:        Tue Apr 02 22:09:15 2013 +0200
summary:     foo 4
So there you have it, an automatic way via a Powershell script to find an introduction of a bad feature in a Hg repository with the aid of a Powershell function or cmdlet. To use this script, it is important to understand that the passed in first argument is a truth condition. It will usually always be necessary to accept if the file to test does not exist yet combined with and -or condition and then specify -notmatch and the text or source code which is not desired. This will usually be more precise than running hg bisect manually, which is kind of tedious still.

Also note that the second and third parameter is set to $good equals 0 and $bad equals 'tip'. In Mercurial or Hg, 'tip' is the newest revision. Usually, for a repository with many revisions, it will be quicker if the values for $good and $bad is specified in a well-known range, say revision 5000 to 5500 or what have you.. This is just another example of how scripting can lighten our daily workload as developers.

Using Psake to build Visual Studio solutions

Psake is a build automation tool that can be used as an alternative to the ubiqutous MsBuild XML files for .NET developers. Psake is a module for Powershell and this means your build scripts will have easy access to other resources such as remote Web Servers and file systems or other required resources in the build process. In addition, building software is primarily a procedural process and using a script with the aid of Psake makes more sense than using MSBuild XML files. It is also quicker to avoid creating MSBuild tasks and so on. Let't take a look at a sample Psake build script:
#tasks.ps1            
            
properties {            
 $config = 'debug'; #debug or release             
}            
            
task -name PackageZip -depends Build -description "proceduces a zip archive of the build output" -action {            
    import-module pscx             
    write -host "Packaging files"            
    write-host $config             
    Push-Location 'C:\Users\Tore Aurstad\Documents\visual studio 2012\Projects\TestLDAP\TestLDAP\bin'            
    dir $config\ | write-zip -output $pwd\$config\TestLDAP.zip            
    Pop-Location            
}            
            
task -name ValidateConfig -action {            
 assert ( 'debug', 'release' -contains $config) `
 "Invalid config: $config; valid values are debug and release";             
}            
            
task -name Build -depends ValidateConfig -description "builds outdated source files" -action {            
    write-host 'The build task is now running';            
    exec {            
        msbuild 'C:\Users\Tore Aurstad\Documents\visual studio 2012\Projects\TestLDAP\TestLDAP.sln' /p:Configuration=$config            
    }            
}            
            
task -name Clean -description "deletes all build artifacts" -action {            
    write-host 'The clean task is now running';             
     exec {            
        msbuild 'C:\Users\Tore Aurstad\Documents\visual studio 2012\Projects\TestLDAP\TestLDAP.sln' /t:Clean /p:Configuration=$config             
    }            
}            
            
task -name Rebuild -depends Clean,Build -action {            
    write-host 'The rebuild task is now running';             
}            
            
task default -depends Build;


The following command then will invoke one of the build targets in the Psake build scripts:

PS C:\users\Tore Aurstad\Documents\WindowsPowerShell\scripts> invoke-psake -buildfile .\script1.ps1 -task PackageZip Before running the script1.ps script file, which I have put in the Scripts folder of the parent folder of the $env:psmodulepath. In addition, psake had to be installed. To install psake, download the .zip file of this Powershell module from the following url:

Psake Github

Save the .zip file to your hard disk, unblock the zip file using file properties and click Anvanced button. Unzip the file contents and move the files into the $env:psmodulespath folder, in a subfolder called psake.

When psake is ready on your system, check that you have the correct execution policy. Run if necessary: Set-ExecutionPolicy RemoteSigned in Powershell.

The psake script contains of multiple task declarations. The task default is obligatory. The other tasks has action blocks, and some of these calls msbuild inside an exec block.

The module pscx is also used to zip the output of running msbuild. The module is available here:
Pscx module. This module is the Powershell Community Extensions powershell module. The module is a central extension module of Powershell. By piping the output of the dir command against the output files and using the cmdlet write-zip and the argument -output following by the file name of the zip file to create, the output is zipped together.

Also make note that tasks can depend upon oter tasks with their -depends flag. To toggle between the debug or release configuration, it is possible to set this by using the -properties flag on the invoke-psake command. To build in release, simply specify this as in the following example:

PS C:\users\Tore Aurstad\Documents\WindowsPowerShell\scripts> invoke-psake -buildfile .\script1.ps1 -task Rebuild -properties @{ 'config' = 'release' } By setting -properties flag and passing in a hashtable where the key 'config' is set to the value 'release', it is possible to build in release mode instead of debug mode. There is also a ValidateConfig task that will check that the provided value is either 'debug' or 'release' or some other value. The assert expression will check that the specified configuration is one of the two valid values. There is a correspondence between the property in the -properties flag and the property declared in the psake script.

Psake can be integrated into TeamCity and you can transform the functionality of the MSBuild Xml file (usually a .proj file) into a psake script. Once this is done, it is much more flexible to make changes since the build script now is inside a Powershell script. Forget creating MSBuild tasks which are compiled, this will instead be other Powershell scripts.

Here is a tutorial on how to integrate psake in Team City:

Psake and Team City

Sunday, 31 March 2013

Searching for text in files with Powershell

This article will present a way of searching for text in files on your hard disk using Powershell. There are of course many ways of achieving this, such as findstr command. I call the function I present here as grep, but this is in no way anything similar to the sophisticated grep tool many users from Unix-based operative systems are familiar with. The output this command gives gives a nice summary of line number and file
function grep($searchPattern, $fileMatch = '*.*', [bool] $ignoreCase = $true){            
             
 if ($ignoreCase){            
  Get-ChildItem -Recurse -Filter $fileMatch | Select-String -Pattern $searchPattern |            
  Format-Table -GroupBy Path -AutoSize            
 }            
 else {            
  Get-ChildItem -Recurse -Filter $fileMatch |             
  Select-String -Pattern $searchPattern -CaseSensitive |            
  Format-Table -GroupBy Path -AutoSize            
 }            
}            
            
grep AsFullName *.cs $true            


The cmdlet above is called grep, but this does not apply to the verb-noun standard of Powershell. A better name could be Search-Files or something similar. The function uses the Get-ChildItem cmdlet with the recurse option to search all files (and folders) recursively from the current working directory and all its subfolders. This is then piped to Select-String and the flag -CaseSensitive is set if $ignorecase is set to $false. As you can see, the default value of the second and third parameter to the function /cmdlet is '*.*' for $fileMatch and $true for $ignoreCase. A function call is done in the code shown above as an example. Make note that only the first parameter is required, the search string. If a case sensitive search is desired, one must specify all three parameters. If it is desired to search through all files, use '*.*'. In my example, I am looking through some source code, and I am only interested in looking at the files with the extension '.cs' (C# source code files).

It is possible to compact the function or cmdlet above by making use of emulating the ternary operator, but the script above is easy should be easy to debug. If $ignoreCase is set to $false, the -CaseSensitive flag is used in the Search-String cmdlet in the script above.

The output of the command is shown below, when the working directory is set to the source code folder where I have cloned the source code of ShellStudio:

PS C:\toaurs-he\StudioShell\studioshell[ default ]> grep AsFullName *.cs


   Path: C:\toaurs-he\StudioShell\studioshell\src\CodeOwls.StudioShell\CodeOwls.StudioShell.Paths\Items\CodeModel\ShellCodeTypeReference.cs

IgnoreCase LineNumber Line                                            Filename                  Path                                                                              
---------- ---------- ----                                            --------                  ----                                                                              
      True         54         public string AsFullName                ShellCodeTypeReference.cs C:\toaurs-he\StudioShell\studioshell\src\CodeOwls.StudioShell\CodeOwls.StudioSh...
      True         56             get { return _typeRef.AsFullName; } ShellCodeTypeReference.cs C:\toaurs-he\StudioShell\studioshell\src\CodeOwls.StudioShell\CodeOwls.StudioSh...