Monthly Archives: August 2014

Powershell -match operator

I think Powershell’s -match operator deserves its own blog post because it does so much. I’ve been automating our build process using Powershell and I gained a lot of respect for this operator. The examples below, would usually refer to the SVN url parsing.

Matching

Firstly, it returns true ($true in Powershell) if the original string matches the regex expression you passed into it. For example,

$string = "Hello"
$string -match "Hello" #return true

$string -cmatch "hello" #return false, cmatch performs case-sensitive match

$string -cmatch "[H|h]ello" #return true  

Group Matching

In addition to regular matching the -match operator will populate a special $matches variable. For example

$string = "Hello World"
$string -match "Hello"

$matches[0] #prints "Hello"

$string = "Hello World"
$string -match "(?'first'Hello)"

Name    Value                                                                                                 
----    -----                                                                                                 
first   Hello                                                                                                 
0       Hello 

Group Matching and Replacing

If you are crafty enough, you can come up with a match and replace strategy for the strings you are working with. For example, if you have to figure out the full path of the svn path if you know the name of the tag, given the current branch path, you’ll do the following:


$url = "http://subversion.url.com/project/branches/VERSION-1.0/path/to/code"
$search = "(.*)(?'branch'branches/[^/]*)(.*)" #Generates 3 groups (1,2, and branch)

$url -match $search
True

$matches

Name     Value                                                                                                 
----     -----                                                                                                 
branch   branches/VERSION-1.0                                                                                  
2        /path/to/code                                                                                         
1        http://subversion.url.com/project/                                                                    
0        http://subversion.url.com/project/branches/VERSION-1.0/path/to/code           

#use the replace method of the Match object to replace branche path with tag path:
$newUrl = $matches[0].Replace($matches['branch'], 'tags/TAG-123') #replaces branch path with new tag path

$newUrl
http://subversion.url.com/project/tags/TAG-123/path/to/code

Automate Visual Studio project management from Package Manager Console

Visual Studio 2012 and above comes with the Package Manager Console, PowerShell-based tool used to automate NuGet package management from the command line.  But you can use it to access and change solutions and projects loaded into your Visual Studio using the DTE API, same interface Visual Studio exposes to its Addins.

In this post, I will demonstrate how you can replace all references to binaries (dll or exe files) with references to projects loaded into your solution. Such operation is necessary when you need to debug the code inside the dependencies. This use case is a frequent occurrence in development shops that split their code base into core, more technie, low-level functionality and higher level business-level code. The lower-level code is encapsulated into one or more dlls and the higher-level developers reference those dlls when developing their client-facing solutions.

Usually, the lower-level code has been well designed, developed and properly unit tested to make them work as expected. But sometimes something is not right, and it’s up to the app developer, who is on the deadline to figure out why. So the app developer would check out the code for the lower-level API and would load the lower-level API  solution into their existing higher-level app solution. You can do this by

  1. right-clicking on the existing loaded solution
  2. picking Add -> Existing Project,
  3. changing the dropdown in the file picker to Solution Files (*.sln)
  4. picking the dependency solution in the dialog

The next step, would be for developers to manually replace references in your higher-level projects, removing references to the binary dll or exe files and pointing them to their equivalent projects that are now part of your solution.  This operation is tedious and error prone and is a perfect candidate for Powershell automation.   Let’s get started.

Before we begin, however, lets make it easier to separate the two sets of projects into the lower-level projects and the higher level projects.  The best way to segregate inside a single solution is to use solution folders.  So let’s create a folder called “Dependencies” and drag all the lower-level API projects under it.  You can create a solution folder by right-clicking on your solution and picking Add -> New Solution Folder.  We are ready now!

Switch to or open the Package Manager Console.  In VS2012  you can open it from TOOLS -> NuGet Package Manger -> Package Manager Console.  You should now be in the command prompt.  Here’s a script to perform such replacements.

function Update-References() 
{
	#find the solution folder called "Dependencies" where the dependency projects live
	$depFolder = $dte.Solution.Projects | where { $_.Kind -eq "{66A26720-8FB5-11D2-AA7E-00C04F688DDE}" -and $_.Name -eq 'Dependencies' }
	if(-not $depFolder)
	{
		Write-Error "Did not find a Dependencies folder in your solution.  It must be a top-level folder";
		return -1
	}

	#Load a list of all the dependency projects into a hashtable
	$depProjects = @{}
	$depFolder.ProjectItems | % { $_.Object }  | % { $depProjects.add($_.Name, $_) }

	#load a list of all high-level projects (my projects)
	$myProjects = $dte.Solution.Projects | where { $_.Kind -eq '{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}' }

	#iterate through references in my projects and remember those that match the dependency projects.  Once matched, remove the old reference
	$projectToRefName = @{}
	foreach ($project in $myProjects.Object) 
	{ 	
		$lst = new-object system.collections.arraylist
		$projectToRefName.Add($project, $lst)
		foreach($reference in $project.References  | where { $depProjects.Contains($_.Identity) } )	 
		{ 
			$lst.add($reference.Identity)
			$reference.Remove() 
		}
	}

	#replace add a project reference for all the binary references we removed
	foreach ($project in $myProjects.Object) 
	{ 	
		$lst = $projectToRefName[$project]
        $depProjects.Values | where { $lst.Contains($_.Name) }  | % { $project.References.AddProject($_) } 
	}
}

Export-ModuleMember Update-References 

You can save this script into a script module file (*.psm1) somewhere and then use Import-Module command to load it. The Import-Module command takes the full path to the psm1 file like this:

Import-Module C:\MyPowershellModules\UpdateReferences.psm1

#if you need to make a change to the file, you will need to remove module first, because importing it again
Remove-Module UpdateReferences