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";
}

Friday, April 27, 2012

IIS Application Pool Disable "Ping Enabled" Via Powershell

Probably one of the most frustrating features of debugging Visual Studio is that it will stop debugging an IIS process after a short amount of time and display a dialog box which says:
The web server process that was being debugged has been terminated by Internet Information Services (IIS). This can be avoided by configuring Application Pool ping settings in IIS. See help for further details.
To add insult to injury, it makes you click OK for every application pool that has timed out. In an environment where you have a 4-5 processes attached, this is an annoying waste of time.

It has been long known and pretty well documented on how to fix it in the IIS gui. It is a rather slow process and needs to be automated.

Here is my Powershell function. It will find all of the application pools that start with the specified string and disable the "Ping Enabled" field.

function Disable-Pinging($appPoolNameStartsWith = 'SharePoint - ') {
 Import-Module WebAdministration;
 sl iis:\AppPools;
 Get-ChildItem | ? { $_.Name.StartsWith($appPoolNameStartsWith); } | % { 
  $_.processModel.pingingEnabled = $false; 
 }
 Remove-Module WebAdministration;
}