StudioShell Update and a Related Effort

§ January 27, 2013 18:05 by beefarino |

Software and computing_Console_128I’m prepping a release of StudioShell by the end of January.  This release will include overdue support for Visual Studio 2012, and contain a ton of small fixes (and a few rather large ones).

As I wrap up this 1.5 beta release, I'm vacillating with certain new features I'm not sure everyone would want.  For instance, in my local StudioShell profile script, I've added menu items for projects, code elements, and files that allow me to quickly mount the studioshell prompt to that location.  E.g., right-click on a class, select "Mount this Class", and StudioShell magically does a push-location to the class location in the code model tree.  Similarly, right-click on the class, select “Mount this File Location,” and StudioShell changes location to the file system folder containing the code file.

There’s a ton of these little things floating about.  While these are useful, I don't think they necessarily need to be made a part of StudioShell.  I think they deserve their own place in the community.  So I’m creating a new StudioShell.Contrib project to collect these little helpers.  I’m not sure where this project will be hosted yet – either alongside StudioShell on CodePlex or perhaps on GitHub - your input is welcome.

UserVoice: PowerShell as a Project File Format

§ January 12, 2013 13:03 by beefarino |

I found an existing suggestion on uservoice that closely mirrors my previous post about using PowerShell as a Visual Studio project and build scripting platform.

Since my post has raised a lot of voices, I thought I’d share the link to this suggestion so  readers would be able to vote the suggestion up:

So if you agree, please spend some votes on this suggestion, and thank you!

A Feature Request for Visual Studio v.Next

§ January 9, 2013 14:56 by beefarino |

simpleisbetterThose of you that know me well know I don’t have many strong opinions.  I tend to keep multiple perspectives and work in whatever limits are provided.  So when I do express a strong opinion, it tends to be backed up with experience and reason (or at times, alcohol).

With that out of the way, let me express the only feature I really desire in the next version of Visual Studio:

Replace the format of all project and solution files with PowerShell scripts.

I hear you groaning – just hear me out.  I have many reasons for wanting this – too many to list all but the highlights here.  In a nutshell they all boil down to the notion of simplicity.

Expressing Data and Logic

A build has two broad parts: data that defines WHAT to build, and logic that defines HOW to build it.  E.g., your typical C# project file contains a list of source files and project/assembly references, and a set of instructions for accomplishing specific things with that data – such as producing assemblies or deploying a website.

It’s simple to express data in PowerShell.  You can declare arrays and hashtables inline.  You can create complex object hierarchies if needed. 

It also simple to express data in XML.  After all, that’s what XML is for.

It’s simple to express logic in PowerShell; like data and XML, expressing logic is what a programming language is designed to do.  However, expressing logic in XML is … well, “cumbersome” is a generous word.  “Obtuse” is perhaps a better choice.

Customizing the Build

It gets worse when you require custom build steps.  Getting logic into the MSBuild XML schema requires a lot of ceremony that serves nothing outside of MSBuild.  Consider the process:

  1. Implement your build task in C# (pulling in all the necessary bits to make MSBuild recognize your task)
  2. Import the DLL into your project using the <Import /> element
  3. Add the XML necessary to get your task working in the build
  4. Reload the project in Visual Studio
  5. Address any security warnings that pop up related to the new “unknown” task you added

At this point, the logic of your custom build task is about as far away from the build as it can get – inside of a binary dll that now must be packed around with the project file.  You have literally no inroad to the task in the same band as the build – all information about using the task (its name, dependencies, parameters, outputs, etc) must be communicated elsewhere.

Now, look at customization process for the PowerShell project file:

  1. Modify the PowerShell build script
  2. There is no step 2.  Again, simplicity.

Moreover, PowerShell is explicitly transparent – documentation is a get-help command away, and the built-in discovery mechanisms let you know what’s there.  Plus, the build logic stays with the build.

Running the Build

Here’s where my opinion really polarizes.  I get irate when a software project can only build inside Visual Studio.  Recent examples of my experiences here include Azure deployments and SCOM management packs.  It’s not that I mind the experience of pushing the Deploy button and having a magical process ensue that mystically transfers and configures an entire website and database – quite the contrary I want that experience.  The thing is, I want it everywhere, not just in Visual Studio.  What exactly constitutes “everywhere?”  For starters, I want the same build experience in:

  • Visual Studio
  • the shell
  • my co-worker’s machine
  • a fresh VM image
  • the automated CI server or build farm

If the build was “just PowerShell,” this would be a piece of cake.  In fact, I’ve taken to using PSake to drive my builds these days for this very reason – so I can maintain a consistent expectation of the build from one location to the next. 

What it Might Look Like

I dunno.  Let’s see what I can do with a default CSharp class library project.  Here’s the original MSBuild project file:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="">
   3:   <PropertyGroup>
   4:     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
   5:     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
   6:     <ProductVersion>8.0.30703</ProductVersion>
   7:     <SchemaVersion>2.0</SchemaVersion>
   8:     <ProjectGuid>{5C818DB6-C86B-4B05-AF13-A4CF46C8ACA9}</ProjectGuid>
   9:     <OutputType>Library</OutputType>
  10:     <AppDesignerFolder>Properties</AppDesignerFolder>
  11:     <RootNamespace>ClassLibrary1</RootNamespace>
  12:     <AssemblyName>ClassLibrary1</AssemblyName>
  13:     <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
  14:     <FileAlignment>512</FileAlignment>
  15:   </PropertyGroup>
  16:   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
  17:     <DebugSymbols>true</DebugSymbols>
  18:     <DebugType>full</DebugType>
  19:     <Optimize>false</Optimize>
  20:     <OutputPath>bin\Debug\</OutputPath>
  21:     <DefineConstants>DEBUG;TRACE</DefineConstants>
  22:     <ErrorReport>prompt</ErrorReport>
  23:     <WarningLevel>4</WarningLevel>
  24:   </PropertyGroup>
  25:   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
  26:     <DebugType>pdbonly</DebugType>
  27:     <Optimize>true</Optimize>
  28:     <OutputPath>bin\Release\</OutputPath>
  29:     <DefineConstants>TRACE</DefineConstants>
  30:     <ErrorReport>prompt</ErrorReport>
  31:     <WarningLevel>4</WarningLevel>
  32:   </PropertyGroup>
  33:   <ItemGroup>
  34:     <Reference Include="System" />
  35:     <Reference Include="System.Core" />
  36:     <Reference Include="System.Xml.Linq" />
  37:     <Reference Include="System.Data.DataSetExtensions" />
  38:     <Reference Include="Microsoft.CSharp" />
  39:     <Reference Include="System.Data" />
  40:     <Reference Include="System.Xml" />
  41:   </ItemGroup>
  42:   <ItemGroup>
  43:     <Compile Include="Class1.cs" />
  44:     <Compile Include="Properties\AssemblyInfo.cs" />
  45:   </ItemGroup>
  46:   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  47:   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
  48:        Other similar extension points exist, see Microsoft.Common.targets.
  49:   <Target Name="BeforeBuild">
  50:   </Target>
  51:   <Target Name="AfterBuild">
  52:   </Target>
  53:   -->
  54: </Project>

And here’s some PowerShell I hacked up to represent the same thing:

   1: param(
   2:     $configuration = 'debug',
   3:     $platform = 'anycpu'
   4: );
   6: $outputType = 'library'
   7: $projectGuid = [guid]::newguid('5C818DB6-C86B-4B05-AF13-A4CF46C8ACA9');
   8: $targetFrameworkVersion = 'v4.0'
  10: $errorReport = 'prompt';
  11: $warnLevel = 4;
  13: switch ( "$configuration|$platform" ) {
  14:     'debug|anycpu' {
  15:         $optimize = $false;
  16:         $outputPath = 'bin\debug';
  17:         $defines = 'debug','trace';
  19:         $debugSymbols = $true;
  20:         $debugType = 'full'; 
  21:     }
  23:     'release|anycpu' {
  24:         $optimize = $true;
  25:         $outputPath = 'bin\release';
  27:         $debugSymbols = $true;
  28:         $debugType = 'pdbonly'; 
  29:     }
  31:     default {
  32:         throw "$buildFlavor is not a valid build configuration"
  33:     }
  34: };
  36: $assemblyReferences = @(
  37:     "System", "System.Core", "System.Xml.Linq", 
  38:     "System.Data.DataSetExtensions", 
  39:     "Microsoft.CSharp", "System.Data", "System.Xml"
  40: );
  42: $sourceFiles = @(
  43:     "Class1.cs",
  44:     "Properties\AssemblyInfo.cs"
  45: );
  47: import-module psbuild;
  48: function invoke-BeforeBuild {}
  49: function invoke-AfterBuild {}

Granted, I’m not putting much thought into this conversion – but even as is, this script lends itself to many more possibilities than its MSBuild counterpart.  For example, if you wanted to run your own static analysis on the source code for the project, you could dot-source this file and reference the $sourceFiles variable in your own script…

Anyway, opinion expressed.  Back to work.