Mar 30 2010

Using Powershell to apply new C# field naming conventions

Category: PowerShellMatt @ 02:34

I recently found myself wanting to rename all the fields in several large projects to conform to a new naming scheme.  Sadly Resharper was not helpful here, so I turned to a combination of Regex and Powershell. 

Our team has recently revised our 7 year-old coding guidelines.  One of the guidelines that was changed concerned field naming.  Our previous guideline stated that fields should use an ‘m’ prefix with Pascal Casing, creating fields of the form “mFieldName”.  At the time the guidelines were written, .NET was still young, and the community hadn’t yet settled on a widely accepted standard.  Since then, most of the community seems to have gravitated towards using an underscore prefix for fields with camel casing, creating fields of the form “_fieldName”.  We finally decided to bite the bullet and switch our guideline to conform to the more generally accepted convention.  The problem is that we have a lot of code that uses the old style.  By “a lot”, I mean probably in the neighborhood of 100k lines of code across the various projects. 

One solution we discussed was to just leave existing fields alone, and only use the new convention when creating new code.  This would work, but since we use Resharper to enforce the naming conventions, it would create a lot of false warnings.  It also meant that we would be introducing some inconsistency into our code base, which we wanted to avoid.

One avenue that we explored was using Resharper to update all existing field names.  The Patterns functionality introduced in Resharper 5.0 might be able to accomplish this, but I was unable to figure out how. 

When that failed, James suggested that I try using Regex.  It took a bit of Powershell hacking, but I managed to come up with a script that worked.  I’ve applied it across thousands of classes, and it *appears* to rename things correctly.  Here’s the script:

   1: $regex = New-Object System.Text.RegularExpressions.Regex "(?:\W(?<name>m[A-Z][a-zA-Z0-9_]+))"
   2: Get-ChildItem * -Recurse -Include *.cs | ? {$_ -notmatch 'Resharper' } | ? {$_ -notmatch 'DecompilerCache' } | ForEach-Object { 
   3:     $text = [System.IO.File]::ReadAllText($_.FullName)
   4:     $matches = $regex.Matches($text)
   5:     $matches | ForEach-Object { 
   6:         $toReplace = $_.Groups["name"].Value 
   7:         $newValue = "_" + [System.Char]::ToLower($toReplace[1]) + $toReplace.Substring(2)
   8:         $text = [System.Text.RegularExpressions.Regex]::Replace($text, "(?:(?<prefix>\W)" + $toReplace + ")", "`${prefix}$newValue")
   9:     } 
  10:     [System.IO.File]::WriteAllText($_.FullName, $text)
  11: }

The first line creates a new Regex object that will match things of the form “mFieldName” that are prefixed by a non-word character.  Next, the script recursively iterates through C# files, ignoring the contents of the Resharper directory as well as the DecompiledSources directory.  For each matched file, the script grabs the full text from the file, finds the field names that need to be changed (if any), then uses a different regex to replace the field name in the text.  Note that simple string.Replace won’t work here since the replacement must account for the non-word character that prefixes the filed name, otherwise things such as “NumWhatever” will be transformed as well (as I found out with an earlier version of this script).  Finally, the modified text is written back to the file.

It’s not terribly pretty, and it certainly isn’t innovative, but it did save me quite a bit of time this morning. :)

Tags:

Feb 1 2010

The trials and tribulations of using MSDeploy with PowerShell

Category: Deployment | PowerShell | MSDeployMatt @ 09:21

I can sum my experience with trying to use MSDeploy and Powershell together with a single word: hell.  MSDeploy.exe does not play nicely with PowerShell, but thanks to some help from James and a lot of trial-and-error, I’ve got it sort-of working now.  Here’s a tail of my journey.  Hopefully you, brave reader, will learn from my mistakes.  THAR BE DRAGONS HERE.

It began with the automation of deployments

This journey started from a desire to automate our deployment processes.  We have three different software applications that we host under a SaaS model, and only one of those has a deployment that even remotely qualifies as semi-automated.  Making deployment less painful typically leads to more frequent deployments and happier developers, so I set out to automate things.  I have previously used MSBuild and PowerShell to automate things, but that was several years ago, and the tools have changed.  Today, we have a new tool to help with deploying web applications: the Microsoft Web Deployment Tool, or MSDeploy. FTA:

The Web Deployment Tool simplifies the migration, management and deployment of IIS Web servers, Web applications and Web sites.

Basically, MSDeploy gives us some great functionality for remote deployments.  With it, I can deploy files, execute commands remotely, run SQL scripts, etc.  On paper, this seemed like a great tool.  Using it, I should be able to deploy applications without having to log on to the target servers at all

So, with MSDeploy installed and ready, I decided I would use PowerShell to drive things instead of MSBuild or batch scripting.  After all, PowerShell is the future of the Windows Shell. And MSDeploy is new hawtness.  So the two will surely play nice together, right?  Right?

You seek the 2.0 shell?

Before starting, I decided I would install PowerShell 2.0.  It was required for one of the tools I was using (which I’ll cover in a future post).  I was running Vista with PowerShell 1.0 installed.  So I hit up Google to find the 2.0 release:

PowerShellGoogle

Hmm, none of the top results looked like a download page for the final version.  I didn’t want a CTP, I wanted the final version.  Bing was not much better:

PowerShellBing

Bing returned a blog post indicating that the RTW version shipped, but no link in the first few results (yes, I am one of those searchers who only scans the first few hits before abandoning my query and trying a different one).  I finally found the PowerShell portal on TechNet, which did include a link to download PowerShell 2.0.  PS2 is part of the Windows Management Framework, which is intuitively* disguised as KB968929. Suggestion to Microsoft: how about making it a little less difficult to find PowerShell?

*SarcasmLevel = int.MaxValue;

There can be no alliance between PS and MSDeploy

Despite the initial frustration of finding and installing the latest PowerShell, I had high hopes that things were going to go smoother now.  I had numerous examples of how to use MSDeploy.exe from the command-line, and the documentation actually seemed pretty solid.  You can imagine my surprise when I tried running a simple example and was greeted by the following:

powershellFail

Note: The comments are mine, not MSDeploy output.  There is no MSDeploy output.  Nothing.  At all.  Until I forcibly end msdeploy, I can type whatever I want, and it just sits there quietly.

I’m not a PowerShell guru, but I don’t think that is normal behavior.  I found a handful of blog and message forum posts scattered across the web reporting similar behavior when using MSDeploy with PowerShell.  After reading the IIS blog, I started to suspect that using MSDeploy with PowerShell, at least through the command line, was actually not supportedThis really dashed my hopes:

And let us know if you want to use Web Deploy with PowerShell, we're interested in hearing about what tasks you'd like to accomplish and why!

I bet the use cases for MSDeploy with PowerShell are the same as using MSDeploy from the old CMD shell.  I’m really surprised that PowerShell is apparently not fully supported right out of the box.  I think Microsoft did realize that people might want to use the tools together. Why else would they have had PowerShell cmdlets in the early preview releases?  Unfortunately, these cmdlets were removed from the final version (see the last comment).

More digging around led me to some actual working examples of using MSDeploy with PowerShell, but it seemed a lot less elegant than running msdeploy.exe. 

An old friend comes to the rescue!

Despite this setback, I wasn’t ready to give up yet.  My initial solution was to wrap msdeploy.exe in a (very) simple batch script that I could invoke from PowerShell, but James gave me a better solution: invoke msdeploy.exe through cmd.exe directly!  I created a function to wrap the invocation, like so:

function PushToTarget([string]$server, [string]$remotePath, [string]$localPath) {
    cmd.exe /C $("msdeploy.exe -verb:sync -source:contentPath=`"{0}`" -dest:computerName=`"{1}`",contentPath=`"{2}`" -whatif" -f $localPath, $server, $remotePath )
}

Now I can use MSDeploy to sync files to my production servers.  In a future post, I’ll show you how capability fits in to our larger deployment process.

Tags:

Jul 29 2009

Merging and minimizing JavaScript files with YUI Compressor and PowerShell

Category: PowerShell | JavaScriptMatt @ 07:26

One of the tenants of liteGrid is that it’s modular, with the core doing as little as possible, and all the richness being layered on top by various pluggable modules.  These modules are currently spread across multiple files (one per module).  As liteGrid gets closer to “production,” it became time to merge things into a single file (no one wants to include 17 JS files).  I also wanted to minify/minimize the scripts to insure faster downloads.  The YUI Compressor is a good command-line tool for compressing JavaScript and CSS, and it turns out that it can actually be used to merge files, too.  I wrote a simple one-line PowerShell script that does all the magic.  Get ready for it… here it is:

gc *.js | java -jar yuicompressor-2.4.2.jar --type js -o liteGrid.min.js

The script assumes that “java” is in your path and that yuicompressor is in the current directory.  Enjoy!

I’ll get back to writing longer (better?) blog posts next week, this week is filled with deadlines and other not-fun stuff.

Update: Rookie mistake: you want to be sure you delete the minimized file before regenerating it, otherwise it’s old contents will be pulled in as well (not good).

Tags:

Dec 5 2008

Setting Directory Permissions with Windows PowerShell

Category: PowerShellMatt @ 09:41

In my on-going attempts to simplify the deployment of our web application here at Day Job, I have created a PowerShell script that sets the permissions on all the directories used by the app.  It's amazing how much simpler something like this is in PowerShell compared to VBScript.   Here's some code that will give the NetworkService user read-access to a directory (useful for data files that your web app needs to access):

   1: #These constants are used to set permissions
   2: $inherit = [system.security.accesscontrol.InheritanceFlags]"ContainerInherit, ObjectInherit"
   3: $propagation = [system.security.accesscontrol.PropagationFlags]"None"
   4:  
   5: #Set directory permissions
   6: $directory = "DataFiles"
   7: $acl = Get-Acl $directory
   8: $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("NetworkService", "ReadAndExecute", $inherit, $propagation, "Allow")
   9: $acl.AddAccessRule($accessRule)
  10: Set-Acl -aclobject $acl $directory

You need to pass in enumeration members to insure that the permission change is applied correctly, so the first block of code just grabs the needed values from the appropriate enumerations.  With those in hand, all you have to do is use the FileSystemAccessRule class to add a new rule, then apply it to the directory. 

One tricky thing I ran into was handling the Internet Guest Account.  Recall from my WiX ordeals that IIS 7.0 systems (Vista and Windows 2008) use IIS_USRS while IIS 6 systems use IUSR_[MachineName].  I couldn't come up with an elegant way to handle it, but this hackery does the trick:

   1: $computerName = (Get-WmiObject win32_computersystem).name
   2: #This determines which user is the guest user for IIS.  Windows Vista and 08 use the IIS_USRS group, Previous version use 
   3: #IUSR_[MachineName]
   4: if ([environment]::osversion.Version.Major -eq 6) {$webUser="IIS_IUSRS"} else {$webUser="IUSR_" + $computerName}
   5:  
   6: $directory = "Web"
   7: $acl = Get-Acl $directory
   8: $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("NetworkService", "ReadAndExecute", $inherit, $propagation, "Allow")
   9: $acl.AddAccessRule($accessRule)
  10: $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($webUser, "ReadAndExecute", $inherit, $propagation, "Allow")
  11: $acl.AddAccessRule($accessRule)
  12: Set-Acl -aclobject $acl $directory

The above snippet Grants the NetworkService and IIS guest account read permissions on the Web directory, which will allow our ASP.NET located in Web to execute once the virtual directory is created in IIS (which I'll describe how to do just as soon as I figure it out).

Tags: