Friday, July 6, 2012

Powershell Unlock File In TFS Using Visual Studio Command Line Vars

I recently found a situation where someone checked out a file and locked it. That severely impedes my ability to make changes to that file. So I set out to remove the lock using Powershell, while I didn't get the native Powershell functionality I normally like, it still achieves the solution in a usable script.

The solution required that I use tf.exe, not an issue, but it is not in windows path by default. It is available if you use the Visual Studio Command Line, which is just a regular command line which runs a batch file. I thought it might be useful to have the ability to turn Powershell into a Visual Studio Command Line. I found a blog post, Replace Visual Studio Command Prompt with Powershell, that works, but I updated it to work with Visual Studio 2010 and more fully use the power of Powershell.

Set Visual Studio Command Prompt environment variables
#Set Visual Studio Command Prompt environment variables
pushd "c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC";
cmd /c "vcvarsall.bat&set" | ? { $_ -match "^(?<Name>[^=]+)=(?<Value>.+)$" } | % {
    Set-Item -Force -Path "env:\$($matches['Name'])" -Value $matches["Value"];
}
popd;
Write-Host "Visual Studio 2010 Command Prompt variables set." -ForegroundColor Yellow;
Here is my script to unlock a file in TFS even if the workspace doesn't belong to the user or on the users computer. If the command line arguments are not entered, the script will prompt for the arguments. It is a nice pattern that I may make a standard of in my scripts.
param(
  [string] $SourceControlFilePath = $(Read-Host "Source Control File Path"),
  [string] $Workspace = $(Read-Host "Workspace"),
  [string] $Username = $(Read-Host "Username")
)

& .\set-vsvars.ps1;

tf lock /lock:none $SourceControlFilePath /workspace:$Workspace`;$Username

Wednesday, June 27, 2012

Google Top Bar Rearrange UserScript for GreaseMonkey

I ran across a script on UserScripts.org called Google topbar. It doesn’t appear to be maintained any longer and doesn’t worked in FireFox 13 (not sure if it didn't work before, but that is what I tested on). So I decided that I would use it as a base for an updated script to make it display the links I most commonly use. 

// ==UserScript==
// @name          Google Top Bar Rearrange
// @namespace     com.blogspot.intellectualponderings.GoogleToolBarRearrange
// @description   Custom Google Links and Order, based on http://userscripts.org/scripts/show/18128
// @include       http://*.google.*/*
// @include       https://*.google.*/*
// ==/UserScript==

(function(undefined) {
  var shared = {
    more: ['Maps', 'Images', 'Play', 'YouTube', 'News', 'Shopping'],
    top: ['Blogger', 'Calendar', 'Reader'],
    topBarList: "//ol[@class='gbtc']",
    topBarNode: "//li[@class='gbt'][a[span[text()='{{0}}']]]",
    moreList: "//ol[@id='gbmm']",
    moreNode: "//li[@class='gbmtc'][a[text()='{{0}}']|text()='{{0}}']",
    nodeLink: "//a"
  };
  
  var node = function(path, text, context) {
    path = (text) ? path.replace(/\{\{0\}\}/g, text) : path;
    context = context || document;
    return document.evaluate(path, context, null, XPathResult.ANY_TYPE,null).iterateNext();
  },
  toMoreMenu = function(text) {
    var tag = node(shared.topBarNode, text);
    if (tag) {
      var more = node(shared.moreList);
      if (more) {
          more.appendChild(tag);
          tag.className = 'gbmtc';
      }
    }
  },
  toTopMenu = function(text) {
    var more = node(shared.moreList),
        tag = node(shared.moreNode, text, more),
        alink, moreli;
    if (tag) {
      var topBar = node(shared.topBarList);
      if (topBar) {
        moreli = topBar.lastChild;
        topBar.appendChild(tag);
        tag.className = "gbt";
        alink = node(shared.nodeLink);
        alink.className = "gbzt";
        if (moreli) {
          topBar.removeChild(moreli);
          topBar.appendChild(moreli);
        }
      }
    }
  };

  if (window.top == window.self) {
      var i;
      for (i = 0; i < shared.more.length; i++) {
        toMoreMenu(shared.more[i]);
      }
      for (i = 0; i < shared.top.length; i++) {
        toTopMenu(shared.top[i]);
      }
  }
})();

And then I decided to change it so that it would use jQuery and be a bit more complete. In fact, I didn't keep much.

// ==UserScript==
// @name          Google Top Bar Rearrange
// @namespace     com.blogspot.intellectualponderings.GoogleToolBarRearrange
// @description   Custom Google Links and Order, based on http://userscripts.org/scripts/show/18128
// @include       http://*.google.*/*
// @include       https://*.google.*/*
// @grant         GM_getValue
// @grant         GM_setValue
// @require       https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js
// ==/UserScript==


(function(undefined) {
  var context = $("#gbz ol.gbtc"),
      more = ['Maps', 'Images', 'Play', 'YouTube', 'News', 'Shopping'],
      top = ['Blogger', 'Calendar', 'Reader'];
      
  var addItem = function(url, text) {
    var item = $("<li />").addClass("gbt").append(
                  $("<a />").attr("href", url).addClass("gbzt").attr("target", "_blank").append(
                    $("<span/>").addClass("gbtb2")
                  ).append(
                      $("<span />").addClass("gbts").html(text)
                  )
                );
        $(context).find("li.gbt:last-child").before(item);
    },
    removeItem = function(text) {
    var link = $(context).find("li.gbt span:contains('" + text + "')").parent(),
        href = link.attr("href");
        link.remove();
        return href;
    },
    moveItemToMore = function(text) {
    if ($(context).find("ol.gbmcc li.gbmtc a:contains('" + text + "')").size() == 0) {
      var url = removeItem(text);
      addMoreItem(url, text);
    }
    },
    addMoreItem = function(url, text) {
    var item = $("<li />").addClass("gbmtc").append(
                  $("<a />").attr("href", url).addClass("gbmt").attr("target", "_blank").html(text));
        $(context).find("ol.gbmcc li.gbmtc:last-child").prev().before(item);
    },    
    removeMoreItem = function(text) {
    var link = $(context).find("ol.gbmcc li.gbmtc a:contains('" + text + "')"),
        href = link.attr("href");
        link.remove();
        return href;
    },
    moveItemToTop = function(text) {    
    if ($(context).find("a span.gbts:contains('" + text + "')").size() == 0) {
      var url = removeMoreItem(text);
      addItem(url, text);
    }
    };
    
  if (window.top == window.self) {
      var i;
      for (i = 0; i < more.length; i++) {
        moveItemToMore(more[i]);
      }
      for (i = 0; i < top.length; i++) {
        moveItemToTop(top[i]);
      }
  }
})();

I am much happier with this one.  Using the DOM XML functions seems so pre-jQuery.

UPDATE 2012-08-29: GreaseMonkey updated and made Google Reader not display feed items. I added some @grant lines in the header and everything works again. My discussion on Google Groups is located here.

// @grant         GM_getValue
// @grant         GM_setValue

Wednesday, June 20, 2012

MSBuild Task Build Projects With Different Properties

I built an MSBuild Script a couple months ago and stumbled across a couple interesting features that MSBuild gives you a way to control a project file's build in a fine tuned way. In my build scenario, I have 3 SharePoint 2010 projects and a WCF service to build (preferably without a lot of source). Since I want to generate the SharePoint solutions and have separate have the WCF service output placed in a different place, normally the path of least resistance would be to have two MSBuild tasks, one for the SharePoint projects and one for the WCF service. This works, but there is some refactoring that can be accomplished. Thus, the following.

Suppose a script reads in several properties we want to use on the various projects. In this example we will hardcode them in a PropertyGroup node as follows:
<PropertyGroup Label="Build options" Condition="'$(GenerateSerializationAssemblies)' == ''">
    <GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies><!-- Auto|On|Off -->
    <Configuration>Release</Configuration>
</PropertyGroup>
Now we define the ItemGroup with all of our projects. The important part each node is that we define the Targets and Properties nodes that we would otherwise define in the MSBuild task node. This example shows that we set the same Configuration setting for all projects and only set the GenerateSerializationAssemblies on the DemoWeb project and then set it to off for the other projects.
<ItemGroup>
<ProjectsToBuild Include="$(SourceFolder)\$(DemoWeb)\$(DemoWeb).csproj">
    <Targets>Package</Targets>
    <Properties>BasePackagePath=.\$(WSPOutputFolder)\;IsPackaging=True;Configuration=$(Configuration);GenerateSerializationAssemblies=$(GenerateSerializationAssemblies)</Properties>
</ProjectsToBuild>
<ProjectsToBuild Include="$(SourceFolder)\$(DemoConfiguration)\$(DemoConfiguration).csproj">
    <Targets>Package</Targets>
    <Properties>BasePackagePath=.\$(WSPOutputFolder)\;IsPackaging=True;Configuration=$(Configuration);GenerateSerializationAssemblies=Off</Properties>
</ProjectsToBuild>
<ProjectsToBuild Include="$(SourceFolder)\$(DemoBusinessData)\$(DemoBusinessData).csproj">
    <Targets>Package</Targets>
    <Properties>BasePackagePath=.\$(WSPOutputFolder)\;IsPackaging=True;Configuration=$(Configuration);GenerateSerializationAssemblies=Off</Properties>
</ProjectsToBuild>
<ProjectsToBuild Include="$(WCFSourceFolder)\$(DemoSvc)\$(DemoSvc).csproj">
    <Targets>ResolveReferences;_CopyWebApplication</Targets>
    <Properties>WebProjectOutputDir=$(WCFOutputFolder)\;OutDir=$(WCFOutputFolder)\bin\;Configuration=$(Configuration)</Properties>
</ProjectsToBuild>
</ItemGroup>
Now the projects and settings are set as needed. The compilation is done as normal, just with some of the nodes refactored. This is great, one MSBuild task, and finer grain control over each project.
<Target Name="Compile" DependsOnTargets="Clean">
    <!-- ... Other tasks ... -->
    
    <MSBuild Projects="@(ProjectsToBuild)">
        <Output ItemName="BuildOutput" TaskParameter="TargetOutputs"/>
    </MSBuild>
    <Message Text="!%0D%0A@(BuildOutput)" />

    <!-- ... Other tasks ... -->
</Target>
As it turns out, it appears that the properties/targets from the current project are applied onto the result of the other project's properties/targets (respectively applied onto the previous.

I have not found this documented previously, but it works for me. I hope it will help someone else.

Wednesday, June 6, 2012

Show and Hide VirtualBox Menu Bar and Status Bar

I use VirtualBox to host virtual machines on my laptop. I am not a fan of clutter, especially when things needlessly take up space. The menu and status bar on the virtual machine window fall into the category of unneeded clutter most of the time. After a considerable amount of digging, I found the VBoxManage command and from there built my commands from there.

There is a downside to using this, the changes are applied the next time VirtualBox starts. Which means you have to completely close VirtualBox and then start it again to toggle it. When you are finished with the bars, you have to close VirtualBox again to toggle it back. I find that I need to use it once every couple months, so the downside is not really an issue.

Here are the contents of batch files to toggle the menu bar and status bar.
C:
cd "\Program Files\Oracle\VirtualBox"
VBoxManage setextradata global GUI/Customizations noMenuBar,noStatusBar
As always, it is good to know how to undo what just occurred.
C:
cd "\Program Files\Oracle\VirtualBox"
VBoxManage setextradata global GUI/Customizations MenuBar,StatusBar

Thursday, May 24, 2012

Calculating the Years and Months Between Two Dates

I ran into an interesting problem that had a much shorter solution than I anticipated. I searched the web for simple solutions and ran across this post on msdn (http://forums.asp.net/t/1289294.aspx/1?TimeSpan+Years+and+Months). Suppose you have 2 dates and want to find the following information about the dates range:
  • Number of years
  • Number of months
  • Number of quarters
  • Number of half years
Often I will test algorithms in Powershell (or some scripting language) and then convert them in what whatever static language I need it to be in; this is one of those cases.
$d1 = [DateTime]::Parse("5/3/2022");
$d2 = [DateTime]::Parse("2/2/2012");
$d3 = [DateTime]::MinValue + ($d1 - $d2)
$d3.Year - 1 # 10
$d3.Month - 1 # 3
$d3.Day - 1 # 1
There are some assumptions that I am making in a couple calculations for my application. I only care about whole periods, so I will be using the floor function to select the correct count. I am also ignoring the days, they don't matter.
TimeSpan span = AmortSched.FirstPrincipalPymntDate - AmortSched.FirstPrincipaMatDate;
DateTime range = DateTime.MinValue + span;

int yearCount = range.Year - 1;
int monthCount = range.Month - 1;
int totalMonthCount = (yearCount * 12) + monthCount;
int totalQuarterCount = Math.Floor(totalMonthCount / 3);
int totalHalfYearsCount = Math.Floor(totalMonthCount / 6);

Thursday, May 3, 2012

Set Visual Studio 2010 Shortcuts Using Powershell

The awesome things you can do with the $dte object inside Visual Studio 2010 just keep piling up. I could not find any examples on how to do it with Powershell.

You can list the commands and their bindings with the following command. Important note... there are a LOT of commands and it will likely take several seconds to output everything.
$dte.Commands | ft -Property Name, Bindings

If you already know the command name, you can get the bindings using the following command.
$dte.Commands | ? { $_.Name -eq "View.TfsSourceControlExplorer" } | % {
    $_.Bindings;
}

Be careful to not choose a shortcut that is already in use. The following are some bindings that I find very useful. The first sets a convenient way to open the Source Control Explorer.
$dte.Commands | ? { $_.Name -eq "View.TfsSourceControlExplorer" } | % {
    $_.Bindings = "Global::Ctrl+W, Ctrl+1";
}

This opens the folder in Windows Explorer containing the current file.
$dte.Commands | ? { $_.Name -eq "File.OpenContainingFolder" } | % {
    $_.Bindings = "Global::Ctrl+Shift+Alt+O";
}

This opens the Attach to Process... dialog. I can't remember if this is the default shortcut, but here it is regardless.
$dte.Commands | ? { $_.Name -eq "Tools.AttachtoProcess" } | % {
    $_.Bindings = "Global::Ctrl+Alt+P";
}