PowerShell
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.
Notepad? THE PowerShell IDE? Please, Save Me – Give Me Some IntelliSense
Feb 22nd
As of lately I've been tasked with more and more work with process automation. As usual, automation is usually found to be best suited for a scripting language and therefore I've been working with PowerShell a lot lately.
I'll admit – I have a problem with scripting languages the IDE's for Scripting Languages. What's the problem? The problem is that the IDE is usually Notepad. Don't get me wrong, I LOVE Notepad, so much that I use Notepad++ as my primary text editor. Adding it to the context menu has been one of the productivity boosters in my day to day work.
The real issue comes down to productivity. I don't write a .NET Enterprise app in Notepad, I use Visual Studio. Could I? Yes. I don't write a Professional Letter in Notepad, I do it in Word. Could I write it in Notepad? Yes. This list could go on and on. I could use MS Paint to create a nice 3D Image, but I wont. But why? Simple… its not productive to. Its not productive to write letters, enterprise applications, or create a billion dollar proposal in Notepad.
Millions of dollars have been put into productivity tools. The industry has put a bounty on productivity so high that entire companies are based around developer productivity. Tools and libraries such as ReSharper, DevExpress, Telerik, Infragistics, etc. help forge the way for developers to increase productivity in their day to day work. In this instance I'm talking about Windows development, there are MANY other companies that create productivity tools for many different industries, such as – graphic design, engineering, medial, etc. The point here is that time is money.
Tools, are exactly that – tools. We've come from the stone age where a wheel was an innovational tool to help move items easier. So please, this is 2008 , and in computer years that puts Notepad into the stone age. So, please don't code in Notepad if you don't have to. Using Notepad as a tool to edit files on the fly is priceless, but using the tool as a primary editor, is, well… ludicrous and crazy. There are many IDE's for almost any environment out there. As new languages are developed, new IDE's soon follow. Unfortunately, one of the worst parts about being bleeding-edge/an-early-adopter and using a new language is waiting for the tool support. Look at VS 2008, the third party companies couldn't even keep up with VS2008 release date. When PowerShell came out, the only IDE was Notepad. Unfortunately, most IT folks still seem to use Notepad for most of there PowerShell scripts. This drives me nuts. This is like driving across country when you could fly. But hey, if you're into long, over drawn out processes, maybe driving across the country will suit you. I'll see you in NYC when you get there, I'll already have gone sight-seeing for 3 days prior to your arrival.
This brings me back to PowerShell. There are many IDE's out there, some free, some not. The one I've seem to use on a regular basis is PowerGUI. Its simple, yet effective. Features include, IntellSense, built in help, debugging, watch window. You can also get more libraries from the downloads as well. For me, being able to debug into a script is worth its weight in gold. I don't know how many times back in ASP Classic I wrote:
Response.Write "In The If"
Man, I don't miss those days at all.
So let me say it again… if you're walking around in the stone age, its time to step out, get a IDE and start enjoying life again. Not reliving the "Response.Write" memoirs. ![]()