Yesterday I recevied an email about some code snippets in my PowerShell profile that I’d mentioned on StackOverflow. While trying to retrieve them; I noticed that I hadn’t put them on to my newly-installed Windows 8.1 machine; so I thought it was worth sharing them here while I was copying them from my work machine anyway.

I don’t use PowerShell for scripting much now; I’ve been using FSI (type safety FTW). However, I do still think PowerShell is an excellent shell, and the ability to connect remotely to servers is incredibly useful for executing our deployment/IIS setup scripts/etc.

Adding Colour to Hostnames in PowerShell Remoting Windows

With some help from StackOverflow, I have some functions to help me connect to frequently-used servers, highlighting the hostname depending on whether the server is local/staging/live (green/yellow/red). They’re all prefixed with R- so I can type R- and then tab through the different servers.

# Connect to servers
function R-LocalQAServer    { R-_Connect Green  "LocalQAServer"       $null          "D:\Applications\MyApps" }
function R-BuildServer2     { R-_Connect Green  "buildserver2"        $null          "D:\CI"               }
function R-StageWebserver   { R-_Connect Yellow "stageweby.mycompany" "mycompany\me" "D:\Applications\MyApps" }
function R-ProductionServer { R-_Connect Red    "live.mycompany"      "mycompany\me" "D:\Applications\MyApps" }

# Shared function to handle connecting to servers with specified colour and a specific starting directory
function R-_Connect([ConsoleColor]$color, [string]$hostname, [string]$user, [string]$root)
{
	If ($user) { $session = New-PSSession $hostname -Credential $user }
	Else       { $session = New-PSSession $hostname }

	# Set up MYAPP:\ Drive
	Invoke-Command -Session $session -ScriptBlock {
		New-PSDrive -Name MYAPP -PSProvider FileSystem -Root $using:root | Out-Null
		CD MYAPP:\
	}

	# Pass the hostname+color in so we know what was connected to, so we can overwrite the prompt in colour
	Invoke-Command -Session $session -ScriptBlock { $connectedHost = $using:hostname }
	Invoke-Command -Session $session -ScriptBlock { $promptColor =   $using:color }

	# Overwrite the prompt function to colour the server name
	Invoke-Command -Session $session -ScriptBlock ([ScriptBlock]::Create("function prompt { $(Get-Content function:\RemotePrompt) }"))

	# Connect the local/remote sessions
	Enter-PSSession -Session $session
}

# Helper function that is transferred to the remote server to rewrite the prompt with colour
function RemotePrompt {
	# write computer name with color
	Write-Host "[${env:computername} (${env:username})]: " -Fore $promptColor -NoNew

	# generate regular prompt you would be showing
	$defaultPrompt = "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "

	# generate backspaces to cover [computername]: pre-prompt printed by powershell
	If (!$connectedHost) { $connectedHost = $env:Computername }

	$backspaces = "`b" * ($connectedHost.Length + 4)

	# compute how much extra, if any, needs to be cleaned up at the end
	$remainingChars = [Math]::Max(($connectedHost.Length + 4) - $defaultPrompt.Length, 0)
	$tail = (" " * $remainingChars) + ("`b" * $remainingChars)

	"${backspaces}${defaultPrompt}${tail}"
}

Launch Kiln/BitBucket/Whatever from Shell

The first thing Iusually do after pushing changes to Kiln, is open up the web app and raise code reviews. So; I added a command “Kiln” which just launches Kiln at the correct repo page for where I am. The Kiln extension can do this (“hg kiln”), but we’ve had bad experiences with the Kiln extensions not working on recent versions of Mercurial (due to PyWin32 dependencies) so most of us have them all disabled (except KilnAuth).

# Launch Kiln for current repo
Function Kiln
{
	if (Test-Path ".\.hg\hgrc")
	{
		$repoUrl = (Select-String "default = (.*)" -Path ".\.hg\hgrc" -AllMatches).Matches.Groups[1].Value
		Start $repoUrl
	}
	else
		{ Write-Warning "Not in a repo!" }
}

Launch Visual Studio for Current Project

Because I switch between repos a lot (different versions of our product), it’s a pain to change directory in PoSh and also navigate to the new solution in Visual Studio. So I simply close Visual Studio and type “VS” when in the correct directory for the new branch to launch Visual Studio for any sln file in the current folder. This could be extended to recurse down (or up), but I found it most convenient just working in the immediate folder.

# Launch VS for sln(s) in current folder
Function VS
{
	ii *.sln
}

Launch Windows Explorer for current folder

Why type “start .” when you can just type “e” to start explorer in the current folder?

# Launch explorer in current folder
Function e { ii . }

Launch default browser for current folder via web server

Sometimes you have a static html file you want to load up in a browser to hack on and test, but some browsers (like Chrome) won’t let you pull in scripts for file:/// paths. This function (which requires Python in your PATH) launches the builtin Python web server for the current folder and fires up your browser. If you pass an optional filename

Serve mytest.htm

then it’ll load that fie in the browser.

Function Serve ($page)
{
	Start-Process python -ArgumentList "-m SimpleHTTPServer 8000"
	Start "http://localhost:8000/$page"	
}

Run Mercurial Commands with Less Typing!

And while we’re at it; why have to type “hg” all the time?

# Mercurial helpers
Function st { hg st $args }
Function vd { hg vd $args }
Function ci { hg ci $args }
Function clone { hg clone $args }
Function hist { hg hist -l 5 $args }

Add Visual Studio / .NET Tools to PATH

I like the Visual Studio Developer Command Prompt because it has lots of useful stuff in PATH; but trying to get this into PoSh sucks. You can’t call the old .bat files because they won’t update PowerShells PATH. Re-implementing them is insane, so it’s easier to just hard-code the important few, and add/update as required!

# Add some useful stuff to PATH
$env:Path += ";C:\Program Files (x86)\Microsoft SDKs\F#\3.1\Framework\v4.0\; C:\windows\Microsoft.NET\Framework\v4.0.30319; C:\windows\Microsoft.NET\Framework\v3.5; C:\Program Files (x86)\Windows Kits\8.1\bin\x86;"

Clean all folders with MSBuild

# Reclaim a ton of disk space from branches that have been built but aren't actively needed
# It's hard-coded with my code folder and MYAPP:\ so I can call it from anywhere, but
# it can be made more generic and simplified with James Tryand's walk function further down
Function Clean-All
{
	pushd MYAPP:\
	Get-ChildItem -Directory | % {
		pushd $_
		Get-Item *.sln | % {
			Write-Host ("    Cleaning {0}" -f $_.ToString().Replace("C:\Work\Source\MYAPP\", "")) -F Yellow
			&'msbuild' /t:Clean /nologo /v:m $_
		}
		popd
	}
	popd
	Write-Host ""
}

Create a PSDrive for Code Folder

To make it easy to always get back to my root code folder, I’ve created a PDrive, and change into it at launch.

# Set up MYAPP:\ to point at code checkout folder
New-PSDrive -Name MYAPP -PSProvider FileSystem -Root C:\Work\Source\MYAPP | Out-Null
CD MYAPP:\

I think that’s all the useful stuff incurrently in my profile that might be handy to others. Do post your own useful snippets in the comments!

Highlights from the Comments

Execute command for all child directories

Function Walk-ChildDirectory
{
    Param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)][ScriptBlock]$Task
    )
    ls -Directory | %{
        pushd $_
        & $Task
        popd
    }
}
Set-Alias walk Walk-ChildDirectory

# eg.:
# walk { git gc } # Git GC (WTF is this?)
# walk { msbuild /t:clean } # Clean all projects to reclaim disk space!
# walk { walk { walk { pwd } } } # Can be nested!

Ensure stuff doesn’t drop off the history list

$MaximumHistoryCount = 1024

Handy functions to handle strings more nicely

function Quote-String { "$args" }
function Quote-List { $args }
Set-Alias qs Quote-String
Set-Alias ql Quote-List