Scott Hanselman

Towards a better console - PSReadLine for PowerShell command line editing

September 25, '14 Comments [22] Posted in Open Source | PowerShell
Sponsored By

Sometimes textmode is where it's at. I've long blogged about tools and techniques that will make your Windows console experience better. Perhaps you're a *nix person who is using Windows in your day job, or you wish the Windows PowerShell prompt was more nix-y. Or perhaps you're a PowerShell person who wants to take your command-line to the next level.

Well, just as NuGet is how we get .NET libraries quickly, and Chocolately is a kind of apt-get for Windows, PsGet is a way to easily add PowerShell modules to your prompt.

To install PsGet you run this script (feel free to vet it):

(new-object Net.WebClient).DownloadString("http://psget.net/GetPsGet.ps1") | iex

Once you've got PsGet, the purpose of this post is to introduce you to PSReadLine.

To install PsReadLine with PsGet, just

install-module PsReadLine

If you want to install PsReadLine manually, you can from their readme. It does a LOT:

PsReadLine replaces the command line editing experience in PowerShell.exe. It provides:

  • Syntax coloring
  • Simple syntax error notification
  • A good multi-line experience (both editing and history)
  • Customizable key bindings
  • Cmd and emacs modes (neither are fully implemented yet, but both are usable)
  • Many configuration options
  • Bash style completion (optional in Cmd mode, default in Emacs mode)
  • Bash/zsh style interactive history search (CTRL-R)
  • Emacs yank/kill ring
  • PowerShell token based "word" movement and kill
  • Undo/redo
  • Automatic saving of history, including sharing history across live sessions
  • "Menu" completion (somewhat like Intellisense, select completion with arrows) via Ctrl+Space

But it doesn't replace it in a scary "moved my cheese" way, but in a comfortable familiar way, similar to how Bash works now. It will add things that you WILL miss when you move to another machine that doesn't have PsReadline. If you are already comfortable (or learning) PowerShell, this will feel comfortable immediately. It's not Dvorak. ;)

Some cool PsReadLine examples

Syntax coloring for things like keywords (cd) and common commands (git):

Syntax coloring with PSReadline

PowerShell often has you opening parentheses, brackets and things, and then you have to count them to close them. PsReadLine helps with that also:

Not only does it give you nice syntax-highlighting for things like function building, it also shows me with the red > that I haven't closed the block.

Forgot to close the block

When you are editing a multi-line script, you can also now backup to other lines!

multiline editing with PSReadline

If you are typing something like Get-Process and either want to autocomplete switches, or autocomplete results, you can press Ctrl-Space:

autocomplete

If you're advanced, check out get-PSReadlineKeyHandler and not only look at what functions are bound to which hotkeys, BUT also check out all the functions that AREN'T bound. You have a lot of power for customization here!

get-PSReadlineKeyHandler

You can even set Emacs keybindings!

Set-PSReadlineOption -EditMode Emacs

Go check out https://github.com/lzybkr/PSReadLine on GitHub and give it a star!

Related Links

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

CSI: Re-enabling Remote Desktop with PowerShell after you've blocked it with your own firewall rule

October 14, '13 Comments [8] Posted in Azure | PowerShell
Sponsored By

Got a great email from reader Seán McDonnell.

The Big Problem:

I set up an Azure virtual machine running Windows Server 2012.

I accidentally disabled the Remote Desktop Windows firewall rule (while I was remotely connected). The connection dropped as you would expect.

I have been pulling my hair out ever since trying to re-enable this rule.

Doh. Ouch. I didn't ask how this happened, but you know, one gets to clicking and typing and you can feel the mistake about to happen as your hand drops towards the keyboard, but by then it's too late. Gravity has screwed you.

I suggested that Seán use Remote Powershell to get in and add the enabling Firewall Rule for RDC. Remote PowerShell is like "SSH" in *nix. You get a remote terminal and can pretty much do whatever you want from there.

TL;DR version of Seán's experience.

  • Make sure PowerShell is enabled in the Endpoints section of the Azure portal.
  • Get the server's certificate (PowerShell needs this for remote commands). You can get the server certificate by going to your domains' URL: https://yourdomain.cloudapp.net:12345 (where :12345 is the port that PowerShell uses).
  • Export the SSL certificate of the site as a .CER file and install it on your local machine.
  • Save it to the "Trusted Root Certification Authorities" store on your machine.
  • Open PowerShell with administrative privileges on your local machine and type:
    Enter-PSSession -ComputerName yourdomain.cloudapp.net -Port 5986 -Credential YourUserName -UseSSL
  • A login popup will appear, enter your VM's login credentials here.
  • You will now be able to execute commands against the Azure VM. In Seán's case, he ran
    netsh advfirewall firewall set rule group="remote desktop" new enable=Yes
    and exited the PowerShell session and was able to remotely connect to my machine.

Long Detailed Version with Screenshots

Long version with screenshots:

Make sure PowerShell is publically accessible in the 'endpoints' section of the Azure portal.

 01 - VM Endpoints

Get the server's certificate (PowerShell needs this for establishing a remote session). You can get the server certificate by going to your domains' URL: https://yourdomain.cloudapp.net:5986 (where :5986 is the port that PowerShell uses).

 image

Go to the Details tab and click Copy to File...

 03 - Certificate Export

Leave the first option selected and save the file to a local drive. 

 04 - Certificate Export

05 - Certificate Export

Once the file is generated and saved locally, install the certificate by double clicking on the certificate-name.cer file.

 06 - Certificate Install

Install the certificate in the following store:

cert install

Open up PowerShell with administrative privileges and execute the following command (replacing the domain name and username with your own one):

 08 - Remote PowerShell Session

A logon credential popup should appear where you will need to enter your VM's username and password:

07 - Remote PowerShell Session

If successful, it should be pretty obvious that you have successfully initiated a remote session with the VM.

Enter-PSSession -ComputerName yourdomain.cloudapp.net -Port 5986 -Credential YourUserName -UseSSL

09 - Remote PowerShell Session Verification

To open re-enable the firewall rule you issue the command:

netsh advfirewall firewall set rule group="remote desktop" new enable=Yes  

 10 - Remote PowerShell Session Firewall Rule Update

The final step was to quit the PowerShell session and RDC to the VM. Success! 

I hope this write-up helps other people as well. Thanks Seán for a great question and for sharing the screenshot of your experience!


Sponsor: Thanks to Red Gate for sponsoring the feed this week! Check out a simpler way to deploy with Red Gate’s Deployment Manager. It can deploy your .NET apps, services, and databases in a single, repeatable process. Get your free Starter edition now.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

Prompts and Directories - Even Better Git (and Mercurial) with PowerShell

February 1, '12 Comments [36] Posted in Open Source | PowerShell
Sponsored By

posh-git with custom directoryI love PowerShell and spent years and years working with it since it first came out. I've actually got 15 or so pages of PowerShell posts on this blog going way back.

PowerShell is insanely powerful. I would even go so far as to say it was (is) ahead of its time. PowerShell is totally dynamic and is almost like having JavaScript at the command line, but even more thoughtfully designed. I appreciate folks that love their bash shells and what not, but PowerShell can do some wonderful things.

A long time ago (2009) Mark Embling blogged about a nice prompt with TabExpansion that he'd made to make working with PowerShell and Git (his favorite source control system) nicer. Later, Jeremy Skinner added TabExpansion for common commands. A little while later Keith Dahlby started with their code (with their blessing) and posh-git was born. Expanding even more, Jeremy later created posh-hg (for Mercurial, my favorite source control system).

All of these are currently in use in various forms. Just recently (days ago, even) while I was trying figure out how to get these two separate but similar PowerShell scripts to live together when Keith created a small shared function that makes sharing prompts easier.

I think that Git Bash on Windows needs to go away. It's just not useful to say that a Windows user has to run Bash in order to use Git. PowerShell with Git (or your favorite VCS) is demonstrably better for Windows folks. I also feel that the installation for posh-git, while it uses PsGet (think NuGet for PowerShell, which is INSANELY awesome), just could be easier.

I happened to be tweeting about this and ended up doing a Skype+Join.me 3-way pair programming session with Keith and Paul Betts to explore some ideas on the topic. While a customized prompt is cool, I wouldn't rest until we'd modified "dir/ls" to show source control status. I'm not talking about a PowerShell Provider, I'm talking about extending the View for the result of a dir (get-childitem and a FileInfo) itself.

I want to be able to take a fresh machine and fresh PowerShell installation, invoke one PsGet command and get Git and Hg (and whoever else) integration with PowerShell, a new prompt AND new (yet to be written at this point) directory listing support.

We did some pairing as I sat in a pub and drove while Keith and Paul told me I was a lousy typist. We got a nice prototype working and I went home. After the kids went to sleep I was asking questions on a mailing list and ended up getting an answer from James Brundage, noted PowerShell expert. I'd met James at a Nerd Dinner in Seattle once and gave him a book. He was kind enough to do a screen sharing session with me and refactor my directory spike (and some of posh-git) into a more useful form. It's still a spike, but Keith and I are going to merge all three of them (posh-git, posh-hg and my VCS dir stuff) into one usable and easy to install module. I'm sure we'll both blog about it when it's cleaner. I'm hoping we'll get it all integrated into a single install line:

(new-object Net.WebClient).DownloadString("http://psget.net/GetPsGet.ps1") | iex
install-module posh-git

Here's the general idea that encapsulates a number of these ideas. Rather than scripts that are plugged into your PowerShell $profile, we'll have a module or two like this.

C:\Users\Scott\Documents\WindowsPowerShell\Modules
$ dir

Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 1/31/2012 10:37 PM Posh-Git
d---- 1/31/2012 10:37 PM Posh-GitDir
d---- 1/31/2012 12:27 AM PsGet

The Posh-Git folder is the Posh-Git source as it is, as a module and imported in your profile like this.

Import-Module Posh-Git
Import-Module Posh-GitDir

Posh-GitDir is my extension module that will change dir/ls/get-childitem and add a Git Status column. I've added extra columns with file information before in PowerShell, except in a cheesy way and I never actually overrode dir directly.

First, we'll make a post-gitdir.Types.ps1xml that adds the new ScriptProperty that pulls details for each file out of a $GitStatus variable that's added each time the prompt is drawn.

<?xml version="1.0" encoding="utf-8" ?>
<Types>
<Type>
<Name>System.IO.FileInfo</Name>
<Members>
<ScriptProperty>
<Name>Git</Name>
<GetScriptBlock>
$retVal = ""
if ($GitStatus.Index.Added -contains $this.Name) { $retVal += "+" } `
elseif ($GitStatus.Index.Modified -contains $this.Name) { $retVal += "~" } `
elseif ($GitStatus.Index.Unmerged -contains $this.Name) { $retVal += "!" } `
else { $retVal += " " }

$retVal += " "

if ($GitStatus.Working.Added -contains $this.Name) { $retVal += "+" } `
elseif ($GitStatus.Working.Modified -contains $this.Name) { $retVal += "~" } `
elseif ($GitStatus.Working.Unmerged -contains $this.Name) { $retVal += "!" } `
else { $retVal += " " }

$retVal
</GetScriptBlock>
</ScriptProperty>
</Members>
</Type>
<Type>
<Name>System.IO.DirectoryInfo</Name>
<Members>
<ScriptProperty>
<Name>Git</Name>
<GetScriptBlock>
""
</GetScriptBlock>
</ScriptProperty>
</Members>
</Type>
</Types>

This adds the Git column to the output as a ScriptProperty, but doesn't change the default view.

$ dir | get-member

TypeName: Get-ChildItem

Name MemberType Definition
---- ---------- ----------
Mode CodeProperty System.String Mode{get=Mode;}
Create Method System.Void Create(System.Security.AccessControl.DirectorySecurity director...
CreateObjRef Method System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
...snip...
BaseName ScriptProperty System.Object BaseName {get=$this.Name;}
Git ScriptProperty System.Object Git {get="";}

I'd have to always select manually, which is tedious.

$ dir | select Name, Git

Name Git
---- ---
CommandType.cs ~
Connection.cs ~
ConnectionExtensions.cs ~
ConnectionManager.cs +
ConnectionScope.cs +
GuidConnectionIdFactory.cs ~

We want to change the dir "view" itself.  We have to copy the default view for a directory at: "C:\Windows\system32\WindowsPowerShell\v1.0\FileSystem.format.ps1xml" and add our new column.

<?xml version="1.0" encoding="utf-16"?>
<Configuration>
<ViewDefinitions>
<View>
<Name>Dir-Git</Name>
<ViewSelectedBy>
<TypeName>Dir-Git</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Git</Label>
<Width>4</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
...snip...
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<Wrap/>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Git</PropertyName>
</TableColumnItem>
...snip...
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>

For the design, I want the Indexed and Working files in two columns, showing Added+, Modified~ and Unmerged! files like this. Deleted files won't show up cause they aren't there.

$ dir #snipped out directories and files for clarity

Mode Git LastWriteTime Length Name
---- --- ------------- ------ ----
d---- 1/31/2012 5:14 PM Configuration
d---- 1/31/2012 5:14 PM Hosting
-a--- + 1/31/2012 5:14 PM 2170 ConnectionManager.cs
-a--- + 1/31/2012 5:14 PM 402 ConnectionScope.cs
-a--- ~ 1/31/2012 5:14 PM 280 GuidConnectionIdFactory.cs
-a--- 1/31/2012 5:14 PM 273 IConnection.cs
-a--- ~ 1/31/2012 5:14 PM 165 IConnectionIdFactory.cs
-a--- + 1/31/2012 5:14 PM 304 IConnectionManager.cs
-a--- 1/31/2012 5:14 PM 118 packages.config
-a--- ~ 1/31/2012 5:14 PM 8296 PersistentConnection.cs
-a--- + 1/31/2012 5:14 PM 1118 PersistentConnectionFactory.cs
-a--- 1/31/2012 5:14 PM 623 PersistentResponse.cs
-a--- ~ 1/31/2012 5:14 PM 1288 SignalCommand.cs
-a--- ~ 1/31/2012 5:14 PM 7386 SignalR.csproj
-a--- 1/31/2012 5:14 PM 23076 TaskAsyncHelper.cs
C:\Users\Scott\Desktop\github\SignalR\SignalR [master +1 ~0 -0 | +10 ~58 -11 !]

We import these modules in our $profile.

Import-Module Posh-Git
Import-Module Posh-GitDir

The posh-git module adds the custom prompt (if you haven't changed yours) with a new function called Write-VcsStatus that is shared between Hg and Git (and any other systems that want to play with us). It only adds the prompt if the user hasn't already customized their prompt. If they have, they'll need to incorporate Write-VcsStatus themselves.

$defaultPromptHash = "HEYStcKFSSj9jrfqnb9f+A=="

$md5 = [Security.Cryptography.MD5]::Create()
$thePrompt = [Text.Encoding]::Unicode.GetBytes((Get-Command prompt | Select-Object -ExpandProperty Definition))
$thePromptHash = [Convert]::ToBase64String($md5.ComputeHash($thePrompt))

if ($thePromptHash -eq $defaultPromptHash) #using the default prompt?
{
#recommend our own
function prompt(){
# Reset color, which can be messed up by Enable-GitColors
$Host.UI.RawUI.ForegroundColor = $GitPromptSettings.DefaultForegroundColor

Write-Host($pwd) -nonewline -foregroundcolor white

Write-VcsStatus

Write-Host ""
return "$ "
}
}
else {
Write-Debug "Make sure your prompt includes a called to Write-VcsStatus!"
}

The craziness that James Brundage came up with to override dir/ls/get-childitem was this. He said he'll do a complete tutorial on his blog with technical details for the generic case.

. ([ScriptBlock]::Create("
function Get-ChildItem {
$([Management.Automation.ProxyCommand]::GetCmdletBindingAttribute((Get-Command Get-ChildItem -CommandType Cmdlet)))
param(
$([Management.Automation.ProxyCommand]::GetParamBlock((Get-Command Get-ChildItem -CommandType Cmdlet)))
)

process {
Microsoft.PowerShell.Management\Get-ChildItem @psBoundParameters |
ForEach-Object {
`$null = `$_.pstypenames.Insert(0, 'Dir-Git')
`$_
}
}
}
"))

Tie it all up with a .psd1 file that has the list of Scripts, Types, Formats and the Module.

@{
ModuleVersion="1.0.0.0"
Author="Scott Hanselman"
Description="Posh-GitDir"
CompanyName="Hanselman and Friends"
RequiredModules="Posh-Git"
ScriptsToProcess="prompt.ps1"
TypesToProcess="posh-gitdir.Types.ps1xml"
FormatsToProcess="posh-gitdir.Format.ps1xml"
ModuleToProcess="posh-gitdir.psm1"
}

To recap, this new module requires the posh-git module, it ands our new "Dir-Git" type, adds the Git ScriptProperty in Types, and shows how to Format it, and overrides get-childitem in the psm1. If you didn't want to override dir proper, maybe you could make a dir-git or dir-hg.

Next steps are for us to integrate them into one module, bring in an inproc library to access the source info (rather than regex'ing the output of git status and hg status) which would speed it up 10x I'm sure, as well as better NuGet support.

In this screenshot you can see posh-git and posh-hg living together. The first directory is a hg repo with a 1 file not in control. The second directory is a git repo with 1 file added in the Index, 10 new added in working, 58 modified, and 11 deleted.

PowerShell with Git and Hg

Keith and Jeremy have done some amazing work. Open Source, baby. I'm looking forward to pairing with them in coming days and buttoning this up. I've been a hardcore Tortoise (Tortoise-Hg, Tortoise-SVN, Tortoise-Git) source control user, but the addition of PowerShell is shaking my faith in a good way. At leat to the point that I think it's worth my spare time to see this through.

Thoughts?

Related Links

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

Unix Fight! - Sed, Grep, Awk, Cut and Pulling Groups out of a PowerShell Regular Expression Capture

August 1, '11 Comments [15] Posted in PowerShell
Sponsored By

There's a wonderful old programmers joke I've told for years:

"You've got a problem, and you've decided to use regular expressions to solve it.

Ok, now you've got two problems..."

A friend of mine was talking on a social network and said something like:

"That decade I spent in the Windows world stunted my growth. one teeny-tiny unix command grabbed certain values from an XML doc for me."

Now, of course, I took this immediately as a personal challenge and rose up in a rit of fealous jage and defended my employer. Nah, not really as I worked at Nike on Unix for a number of years and I get the power of sed and awk and what not. However, he said XML, and well, PowerShell rocks XML.

Because it's a dynamic language, you can refer to XML nodes just like this:

$a = ([xml](new-object net.webclient).downloadstring("http://feeds.feedburner.com/Hanselminutes"))
$a.rss.channel.item

The first line gets the feed and the second line gets all the items.

However, turns out my friend was actually trying to retrieve values within poorly-formed XML fragments within a larger SQL dump file. There's three kinds of XML. Well-formed, valid, and crap. He was sifting through crap for some values. Basically he had this crazy text file with some fragments of XML within it and wanted the values in-between elements: "<FancyPants>He wants this value</FancyPants>."

Something like this:

grep "<FancyPants>.*<.FancyPants>" test.txt | sed -e "s/^.*<FancyPants/<FancyPants/" | cut -f2 -d">"| cut -f1 -d"<" > fancyresults.txt

I'm old, but I'm not an expert in grep and sed so I'm sure there are ways he could have done it more tersely. There always is, right? With regular expressions, sometimes someone just types $@($*@)$(*@)(@*)@*(%@%# and Shakespeare pops out. You never know.

There's also a lot of different ways to do this in PowerShell, but since he used RegExes, who am I to disagree?

First, here's the one line answer.

cat test.txt | foreach-object {$null = $_ -match '<FancyPants>(?<x>.*)<.FancyPants>'; $matches.x}

But I thought I'd also sort them, remove duplicates...

cat test.txt | foreach-object {$null = $_ -match '<FancyPants>(?<x>.*)<.FancyPants>'; $matches.x} | sort | get-unique

But foreach-object can be aliased as % and get-unique can be just "gu" so the final answer is:

cat test.txt | % {$null = $_ -match '<FancyPants>(?<x>.*)<.FancyPants>';$matches.x} | sort | gu

I think we can agree at they are both hard to read. I still love PowerShell.

Related Links:

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

How to change the default browser in Visual Studio programmatically with PowerShell and possibly poke yourself in the eye

August 21, '10 Comments [29] Posted in ASP.NET | ASP.NET MVC | PowerShell | VS2010
Sponsored By

UPDATE: Why my own MacGyver solution was brilliant in its horrible way, the folks over at World of VS have taken up the challenge and created a proper Visual Studio extension that you should use. I'll chat with them and get some details and maybe a write-up of how they did it. So, while I encourage you to enjoy my tale below, go get the World of VS Default Browser Switcher now!

I've heard and seen lots of complaints about how it's hard to set the default browser that Visual Studio launches when you launch a debug session for a website.

Step 0 - Adequate

Folks spend time hunting around the Tools|Options dialog in Visual Studio looking for setting. They eventually realize it's not in there at all, but instead you have to right-click on an ASPX page within a Web Project and click "Browse With..."

Right Click | Browse With

From this dialog you can click Set Default, which is totally obvious, right my daimies? Um, no. This doesn't work for ASP.NET MVC people who use other view engines and might not even have a .ASPX file in their solution. Plus, it's slow and irritating. Sa da tay.

Browse With - Default.aspx 

It IS interesting that I can add other browsers, like Google Chrome to this dialog via Add. Note that Google Chrome installs in C:\Users\Scott\appdata\Local\Google\Chrome\Application\chrome.exe which may not be c:\Program Files where you usually go hunting for these things.

"What my thought process was" - or - "CSI: Visual Studio Default Browser"

Where is this browser information stored? That was my first question. Remember that your computer is NOT a black box. Even good programmers make this mistake and they "flip this switch and hope that light turns on" without confirming that the switch and the light are connected with good wire and they know how electricity works.

I can guess all day, or I can open up ProcMon and just see for myself. Seriously, learn how to use this freaking tool. You can flip light switches all day or you can just open up the wall and see the wires. If you know how to use Process Monitor competently, people of both sexes will immediately find you more attractive. It's true. I get all sorts of free Tacos and Chips when folks look can I run ProcMon like Keanu Reeves can look sad.

I fired ProcMon up set it to only show the devenv.exe process, and I took a chance and set 'contains browser' for the path. If this didn't work I'd open the flood gates and start sifting a bit. I could also have said 'highlight' things with the word browser if I liked.

Process Monitor Filter (2)

I launch VS, open the Browse With dialog and sweet sassy mollassy. Look at all that good stuff.

Process Monitor - Sysinternals www.sysinternals.com

Oh, fonts too small, let me zoom in.

Registry Zoomed In

Looks like we're reading values out of the registry at HKCU:\Software\Microsoft\VisualStudio\10.0\WebBrowser\ConfigTimestamp and...

image 

...reading out of the browsers.xml file at C:\Users\Scott\AppData\Local\Microsoft\VisualStudio\10.0\browsers.xml.

What's in that file? I'm guessing XML with no schema, given it was probably 2003 when someone wrote this.

<?xml version="1.0"?>
<BrowserInfo>
<BrowserInfo>
<Browser>
<Name>Google Chrome</Name>
<Path>"C:\Users\Scott\AppData\Local\Google\Chrome\Application\chrome.exe"</Path>
<Resolution>0</Resolution>
<IsDefault>False</IsDefault>
</Browser>
</BrowserInfo>
</BrowserInfo>

I've seen folks attempt to change this with various extensions in Visual Studio and using automation calls within Visual Studio, but to the best of my knowledge, this feature has been in here for years and years and there's no way to get at it programmatically.

Step 1 - Slightly Awesome

Interestingly as well, my first attempt at changing the browser programmatically consisted of this brilliance:

C:\Users\Scott\AppData\Local\Microsoft\VisualStudio\10.0>copy "browsers - firefox.xml" browsers.xml /y
1 file(s) copied.

But, it seems that those registry keys are serious and really used for something, because each time I opened Browse With... I found my changes thrown away, probably because VS isn't watching for file for change notification, but rather caching the file in memory.

Looks like a job for PowerShell (yes, I know can do this with batch files, but PowerShell is way batter, so nyah. Learn it.)

First I need to figure out what's going on in this registry. If I got to that key (by right-clicking the key within ProcMon and clicking Jump To), then right-click on the key in the Registry Editor I get:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0\WebBrowser\ConfigTimestamp]
"CacheFilePath"="C:\\Users\\Scott\\AppData\\Local\\Microsoft\\VisualStudio\\10.0\\browsers.xml"
"CacheFileSizeBytes"=dword:000000e6
"CacheFileDateLastMod"=hex(b):6f,18,a4,ee,17,41,cb,01
"LastConfigurationTimestamp"=hex(b):00,26,aa,86,09,41,cb,01

Ok, so that's the location of the file we already know about, and  looks like the file size in hex, as well as some magic goo for CacheFileDateLastMod and LastConfigurationTimestamp.

One at a time. The CacheFileSizeBytes is easy. Did you know that the Windows 7 calculator was quietly upgraded while you were out there not learning how to convert hex in your head? It's true! (Yes, you can also just look at the decimal value in the Registry, but again, this is more fun. Yes, you could always convert between Hex and Dec in calc.exe, but again, fun. What's your beef? ;) )

Calculator in Windows 7 - Being Awesome in Programmer Mode

Click Dec(imal) and looks like 230 bytes, the size of my browser.xml file. What's the deal with those mod dates, though? They a probably a Windows file time, if they aren't ticks. Ticks are seconds since 1970-1-1 and a Windows FileTime is the number of 100-nanosecond ticks since 1601-1-1 rounded to the nearest millisecond. Why? Because 1601 was an awesome year. I mean, the Battle of Kinsale stopped the siege in Kinsale, Ireland, and famine killed half the Estonians. Sheesh, that was a horrible year! What were we thinking!?

Anyway, the easiest way to convert something you think might be a Date into a Date (and reason number 0x3b for you to finally learn PowerShell) is this line in PowerShell.

PS C:\> [DateTime]129268523480000000
Saturday, August 21, 0410 8:19:08 AM

Lemme do that again against a blue background with a screenshot so you really believe me.

Windows PowerShell, again, being awesome.

Oh yes, I'm on a horse. Cool. Those are FileTimes. So, tappity tappity and here's a PowerShell script to check the current values and output them for testing.

cd C:\Users\Scott\AppData\Local\Microsoft\VisualStudio\10.0

$browsers = [xml](get-content 'browsers.xml')
$browsers.BrowserInfo.Browser.Name + " " + $browsers.BrowserInfo.Browser.Path

$regkey = "HKCU:\Software\Microsoft\VisualStudio\10.0\WebBrowser\ConfigTimestamp"

"LastConfigTimestamp: " + ([DateTime](get-itemproperty $regkey).LastConfigurationTimestamp).ToLocalTime()
"CacheFileDateLastMod: " + ([DateTime](get-itemproperty $regkey).CacheFileDateLastMod).ToLocalTime()
"CacheFileSizeBytes: " + (get-itemproperty $regkey).CacheFileSizeBytes

I'll go and develop and run this script in the PowerShell ISE (that's S for Scripting) that you already have on your computer. Freaky how Microsoft sneaks stuff like this on your machine. If you've got Windows 7, you're already got this.

Windows PowerShell ISE (x86)

I run Visual Studio and click Browse|With... a few times and watch the values change. Seems I need the SET to my GET script, so why not something like this. I've made copies of browsers.xml like browsers-chrome and browsers-firefox. You can do the same if you like.

cd C:\Users\Scott\AppData\Local\Microsoft\VisualStudio\10.0 

copy '.\browsers - firefox.xml' .\browsers.xml
$regkey = "HKCU:\Software\Microsoft\VisualStudio\10.0\WebBrowser\ConfigTimestamp"

set-itemproperty $regkey -name LastConfigurationTimestamp -value (&{[DateTime]::Now.ToUniversalTime().ToFileTime()}) -type qword
set-itemproperty $regkey -name CacheFileDateLastMod -value (&{((dir .\browsers.xml).LastWriteTimeUtc).ToFileTime()}) -type qword
set-itemproperty $regkey -name CacheFileSizeBytes -value (&{(dir .\browsers.xml).Length}) -type dword

What I am doing in this script? I'm copying my browser xml over the main one AND I'm updating the TimeStamp to now to get Visual Studio to re-read the file. Visual Studio seems to be checking and triple checking and if the CacheFileDateLastMod and CacheFileSizeBytes don't reflect reality, it will freak out and just delete my file completely and rebuild a new browsers.xml from scratch. Paranoid.

You can go and fancy these scripts up with command-line parameters all you want because you're a better programmer than I, but I am all Save|As, baby. I have "UpdateDefaultBrowserToChrome.ps1" and, yes, wait for it, "UpdateDefaultBrowserToFireFox.ps1" and I sleep at night just fine, thank you very much.

I can right click on them on my desktop and select Run with PowerShell if I like.

Right clicking on my PowerShell script on my desktop

But...still....it could be more awesome. Darn my parents and the work ethic they instilled in me as a small child.

Step 2 - Extremely Awesome

You can run PowerShell scripts from the regular not-really-DOS command line like this if you like.

C:\Users\Scott\Desktop>powershell .\UpdateDefaultVSBrowserToChrome.ps1

I could even install PowerConsole and run these commands from INSIDE Visual Studio 2010 if I like and I want to rip a hole in the space time continuum. My, is that intellisense inside PowerShell inside Visual Studio? Double sun power!!!!

Powershell being awesome INSIDE Visual Studio 2010. Double Sun Power!!!!!

Still, this doesn't fit my mindless point and click mouse-like workflow. I'll go to Tools | External Tools and add two. Make sure you select the right PowerShell, which will be x86 even if you're on x64 so you correctly access the registry. I also checked Use Output window and added a single line of text at the bottom of each script.

External Tools 

Nice, but why not a toolbar?

Step 3 - Profit! I mean, Profoundly Awesome!

Right click on any Toolbar, this Customize and add buttons for External Tools (in my case #6 and #7) and...

Switchy Browser Toolbar buttons

Which gives me this when I click:

Pow, Sally. Browser changed to Chrome.

And shows this in Browse With, showing me it worked:

Browse With - Default.aspx again with Chrome

Yay! Enjoy.

Roll Up Instructions

  1. Go to C:\Users\YOURNAME\AppData\Local\Microsoft\VisualStudio\10.0
  2. Make a bunch of browser-whatever xml files for your browsers, or populate your list from Visual Studio, then make copies.
  3. Make yourself a few of these (or make one that switches if you're awesome)
cd C:\Users\YOURNAME\AppData\Local\Microsoft\VisualStudio\10.0

copy '.\browsers - CUSTOMBROWSER.xml' .\browsers.xml
$regkey = "HKCU:\Software\Microsoft\VisualStudio\10.0\WebBrowser\ConfigTimestamp"

set-itemproperty $regkey -name LastConfigurationTimestamp -value (&{[DateTime]::Now.ToUniversalTime().ToFileTime()}) -type qword
set-itemproperty $regkey -name CacheFileDateLastMod -value (&{((dir .\browsers.xml).LastWriteTimeUtc).ToFileTime()}) -type qword
set-itemproperty $regkey -name CacheFileSizeBytes -value (&{(dir .\browsers.xml).Length}) -type dword
"Bam, son. Browser changed to CUSTOM BROWSER."
  1. Add some external tools that call PowerShell with your new scripts as parameters.
  2. Add toolbar buttons if you feel like it.
  3. Go write a proper Visual Studio 2010 extension that does all this and packages it up in one click, put in on the VS Gallery and impress your friends and family. Crap. Now *I* need to do that for my next post, don't I? Shoot. Kzu? Give me a call and teach me how to do this.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Page 1 of 14 in the PowerSHell category Next Page

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.