PowerShell
Pruning Elmah Files with Powershell
Jun 22nd
We use (and love) Elmah. Due to many reasons we decided to opt for the XML file logging to AppData. This works great and is super simple to set up. The only issue is that on our test and production servers we were filling up the AppData directory and after awhile Elmah.axd would take minutes to load.
I wrote a Powershell script that we could put on a timer (Windows Schedule Task) that would delete all but the last 100 error logs. This is a pretty brute force script, but simple. Unfortunately due to some internal regulations we were not able to use the script so I’m sharing it here.
How to Use
Download the source below and save it as “prune.ps1″. Run it like this:
#> .\prune.ps1 -prunedirectory “c:\path\to\elmahlogs\”
The script will keep the 100 most recent. Do you want more kept around? No problem, just change the “$numberOfFilesToKeep” variable.
Enjoy -
param
(
[string] $prunedirectory = $(throw "Parameter - 'prunedirectory' is required.")
)
$numberOfFilesToKeep = 100
$files = get-childitem $prunedirectory*.xml | sort-object -property CreationTime -descending
for($i = $numberOfFilesToKeep; $i -lt $files.count; $i++) { rm -force $files[$i].fullname }
PowerShell TFS WorkSpace Initialization Script
Jan 22nd
I needed a way to set up developer machines quick for access to a new Team Foundation Server rollout awhile back. Normally to set up a workspace on each dev’s machine I’d have to do the following:
A. Teach everyone how to set up workspaces
B. Show them how to get latest
C. Hope they don’t accidentally (or purposely) mess it up.
“C” is my main problem. 80% of users mess it up the first time. I cant blame them, TFS is a paradigm shift and it takes some getting used to. Just like going from VSS to SVN, it takes some getting used to.
To mitigate the risk of going well over my estimated time for installation of the system across the teams I decided to write a PowerShell script to do it for me. However, it is, well clunky. I’ve since improved it to the version below … however, it still has is "clunkiness" to it. But the best part about it is… IT WORKS. So fee free to use it to set up your TFS environments.
Prereq’s to get the script to run
- Users must have .NET 3.5 installed.
- Users must have Visual Studio 2008 installed
- Users must have the Team Foundation Client Installed
- Users must have been added to the proper security groups in TFS (if you’ve set up TFS already this should be easily accomplished through AD and groups).
- Users must have PowerShell (sometimes this is a sticking point with clients, but IMHO I truly believe EVERY developer should have PowerShell – more on this later)
- PowerShell must have the execution policy set to RemoteSigned/AllSigned (if you plan to sign this script, which I have not) or you can set it to Unrestricted (which is what I prefer, just be safe in what you run – read the script before you run it). More on execution policies here.
After that is taken care of, have the user can download this script, run it, and their workspace will be mapped and files will be downloaded. Note, this script may take awhile to run because it downloads all workspace files to your machine.
# Script that will:
# 1. Create a workspace. Workspacce Name: <ComputerName>_Root
# 2. Get the latest code from repository
$tfsServer = "TFSRTM08"
$userName = [system.environment]::UserName;
$computerName = [system.environment]::machinename
$workspaceName = $computerName + "_" + $userName +"_WS" #Use 'WS' as an acronym for "WorkSpace"
$folderName = "C:\TFS";
# Set up the TF Alias</pre>
# Find where VS is installed.
$key = Get-ItemProperty HKLM:\SOFTWARE\Microsoft\VisualStudio\9.0
$dir = [string] (Get-ItemProperty $key.InstallDir)
Set-Alias tf "$dir\tf.exe"
Write-Progress -Activity "Initializing Tree" -Status "Percentage Complete" -PercentComplete(10)
# Create the folder
echo "Creating folder: $folderName"
new-item -itemtype directory -path $folderName -force
echo "Completed Creating folder: $folderName"
[system.environment]::newline
Write-Progress -Activity "Initializing Tree" -Status "Percentage Complete" -PercentComplete(20)
# Move to folder
echo "Navigating to $folderName..."
cd $folderName
echo "Arrived at $folderName"
[system.environment]::newline
Write-Progress -Activity "Initializing Tree" -Status "Percentage Complete" -PercentComplete(30)
# Delete the workspace if it exists.
echo "Deleting workspace (if exists): $workspaceName"
$expr = "tf workspace /delete " + $workspaceName + " /noprompt"
$output = invoke-expression $expr
echo "Done deleting workspace."
[system.environment]::newline
Write-Progress -Activity "Initializing Tree" -Status "Percentage Complete" -PercentComplete(40)
# Create the workspace
echo "Creating workspace: $workspaceName"
$expr = [system.string]::Format("tf workspace /new /computer:{0} /server:{1} /noprompt {2}", $computerName, $tfsServer, $workspaceName)
$output = invoke-expression $expr
echo "Done Creating workspace: $workspaceName"
[system.environment]::newline
Write-Progress -Activity "Downloading Entire Source Tree" -Status "% Complete - this bar will not move for awhile. This process takes awhile." -PercentComplete(50)
# Get the latest
echo "Getting the latest code from: $tfsServer. This could take awhile..."
$expr = "tf get";
$output = invoke-expression $expr
echo "Done getting latest."
Write-Progress -Activity "Initializing Tree" -Status "Percentage Complete" -PercentComplete(100)
echo "Tree initialization is complete."
You can copy the above into a InitializeScript.ps1 file and then run it from any machine that has the pre-req’s installed. Enjoy.
Loading PowerShell Profiles from Other Script Files
Oct 29th
PowerShell profiles are used for loading common scripts, add-in’s, functions, etc into the PowerShell session at startup. There are four different locations where profiles are loaded from:
|
You can have four different profiles in Windows PowerShell. The profiles are listed in load order. The most specific profiles have precedence over less specific profiles where they apply.
|
The Problem
This works out just fine if you want to write your own profile.ps1 file and load it from one of those areas. For example, you could perform a Set-Alias for MSBuild if you wanted to have “msbuild” run the msbuild file of your choice (2.0, 3.5, etc). But what happens when you want to load an external function that is located in another script? For example, lets say I have a script called “Convert-Xml.ps1″ on my disk – as shown below (script source):
function Convert-WithXslt($originalXmlFilePath, $xslFilePath, $outputFilePath)
{
## Simplistic error handling
$xslFilePath = resolve-path $xslFilePath
if( -not (test-path $xslFilePath) ) { throw “Can’t find the XSL file” }
$originalXmlFilePath = resolve-path $originalXmlFilePath
if( -not (test-path $originalXmlFilePath) ) { throw “Can’t find the XML file” }
$outputFilePath = resolve-path $outputFilePath
if( -not (test-path (split-path $originalXmlFilePath)) ) { throw “Can’t find the output folder” }## Get an XSL Transform object (try for the new .Net 3.5 version first)
$EAP = $ErrorActionPreference
$ErrorActionPreference = “SilentlyContinue”
$script:xslt = new-object system.xml.xsl.xslcompiledtransfrm
trap [System.Management.Automation.PSArgumentException]
{ # no 3.5, use the slower 2.0 one
$ErrorActionPreference = $EAP
$script:xslt = new-object system.xml.xsl.xsltransform
}
$ErrorActionPreference = $EAP
## load xslt file
$xslt.load( $xslFilePath )
## transform
$xslt.Transform( $originalXmlFilePath, $outputFilePath )
}
The end result is that when I start PowerShell I want to load this function inside of my profile when PowerShell starts up. If that happened I would be able to start PowerShell and type Convert-WithXslt and get the function to work no problem. How do we load an external script into the current session via the profile.ps1 script? Hmm… not so simple at first.
How To Load an External Script Into Your Profile
In your profile.ps1 file, you can script it like this (assuming that your profile.ps1 is in one of the locations above):
# Loads the script into the profile.
$fileContents = [string]::join([environment]::newline, (get-content -path C:\PowerShellScripts\Convert-Xml.ps1))
invoke-expression $fileContents
This will load the script (C:\PowerShellScripts\Convert-Xml.ps1) contents into your profile. Now the function Convert-WithXslt will be available from the shell.
Script Explained
- (get-content -path C:\PowerShellScripts\Convert-Xml.ps1) – This opens the script, reads the contents and then returns the entire file. The ( ) are necessary because get-content returns an array of lines of text. ( ) force full evaluation of the expression. Therefore the full array is returned. Each item in the array is a line of text.
- [string]::join([environment]::newline, (get-content -path C:\PowerShellScripts\Convert-Xml.ps1)) – In this script we take the array as shown above, and join it on the new line character. Therefore we have a full string of the file contents.
- $fileContents = … – Now we just set it to a local variable.
- invoke-expression $fileContents – This runs the Invoke-expression cmdlet. In a nutshell this command takes the value of whats in $fileContents and runs it in the local session. Therefore, if we have a function declaration inside of that variable, we can then invoke the shell to evaluate that function declaration which in turn will then add that function to the shell’s session. Now your profile can execute whatever is in this script. Functions, aliases, etc.
Taking it a Step Further
What if you want to have a common set of scripts that everyone on your team should be using? You wont want to have those scattered over 25 PCs. What if you have to update one of them? You’ll have to update 25 machines. No good.
So here’s a simple script that will go out to a network share, load all the files it finds in the share, and load them into the PowerShell session. Therefore we have now out-sourced our profile declaration to a centralized location. I’m sure there are 10 other ways to do this, but this is the way I stumbled upon it.
I would put this file in the “%windir%\system32\WindowsPowerShell\v1.0\profile.ps1″ location so that each person who logs onto the machine now has access to the profile.
# Loads the profile for all users.
$locationOfScriptsToLoad = “\\YourCorporateShare\PowerShell\”$files = (ls -path $locationOfScriptsToLoad -recurse | where { $_.attributes -ne “directory” })
foreach($file in $files)
{
# Load the contents of the file into the profile
$fileContents = [string]::join([environment]::newline, (get-content -path $file.fullname))
invoke-expression $fileContents
}
Basically, this script will go up to \\YourCorporateShare\PowerShell\ and find all files inside of it – excluding directories (as directories are objects in PowerShell too, I want to exclude them and this is done with the { $_.attributes -ne “directory }. Once we have the file we will loop through each file and load it into the current PowerShell session. This allows you to store all of your shared PowerShell profile needs in one location. As long as this profile.ps1 script is loaded, the person executing PowerShell will have access to all the profile info.
I hope this helps anyone who is using PowerShell and wants shared profiles.
Downloads
Custom Google Maps Geocode Powershell CmdLet
Sep 18th
This may look like a repeat… well.. because it is… BUT.. you cannot find the old post. Why? Because, well … it no longer exists. My old host decided to accidentally delete my account. Yeah. Oops. After that nightmare, I’ve moved to a new host where the grass is greener, the hippies wear deodorant and well … wait … never mind… onto the good stuff…Here’s the old post, reposted again.
———————————
Ok, I admit … I have a fascination with maps. Why? Who knows. I just love knowing where I’m at, where I’m going, not being lost, etc etc etc. Last year I wrote a C# class that would connect to Google Maps and return the latitude and longitude of an address by utilizing the Google Maps API. Get that class .
Awhile back I also wrote about the complexity of our industry and about how staying up to speed is quite difficult. Jeffrey Snover (PowerShel/Windows Management Architect) commented on that post and recommend learning PowerShell. I completely agree with what he said. However – I’ve only scratched the surface of PowerShell and its capabilities and just recently I picked up a copy of Bruce Payettes “Windows PowerShell in Action“. So far, its a great book. I’ll post a review when I’m done. But as I was reading I got into thinking about how I would like to Geocode addresses from the command line in certain instances. A custom PowerShell cmdlet was in fashion.
The Get-GoogleGeocode CmdLet
At that point, the Get-GoogleGeocode cmdlet was born. I took my code from the C# Google Maps Geocoding class I wrote, added a new property “Address” (which is what I needed to store the address the user supplied) as a simple string and then wrapped it in a cmdlet.
The result? An awesome cmdlet.
The cmdlet will take your Google Maps API Key and a list of addresses and it will Geocode them for you. Very simple, yet very powerful.
Lets take a look at what it can do:
Examples

Note: My Google Maps API key has been hidden in the examples below … SO YOU DONT STEAL IT!
Script Explanation
Click the pictures for full resolution images.
Example 1: Gets the longitude and latitude for one single address and outputs it to the screen.
PS C:\Development\DF.PowerShell.Cmdlets\bin\Release> Get-GoogleGeocode -apiKey <YourAPIKeyGoesHere> -addresses “2203 E. Empire St. Bloomington, IL 61704″
Address Latitude Longitude
——- ——– ———
2203 E. Empire St. Bloomington, IL 6… 40.488283 -88.945315
Example 2: Gets the longitude and latitude for multiple addresses and outputs them to the screen.
PS C:\Development\DF.PowerShell.Cmdlets\bin\Release> Get-GoogleGeocode -apiKey <YourAPIKeyGoesHere> -addresses “2203 E. Empire St. Bloomington, IL 61704″, “8300 Norman Center Dr., Suite 950 Bloomington, MN 55437″
Address Latitude Longitude
——- ——– ———
2203 E. Empire St. Bloomington, IL 6… 40.488283 -88.945315
8300 Norman Center Dr., Suite 950 Bl… 44.853079 -93.352194
Example 3: (I think this is the REALLY cool one) Opens a file, reads each line and gets the longitude and latitude for each address in the file.
PS C:\Development\DF.PowerShell.Cmdlets\bin\Release> Get-Content c:\temp\MicrosoftOffices.txt | Foreach-Object {Get-Goog
leGeocode -apikey <YourAPIKeyGoesHere> -addresses $_ }Address Latitude Longitude
——- ——– ———
2203 E. Empire St. Bloomington, IL 6… 40.488283 -88.945315
77 W. Wacker Dr., Suite 2300 Chicago… 41.886499 -87.630526
3025 Highland Pkwy., Suite 300 Downe… 41.831445 -88.010731
500 E. 96th St., Suite 460 Indianapo… 39.928171 -86.150754
N19 W24133 Riverwood Dr., Suite 150 … 43.056940 -88.229463
4601 Westtown Parkway, Suite 136 Wes… 41.610376 -93.711712
10801 Mastin Blvd., Suite 620 Overla… 38.932978 -94.702328
8300 Norman Center Dr., Suite 950 Bl… 44.853079 -93.352194
3 City Place Dr., Suite 1100 St. Lou… 38.670381 -90.433552
13815 FNB Parkway Omaha NE 68154 41.266103 -96.130733
Script HELP
If you ever forget about how to run this command, I have included the help files as well.
Access help by typing: Get-Help Get-GoogleGeocode. This will give you very basic help. For detailed or full help type: Get-Help Get-GoogleGeocode -detailed or Get-Help Get-GoogleGeocode -full
To use this script, execute the command Get-GoogleGeocode and supply a api key and a list of addresses separated by a comma.
From help:
PS C:\Development\DF.PowerShell.Cmdlets\bin\Release> get-help get-googlegeocode
NAME
Get-GoogleGeocode
SYNOPSIS
This cmdlet will return the latitude and longitude of the addresses that are passed into the cmdlet.
SYNTAX
Get-GoogleGeocode -ApiKey <String> -Addresses <String[]> [<CommonParameters>]
DETAILED DESCRIPTION
This cmdlet will return the latitude and longitude of the addresses that are passed into the cmdlet. The apikey is
required for this to work. Also, you may encounter proxy issues if you require a proxy for access to the internet.
This cmdlet utilizes the System.Net.WebClient class to access the internet. This cmdlet will connect with the Googl
e Maps API, ask for the geocoding information and then return it.
You will need a Google Maps API Key for this. Sign up for one here: http://code.google.com/apis/maps/
If the latitude and longitude are both ZEROS, this means the address could not be geocoded.
RELATED LINKS
REMARKS
For more information, type: “get-help Get-GoogleGeocode -detailed”.
For technical information, type: “get-help Get-GoogleGeocode -full”.
How to Install
Start powershell and navigate to the Release directory run the “install.ps1″ script from the command line. It should look like this when you run it:

How to uninstall?
Run the uninstall.ps1 script. Running that script should result in something that resembles this:

Downloads
C# Solution & Binaries:
DF.PowerShell.zip (44.51 kb)Note: Text File with Addresses from Example 3 is included in solution download.
Creating .NET Event Sources with PowerShell
Feb 26th
I use Enterprise Library for Logging and I use different event sources for each app. I’ve noticed that creating event sources is kind of a pain sometimes. Either you have to write an app that does it for you, or you have to write some logic in your code that checks for the event source. Either way, I think that’s too bloated for in-house software. Now for software that has to be deployed, then ok, no problem I see the use. But we’re talking bout non-released code here. Code that is used on an internal site or externally facing site.
Or you can use PowerShell, simple as pie.
This one liner will check for the Event Source and if it does not exist, it will create it for you. Real easy. Gotta love PowerShell.
if (![System.Diagnostics.EventLog]::SourceExists("MySourceName")) { [System.Diagnostics.EventLog]::CreateEventSource("MySourceName", "Application") }
Now if we write an event to the event log, it will show up because the source now exists.
You HAVE TO love one liners that can replace entire applications.
Albeit, not a big app, but an app, none the less.
