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...

Using Studioshell to prefix booleans with Is

An often used convention used in coding is to prefix boolean properties that are public with the prefix Is. In this article, an automatic refactoring technique will be explained using Powershell. Alternatives could be using Resharper or Roslyn. Since Powershell will tackle this on a file level, it will execute very fast. Using Powershell alone will not be sufficient, since Powershell does not have the needed code analysis capabilities. To tackle this, we will useStudioshell and execute the script presented in this article.
To download Studioshell, visit the following url:
Studioshell codeplex page

As an alternative, download and install StudioShell via NuGet. Open up a new solution in Visual Studio, then choose the menu option Tools, Library Package Manager and Package manager console. Choose your main project or what's best suited and run the command:
Install-Package StudioShell.Beta

When StudioShell is installed, check that the Execution Policy is remote signed. Start Visual Studio as an administrator and run the following cmdlet: Set-ExecutionPolicy RemoteSigned . StudioShell has its own profile file. Let's first create the profile file, if necessary. Run the following cmdlet: New-Item -ItemType File $profile Add the following function to the $profile file, by running the following cmdlet to open up the $profile file in Notepad: Invoke-Item $profile The automatic refactoring function in Powershell with the help of StudioShell then looks like this:

function PrefixWithIs-Booleans(){            
 Set-Location dte:\solution\codemodel            
        dir -recurse | where { $_ -match 'property' -and $_.name -notMatch '^Is' -and $_.access -match 'public' -and             
        $_.type.asfullname -match 'bool' } | foreach { $newname = "Is" + $_.name; $_.renamesymbol($newname); }            
            
}
Of course, this function could be put in a .ps1 or .psm1 file instead of residing in your StudioShell profile file. By putting the function or cmdlet into the $profile file, it will always be available in StudioShell. As you can also see, the cmdlet is named with verb-noun, as is the convention in Powershell. To run this cmdlet, just type: PrefixWithIs-Booleans This cmdlet will automatically rename all public properties that return a boolean and that has not have the prefix Is, and rename this public property to have the prefix Is. This will result that all public properties start with Is. The cmdlet PrefixWithIs-Booleans starts with running the dir -recurse command to recursively look at all files (and folders) in subdirectories. But first the location is changed to dte:\solution\codemodel. Studioshell has a folder structure which is used for the analysis of code of the currently loaded solution. By then looking at all files (and folders) in the folder dte:\solution\codemodel, effectively one is looking at the entire Visual Studio solution. The property of the items one is looking at is CodeOwls.StudioShell.Paths.Items.CodeModel.ShellCodeProperty2. The command dir -recurse is piped to the cmdlet where and the long where condition effectively looks for boolean properties that are public and does not have a name starting with Is. The result set is then furter piped to foreach which will iterate on the current object in Powershell, $_ and then call the renamesymbol method of the current object. The new name is then set to Is + name member of current object. This shows how Studioshell can refactor a Visual Studio solution using the dte: folder structure of Studioshell and then to code manipulation on the Visual Studio. It is important to be careful when using Studioshell, I would suggest using a versioning system such as Mercurial. Studioshell can also automate a multitude of features in Visual Studio besides code manipulation and code analysis. For example, it is possible to save window layout and load up a window layout with a single command from the shell command line of Studioshell inside Visual Studio. It is also possible to toggle on and off all breakpoints and so on. If you find yourself doing repetitive tasks in Visual Studio or changing the code in a repetitive manner, consider using StudioShell for automating these tasks.

Saturday 30 March 2013

Creating a Mercurial-aware Powershell command line

This article will describe how we can create a Mercurial-aware Powershell command line. Mercurial is a (distributed) source control. It supports branch per feature and multiple other nice features that gives powerful control of the source code. To get Mercurial, check out the following url: Mercurial The Mercurial-aware Powershell command line will detect when the user enters a Mercurial repository folder on disk. This will be done via the hg branch command, which will return null if the user is not in a Mercurial folder and the name of the branch if the user is inside a Mercurial repository. Let's look at the needed code first, I put this code in my $profile file, use the cmdlet inside Powershell to open the $profile file: Invoke-Item $profile This will edit the profile file of your Powershell by launching Notepad. The $profile file is not perhaps created yet. If that is the case, enter the following command: new-item -ItemType file -path $profile Now retry the command Invoke-Item $profile if this command failed. If you just type: $profile at the Powershell prompt, you will get the file location of the profile file. Make note that Powershell ISE and Powershell prompt has different profile files.. Before I can show you the script, I must get the script transformed into readable colored HTML right? I type Import-Module PowershellPack, now I can use the nice cmdlet Copy-ColoredHTML. This copies the script as colored HTML into the clipboard. I can then paste the
function get-hgStatus(            
    $status = @('M','A','R','C','!','?','I')            
){            
    hg status --all |            
        where { $_ -match "^\s*[$Status]" } |            
        foreach { $_ -replace "\s+", ',' } |            
        ConvertFrom-Csv -Header Status, Path             
            
}            
            
function prompt {            
             
 $branch = (hg branch);            
 $status = get-hgStatus | Group-Object Status;            
            
 $modified = $status | where { $_.name -eq 'M' } | select -expand count;             
 $added = $status | where { $_.name -eq 'A' } | select -expand count;             
 $removed = $status | where { $_.name -eq 'R' } | select -expand count;             
 $untracked = $status | where { $_.name -eq '?' } | select -expand count;             
 $missing = $status | where { $_.name -eq '!' } | select -expand count;             
            
 Write-Host "PS $pwd" -NoNewline            
            
 if ($branch){            
              
  write-host "[" -NoNewline            
  write-host " $branch " -ForegroundColor White -NoNewline            
              
  if ($added){            
    write-host " " -NoNewline            
    write-host "+$added" -ForegroundColor green -NoNewline;            
  }            
            
  if ($modified){            
    write-host " " -NoNewline            
    write-host $modified -ForegroundColor yellow -NoNewline;            
  }            
            
  if ($removed){            
    write-host " " -NoNewline            
    write-host "-$removed" -ForegroundColor magenta -NoNewline;            
  }            
            
  if ($missing){            
    write-host " " -NoNewline            
    write-host "!$missing" -ForegroundColor red -NoNewline;            
  }            
            
  if ($untracked){            
    write-host " " -NoNewline            
    write-host "?$untracked" -ForegroundColor gray -NoNewline;            
  }               
            
  write-host "]" -NoNewline;            
            
 }            
            
 "> "            
            
}            
Note the use of a default value in the passed in $status variable, which is default set to an array of all the possible hg status flags. The hg-GetStatus function can be called individually like hg-GetStatus A to show all the added files in the repo. The script above uses the hg status command to get the status of the current hg repository in the folder. If the current folder is not a hg repository, a default prompt is shown instead. The output of the command hg status is converted into a structured object and then grouped by status using the Group-Object cmdlet. A pipeline is used to prepare the output of the hg status before it is transformed into the structured object. The count of each group is retrieved with the Select cmdlet, and using the -extract flag. The group counts are then shown to the user in the prompt and formatted with color. Make note of the use of -NoNewLine and -ForegroundColor. In addition, the Powershell function prompt will control how your prompt in the shell will look like, i.e. the command line. I put this prompt in the $profile file such that this is inited each time.

This is just an introduction to customizing the Powershell command line to a more suited hg aware prompt for developers using hg. There is actually already a better package available on the Internet for displaying the state of the hg repository in the folder displayed in Powershell, which is called hg posh. Check out hg posh on the following url:
hg posh


However, although hg posh is more correct, I like the look of this command prompt better ... For many hg users, this will suffice.. And here is our nice new Mercurial aware command line: