Encrypt App.config section using PowerShell as a Post-build event

It is very easy to encrypt a section of the Web.config file using Aspnet_regiis.exe tool, but there is no equivalent tool to encrypt a section of an application configuration file (App.config). It can be done very easily in code, as explained in this post, but there is now way to do that automatically. So I decided to write a PowerShell script that would encode a section of the given application’s configuration file.

Here is what it looks like:

param(
  [String] $appPath = $(throw "Application exe file path is mandatory"),
  [String] $sectionName = $(throw "Configuration section is mandatory"),
  [String] $dataProtectionProvider = "DataProtectionConfigurationProvider"
)
 
#The System.Configuration assembly must be loaded
$configurationAssembly = "System.Configuration, Version=2.0.0.0, Culture=Neutral, PublicKeyToken=b03f5f7f11d50a3a"
[void] [Reflection.Assembly]::Load($configurationAssembly)
 
Write-Host "Encrypting configuration section..."
 
$configuration = [System.Configuration.ConfigurationManager]::OpenExeConfiguration($appPath)
$section = $configuration.GetSection($sectionName)
 
if (-not $section.SectionInformation.IsProtected)
{
  $section.SectionInformation.ProtectSection($dataProtectionProvider);
  $section.SectionInformation.ForceSave = [System.Boolean]::True;
  $configuration.Save([System.Configuration.ConfigurationSaveMode]::Modified);
}
 
Write-Host "Succeeded!"

Some explanation on the script:

Believe it or not, that was the easy part.

The next step is to run this script automatically as a Post-build event in Visual Studio, so the .config file that is "built" is encrypted. Sounds very easy, but is actually tricky, I had to try many times to get it right.

I will give it straight away, here is the command to put in the Post-build event:

powershell "& ""C:\Documents and Settings\VlericP\My Documents\WindowsPowerShell\EncryptAppConfigSection.ps1""" '$(TargetPath)' 'connectionStrings'

There are lot of quotes, but this is the only way to get it working. For detailed explanations on why, see this post: Invoking a PowerShell script from cmd.exe (or Start | Run).

Using this, you can have your App.config file unencrypted in your solution, and when you build the output .config file is encrypted.

Message.ToString() returning "…stream…"

A month ago, I wrote an post on how to send an XmlDocument trough Windows Communication Foundation. At the end of the post, I noted that the ToString method on the Message object might return "…stream…" instead of the SOAP content.

A recent development needed to have the full message content in a string, and we hit the "…stream…" issue again. The development was a message inspector which was to be used on various BizTalk WCF Adapters. The inspector had to work on service side and on client side, implementing AfterReceiveRequest of IDispatchMessageInspector and BeforeSendRequest of IClientMessageInspector. Strangely, Message.ToString returned the full message on the server side adapter, but returned "…stream…" in client side adapter.

Searching on that matter on Google didn’t provided me with lots of results, but I found a post with a solution to that particular issue: "Accessing the message inside of your WCF service operations". So, it seems that this is due to the fact that the Message object is streamed (yeah I know it’s quite obvious…) on the client side.

So, to get the message’s content as a string, here is what is needed:

StringWriter stringWriter = new StringWriter();
XmlTextWriter xmlTextWriter = new XmlTextWriter(stringWriter);
message.WriteMessage(xmlTextWriter);
xmlTextWriter.Flush();
xmlTextWriter.Close();

String messageContent = stringWriter.ToString();

However, there is an issue with that code in a message inspector. Using it like this will throw some exception about the message already been written. According to the documentation, a Message object can only be read or written once:

As the body of a Message object is a stream, it can only be read or written once. This is enforced by maintaining the current state of the Message object. A Message object can be read/written/copied when in the Created state. Other states are Read, Written and Copied, which means that the respective action has been performed already once.

So, when calling Message.WriteMessage to write the message in the XmlTextWriter, the state of the message changes from "Created" to "Written". This operation is irreversible, and the message is pretty useless afterward.

To avoid this, we can make use of the MessageBuffer class. This can be used to make a copy of a message in a buffer, then you can use the object created to create as many Messages as you want. Here is how to use it:

MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
Message message = buffer.CreateMessage();

After that, the message variable holds a reference to a copy of the initial request, and the buffer object can be used to produce copies of the initial Message.

There is one last trick. When creating a MessageBuffer out of the request, we actually read the request Message object, changing its state to Read and making it unusable afterward (in this case, the application!). Notice that in IDispatchMessageInspector and IClientMessageInspector methods, Messages parameters are always given using the ref keyword, meaning that the reference can be modified.

Solution to that last issue is to make another copy of the initial request and assign it to the variable. Here’s how to do it:

MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
Message message = buffer.CreateMessage();
//Assign a copy to the ref received
request = buffer.CreateMessage();

Now the request variable hold a reference to a fresh Message, and the message variable holds a reference to another copy of the Message object that you can mess with in your inspector.

PowerShell Script to Download a List of Files

Lately I have been playing around with PowerShell. I decided that I will write scripts in order to perform some simple actions, actions that could be scripted generally are not due to the fact that writing the script takes longer than manually doing it.

So here is the first task I would like to automate: sequentially download each file in a list, the list being provided as a text file.

Download a file using PowerShell

For this part, I simply "stole" the code from somewhere else: http://community.bartdesmet.net/blogs/bart/archive/2006/11/25/PowerShell-_2D00_-How-to-download-a-file_3F00_.aspx, plain simple, I don’t think I’ll be able to do better.

Read a text file in an array

I was expecting this to be a bit more complicated, but it turns out to be piece of cake:

$array = Get-Content TextFile.txt

Not too much trouble.

Putting it all together

So now we need to do make these things work together. First, read the content of the file (given as parameter) in an array, then for each item in the array get the client to download it.

Now, there is one little trick here. The WebClient.DownloadFile method’s second argument is the local file. In this case, we want it to have the same file name as the source file name. Leveraging the .NET framework, System.IO.Path is the way to go.

$list = Get-Content $args
 
$clnt = New-Object System.Net.WebClient
 
foreach($url in $list) 
{ 
 
	#Get the filename 
	$filename = [System.IO.Path]::GetFileName($url) 
 
	#Create the output path 
	$file = [System.IO.Path]::Combine($pwd.Path, $filename) 
 
	Write-Host -NoNewline "Getting ""$url""... "
 
	#Download the file using the WebClient 
	$clnt.DownloadFile($url, $file) 
 
	Write-Host "done." 
}

Two remarks on of the Path class:

The script is not error proof, but it good enough for me now.

Edit: I added some Host-Write lines in order to display something in the shell. When downloading a long list of files, it is nice to see the progress.

PowerShell Prompt Customization

I installed PowerShell a few month ago, but never really used it since then. Last week, I read some articles about it and decided to start using it to see what it was worth. Even if it will probably not change my life as a developer, I can say that so far it has been quite interesting.

I’ve been using the Unix/Linux shell for some years now, and from time to time I miss it under Windows. The cmd.exe prompt is really aging and has countless limitations. It get very hard to do some things that would be really easy with a Unix prompt. Enters PowerShell, a command line utility that leverages the .NET platform. But I digress, my point is not to teach what PowerShell is nor to write an article on how to use it, there are already tons of tutorials online that will fulfil that need better than I can possibly do.

When you install PowerShell, you get some "Windows PowerShell" content added to the start menu, and when you open it you get a nice prompt window that opens up, starting in your home directory (note that the size of the window is bigger, I resized it to fit):

PowerShellDefault

Now let’s say that you use Total Commander or any tool of that kind, or that you install PowerShell Prompt Here tool. Using one of these, you open a PowerShell prompt somewhere. Well, you are back with the default cmd.exe size window, which is too small for any nowadays screen. If you want to a nice window like the one you get when starting PowerShell from the start menu, you’ll have to modify your PowerShell profile.

Modify Your Profile

When PowerShell loads, it reads a profile for that is located at $profile variable value. Simply type $profile in the PowerShell window to see where it is (or should be) located. If it does not exist, you can create it.

So, in order to get a nice window, you have to modify some properties of the  object

$a = (Get-Host).UI.RawUI 
 
$a.ForegroundColor = "Gray" 
$a.BackgroundColor = "Black" 
 
$a.WindowTitle = "Windows PowerShell"</p>
 
$b = $a.BufferSize 
$b.Width = 120 
$b.Height = 3000 
$a.BufferSize = $b
 
$b = $a.WindowSize 
$b.Width = 120 
$b.Height = 50 
$a.WindowSize = $b

It’s pretty straightforward.

Modify

The Prompt

I was not very satisfied with the prompt (showing the PWD, present working directory). In order to modify it, you simply have to define a function called "prompt" that returns a String. I found a nice prompt script there, but was not satisfied with it as it hides the drive. I modified it a bit in order to show the drive’s letter no matter how long the PWD is.

function prompt 
{ 
	$m = 30 # maximum prompt length 
	$d = 3 #size of the drive prompt to keep 
	$str = $pwd.Path 
 
	if ($str.length -ge $m + $d) 
	{ 
		# The prompt will begin with "...", 
		# end with ">", and in between contain 
 		# as many of the path characters as will fit, 
		# reading from the end of the path. 
		$str = $str.substring(0, 3) + "(...)" + $str.substring($str.length - ($m + $d) + 4) 
	} 
 
	"$str > "; 
 
}

Now, this is what I wanted, but using it was not satisfactory for some reason. So I figured out that what I wanted was a prompt showing the command number, and the window displaying the PWD in the title.

function prompt 
{ 
	$str = $pwd.Path 
	$nextId = (h -count 1).Id + 1; 
 
	$Host.Ui.Rawui.WindowTitle = $str + " - Windows PowerShell" 
 
	"PS (" + $nextId + ") > "; 
}

So here it is, the prompt exactly as I wanted it.