Wednesday, April 29, 2015

Bulk Nuget updates

This article is really old and the instructions are unlikely to work correctly any more. There might be some newer and safer way of performing bulk NuGet updates across many projects, but maybe that's just too dangerous in general these days.

Every few months I need to bulk update all of my VS2015 projects to use the latest version of a certain package. Because I spent another 10 minutes stuffing around to get the correct syntax, I'm putting it in here so I won't forget again. The following command recursively loops down looking for *.sln files and runs a nuget update against them.

for /R %f in (*.sln) do nuget update "%f" -id packagename

If the solution's projects do not contain the package id you will get this for each project:

Updating 'project name'...
WARNING: Unable to find 'packagename'. Make sure they are specified in packages.config.

If the solution's projects do not use Nuget then you get:

Scanning for projects...
No projects found with packages.config.

If a solution's project is updated you get this without a following warning message:

Updating 'project name'...

The resulting output from processing many solutions and projects can be quite cluttered and difficult to read to see what really happened.

UPDATE NOTE -- December 2015

Solutions containing web projects don't seem to upgrade correctly using the technique described above. I found I had to open these solutions in Visual Studio and manually update the packages. I haven't diagnosed the details of this issue yet, so this is just a friendly warning.

Saturday, April 25, 2015

Batch build vdproj

As I mentioned in a different blog post, support for vdproj (Visual Studio setup) projects was removed in VS2010, then it quietly returned as an optional extension for VS2013. After resurrecting some old setup projects I decided I needed to build them in batch scripts. I remember several years struggling for an hour to find the correct syntax of the command required to batch build a vdproj file. It turns out you need something like this sample:

"<path>\devenv" foo.sln /Project setup/foo.vdproj /Build Debug

The arguments are fragile and you must specify the path of the sln file first, then /Project specifies the path to the vdproj file (which is a part of the solution), then other switches which are documented HERE. If you get anything slightly wrong then all you get is the devenv help display. However, this time, all I could get out of this command was:

ERROR: An error occurred while validating.  HRESULT = '8000000A'

For ages I thought it was my fault due to a syntax error or a version stuff up. Then I ran a web search and found it was a known problem. The fix/workaround is documented HERE. You have to add this DWORD value to the registry:

HKCU
  Software
    Microsoft
      VisualStudio
        14.0_Config
          MSBuild
            EnableOutOfProcBuild = 0x0

This dreadfully obscure change allows the devenv command to work correctly. I'm quite angry about this as it took me over an hour to defeat the problem and document it.
Update July 2017 -- This post was updated for more recent versions. All of the information above was confirmed using Visual Studio 2015.

Friday, April 24, 2015

Solution Open with ... batch build

I often want to right-click a Visual Studio solution file (.sln) in Windows Explorer to ensure that it builds cleanly. I don't want to bother manually launching Visual Studio for the same purpose. Here's my way of doing this.

Create a batch file like the following (mine is called buildsln.bat). This command is rather simple, you can make one as fancy as you need.

@echo off
"C:\Program Files (x86)\MSBuild\14.0\Bin\msbuild" "%1" /t:build /v:minimal
if errorlevel 1 pause

Import the following into the Registry (as an Administrator) to add an Open With menu selection when you right-click a .sln file.

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Orthogonal.buildsln]
@="Build SLN"

[HKEY_CLASSES_ROOT\Orthogonal.buildsln\Shell\Open\Command]
@="\"C:\\ProgramData\\Utility\\buildsln.bat\" \"%1\""

[HKEY_CLASSES_ROOT\.sln\OpenWithProgids]
"Orthogonal.buildsln"=""

Change the key name Orthogonal.buildsln to be whatever suits you. Set the first default key value to be the text that is displayed in the context menu. The next default key value is the path to the batch file.

Note: Windows 7 to 10

After upgrading my development PC from Windows 7 to 10 and recreating the registry values above, I found that the Open With menu did not have my new item. Even worse, the OpenWithProgids key for .sln has 4 values, but only 2 of them appear in the Open With menu. Despite extensive experiments, I haven't figured out yet how to get my extra context commands to appear.

XmlReader/Writer and XElement

When reading or writing large amounts of XML you can use the XmlReader and XmlWriter classes to stream the data and avoid holding it all in memory. These classes are unfortunately quite low-level and the code that uses them can get quite cluttered. The XmlReader can be particularly tricky to use, as you must inspect the names and types of the nodes as they are sequentially consumed by the reader. You may need to remember your depth and position in the incoming XML, which can result in verbose and fragile code.

A great way to produce shorter and more readable code for XML stream reading is to combine the XmlReader class with the XElement class that was introduced in Framework 3.5. Use XmlReader to process the "outer" elements, then use XElement to read "inner" chunks of XML so they can be conveniently processed using LINQ to XML. Say you had incoming XML like this:

<export>
  <rows>
    <row id="1">
      <name>Fred Smith</name>
      <hired>1998</hired>
    </row>
    :
    : huge numbers of row elements
    :
  </rows>
</export>

Use an XmlReader to sequentially consume the XML, then when you hit the <rows> element, loop over the child <row> elements and pull them into an XElement for processing.

var settings = new XmlReaderSettings() { IgnoreWhitespace = true };
using (var reader = XmlReader.Create("export.xml", settings))
{
  reader.ReadToFollowing("rows");
  reader.Read(); // forward to the first row
  while (reader.Name == "row")
  {
    var elem = (XElement)XElement.ReadFrom(reader);
    int id = (int)elem.Attribute("id");
    string name = (string)elem.Element("name");
    int? hired = (int?)elem.Element("hired");
    Console.WriteLine("We would now import id {0} name {1} hired {2}", id, name, hired);
  }
  reader.ReadEndElement(); // eat the </rows>
}

A real example may contain many more nested elements, but the principle is the same where smaller inner chunks of XML are read into an XElement for convenient processing.

Wednesday, April 1, 2015

Drop data from Windows

This is a reminder to myself about what data is available when you drag-drop Windows Explorer files or folders onto a WPF control. I used this code to intercept and dump the Drop event:

private void ListBox_Drop(object sender, DragEventArgs e)
{
  foreach (string format in e.Data.GetFormats())
  {
    Trace.Write(format.PadLeft(22) + " ");
    object o = e.Data.GetData(format);
    if (o is string[])
    {
      Trace.WriteLine(string.Join(" + ", (string[])o));
    }
    else if (o is MemoryStream)
    {
      var ms = (MemoryStream)o;
      byte[] buff = new byte[ms.Length];
      ms.Read(buff, 0, (int)ms.Length);
      Trace.WriteLine(string.Format("[{0}]{1}",
          buff.Length,
          BitConverter.ToString(buff, 0, Math.Min(1024, buff.Length)).Replace("-", "")));
    }
    else
    {
      Trace.WriteLine(string.Format("Other {0}", o));
    }
  }
}

The number of formats is unpredictable and seems to depend upon whether you drop one or more objects, or if they have long names or not. I dropped three long name files and found this data (truncated):

   Shell IDList Array [549]0300000014000000A7000000230100008F01000014001F50E04FD020EA…
UsingDefaultDragImage [4]01000000
        DragImageBits [36896]60000000600000003000000059000000BD0C05D000000000FFFFFFFF…
          DragContext [16]00000000010000000000000000000000
DragSourceHelperFlags [4]01000000
      InShellDragLoop [4]00000000
             FileDrop K:\dev_experiments\sk-security-tiers.nuspec + K:\dev_experiment…
            FileNameW K:\dev_experiments\sk-security-tiers.nuspec
             FileName K:\DEV_EX~1\SK-SEC~1.NUS
     IsShowingLayered [4]00000000
           DragWindow [4]10042200
     IsComputingImage [4]00000000
      DropDescription [1044]FFFFFFFF0000000000000000000000000000000000000000000000000…
      DisableDragText [4]01000000
        IsShowingText [4]00000000
    ComputedDragImage [4]BD0C05D0