A simple script that cleans and builds visual studio solution with psake

psake is a simple build automation tool written in powershell, and works well on Microsoft platform.

This is a really simple example that cleans bin folder, run msbuild to build the solution, and clean up pdb and xml files afterward.


properties {
    $BuildConfiguration = if ($BuildConfiguration -eq $null ) { "debug" } else {     
        $BuildConfiguration }
    $BuildScriptsPath = Resolve-Path .
    $base_dir = Resolve-Path ..
    $packages = "$base_dir\packages"
    $build_dir = "$base_dir\Sushiwa\bin"
    $sln_file = "$base_dir\Sushiwa.sln"
}

task default -depends CleanUp, Compile

task CleanUp {
    @($build_dir) | aWhere-Object { Test-Path $_ } | ForEach-Object {
    Write-Host "Cleaning folder $_..."
    Remove-Item $_ -Recurse -Force -ErrorAction Stop
    }
}

task Compile {
    Write-Host "Compiling $sln_file in $BuildConfiguration mode to $build_dir"
    Exec { msbuild "$sln_file" /t:Clean /t:Build /p:Configuration=$BuildConfiguration 
        /m /nr:false /v:q /nologo /p:OutputDir=$build_dir }

    Get-ChildItem -Path $build_dir -Rec | Where {$_.Extension -match "pdb"} | Remove-Item
    Get-ChildItem -Path $build_dir -Rec | Where {$_.Extension -match "xml"} | Remove-Item
}
A simple script that cleans and builds visual studio solution with psake

Writing a powershell cmdlet…

A cmdlet (reads “command-lit”) is a lightweight command used in Powershell environment. The Powershell runtime invokes cmdlets within the context of automation scripts that are provided at the command line.

Cmdlets perform an action. Through Powershell pipiline, they can return a .NET object.

Let’s look at the code.

[Cmdlet(VerbsData.Publish, "Message", DefaultParameterSetName = IdRangeParameterSet, SupportsShouldProcess = true)]
public class EventCmdlet : Cmdlet
{
    internal const string IdRangeParameterSet = "IdRange";
    const string DateRangeParameterSet = "DateRange";

    [Parameter(HelpMessage = "The source for the messages.", Mandatory = true, Position = 0)]
    public string Source { get; set; }

    [Parameter(HelpMessage = "The single audit entry id", Mandatory = true, ParameterSetName = IdParameterSet)]
    public int? SingleId { get; set; }

    //this is to support WhatIf in unit tests. (In the context of the tests ShouldProcess would return always false)
    public Func<bool> ShouldProcessAction = () => true;

    protected override void ProcessRecord()
    {
        var shouldProcess = ShouldProcessAction.Invoke();
        _eventManager.ShouldProcess = shouldProcess;

        var source = _sourcesEvents.FirstOrDefault(s => s.Name.ToLower() == Source.ToLower());

        if(source == null)
        {
            _logger.ErrorFormat("The specified source {0} was not found", Source);
            return;
        }
      
        if(SingleId.HasValue)
        {
            _logger.InfoFormat("processing a message by a single Id of {0} ", SingleId.Value);
            _eventManager.PublishEvent(source, SingleId.Value);
        }
    }
}

Cmdlet attribute

[Cmdlet(VerbsData.Publish, "Message", DefaultParameterSetName = IdRangeParameterSet, SupportsShouldProcess = true)]
public class EventCmdlet : Cmdlet

In order to declare a cmdlet class as a cmdle, Cmdlet attribute is required. So you start your class with this attribute.

Cmdlet parameter

    [Parameter(HelpMessage = "The source for the messages.", Mandatory = true, Position = 0)]
    public string Source { get; set; }

    [Parameter(HelpMessage = "The single audit entry id", Mandatory = true, ParameterSetName = IdParameterSet)]
    public int? SingleId { get; set; }

    [Parameter(HelpMessage = "The starting audit entry id", Mandatory = true, ParameterSetName = IdRangeParameterSet, Position = 1)]
    public int? FromId { get; set; }

    [Parameter(HelpMessage = "The ending audit entry id", Mandatory = true, ParameterSetName = IdRangeParameterSet, Position = 2)]
    public int? ToId { get; set; }

The public properties define the parameters available to the user that is running the application. You can use positional parameters which are handy if you need to put range values. Parameter set can be used to perform a specific action.

ShouldProcess feature

[Cmdlet(VerbsData.Publish, "Message", DefaultParameterSetName = IdRangeParameterSet, SupportsShouldProcess = true)]
public class EventCmdlet : Cmdlet

...

    //this is to support WhatIf in unit tests. (In the context of the tests ShouldProcess would return always false)
    public Func<bool> ShouldProcessAction = () => true;

...

    protected override void ProcessRecord()
    {
        var shouldProcess = ShouldProcessAction.Invoke();
        ....
    }

Powershell allows you to write cmdlets that prompt for user feedback. It should be declared in the Cmdlet attribute and you must call ShouldProcess method. (For more info, Requesting Confirmation from Cmdlets)

Processing method

There are Processing Methods like BeginProcessing, ProcessRecord, and EndProcessing. Typically, you override ProcessRecord, as it is called for every record that the cmdlet processes. BeginProcessing and EndProcessing are called only one time.

Running your cmdlet with F5 / Ctrl F5, visual studio run command

It will be very convenient if you can run your cmdlet without manually opening a shell and importing your module. In your cmdlet project, open property of the project and set the following sections on Build tab.

  • Start external program: c:\windows\syswow64\WindowsPowerShell\v1.0\powershell.exe
  • command line arguments: -noexit -command import-module .\Event.psd1

Resolving dependencies

If your cmdlet performs a rather complex action, it will depend on various other assemblies. You need to inject those dependent binaries into your cmdlet. Also, in order to unit-test your code, you need to mock out those assemblies too.

Unfortunately, powershell cmdlet can only be invoked through the default constructor, and as a result, you cannot pass dependencies through its default constructor.

First, you need psd1, which is a powershell module definition. You can create one by using New-ModuleManifest. In the manifest file, you can specify RequiredAssemblies.

# Assemblies that must be loaded prior to importing this module
RequiredAssemblies = 'Test.Framework.dll',
                     'EventR.Lib.dll',
                     'EventR.Configuration.dll',
                     'RabbitMQ.Client.dll',
                     'Jayrock.Json.dll'

Then use your IoC as service locator in the default constructor.

public EventCmdlet()
{
    Container.InitializeInstance<EventConfigurationContributor>();
    _events = Container.Instance.ResolveAll<IEvents>().ToList();
    _eventManager = Container.Instance.Resolve<EventManager>();
    _logger = Container.Instance.Resolve<ILoggerFactory>().Create(MethodBase.GetCurrentMethod().DeclaringType);
    ShouldProcessAction = () => ShouldProcess("Messages");
}
Writing a powershell cmdlet…

Powershell and me

It’s an interesting scripting language. Well, scripting has been for a long time in *nix systems, and I feel it’s quite late that Windows catch up now. But still it’s better than nothing, and I appreciate Microsoft for it. MS seems to be losing it’s power to be blamed for it’s evil or laziness in this post-pc era, anyway.

Learning Powershell

The best way to learn something is 1) to use it everyday (like a young boy practices kung fu while wiping his master’s car window in “Best kid”, I believe. In that sense, I’m lucky that I uses it everyday at Huddle (at the moment, as of 2012).

Another good ways, not as good as the first one, are to read good books about the topic and to answer others’ questions on Q & A sites like StackOverflow. Probably, using it everyday and reading a book about it would be a fantastic combination.

I’m reading Windows Powershell for Developers by Douglas Finke. Very thin, but interesting, informative, and easy read with causual 1:1 chat style.

useful resources

Installing Powershell

It comes preinstalled with Windows 7, Windows Server 2008 R2, Windows 8, and Windows Server 2012 (if you have)
If you want try PowerShell v3, you can download it at http://www.microsoft.com/en-us/download/details.aspx?id=29939

PowerShell has execution policy. By default, it can’t load the profile and others.
By running get-executionpolicy, you can check the policy on your machine.
The recommended setting to begin with is “remotesigned”.

PS D:\dev> get-executionpolicy
RemoteSigned
PS D:\dev>set-executionpolicy remotesigned

Determining the version of PowerShell installed

$PSVersionTable will be the usual answer, as you do like this.

PS D:\dev> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      2.0
PSCompatibleVersions           {1.0, 2.0}
BuildVersion                   6.1.7601.17514
PSRemotingProtocolVersion      2.1
WSManStackVersion              2.0
CLRVersion                     4.0.30319.269
SerializationVersion           1.1.0.1

But $PSVertionTable was introduced with PowerShell version 2. so, if it doesn’t work, you need to do $Host.Version

PS D:\dev> $Host.Version

Major  Minor  Build  Revision
-----  -----  -----  --------
2      0      -1     -1

simple scripts with files, process …

the examples below are from Jeff Alexander’s screencast.

# showing the list of files
Get-Childitem c:\

# format the output with pipe |
dir c:\windows\explorer.exe | Format-List *

# Choose the columns that you want
dir c:\ | Format-Table Name, Extension, CreationTime

# Lists only first 10 lines
Get-Childitem c:\windows\system32\*.dll | select-object -first 10

# Grouping by file extension
Get-Childitem c:\windows\system32 | Group-Object extension

# Lists files updated in 10 days, leveraging DateTime
Get-Childitem c:\windows\system32 | Where-Object {$_.LastWriteTime -gt $($(Get-Date).adddays(-10))}

# Sort process by process id
Get-Process | Sort-Object ID

# WMI to access system information
Get-WmiObject win32_operatingsystem

# Access registries
Get-Childitem hkcu:\Software\Microsoft

# Stop a process
Get-Process notepad | stop-process

Pipeline

from PowerShell Essentials for the Busy Admin (Part 2)

It allows ability to easily work at command line

  • to retrieve items and work on them
  • to filter out data
  • to persist information
  • and to format output
# Start Process
"notepad", "calc" | foreach { start-process $_ }

# Filter processes using too much CPU or Memory
Get-Process | where { $_.pm -gt 20MB }
Get-Process | where { $_.cpu -gt 10 }

# Sort processes
Get-Process | sort cpu -Descending

# Sort event log entries
Get-EventLog -LogName application -EntryType error | sort source | group source

# Pipe to present
get-process | format-table name, id -AutoSize
Get-Service | where {$_.status -eq "running" } | Format-List *

# Pipe to persist information
get-process | format-table name, id -AutoSize | out-file c:\temp\processtable.txt
gps | select name, id | Export-Csv -Path c:\temp\processCsv.csv

Handy commands

# Measure-command measures the performance of your command.
Measure-command {Get-EventLog -LogName application -EntryType error | sort source | group source}

# h lists all of your commands in the console
PS D:\dev\> h

  Id CommandLine
  -- -----------
  11 Get-Childitem c:\
  12 get-childitem c:\
  13 get-childitem c:\ | Format-List *
  14 dir c:\windows\explorer.exe | Format-List *
  15 dir \ | Format-Table Name, Extension, CreationTime
  16 dir c:\ | Format-Table Name, Extension, CreationTime
  17 Get-Childitem c:\windows\system32\*.dll | select-object -first 10
  18 Get-Childitem c:\windows\system32 | Group-Object extension

Powershell and me

Open sublime text 2 from command line on windows

In Mac, you can open sublime text 2 from terminal, and it’s really handy.

I wanted to replicate the same thing on windows, so wrote a simple script that enables it.
Once you run this script, you can do the following things from command line.

subl // this will open sublime text 2
subl . // open sublime text 2 with folder side bar.

For example, if you do it in c:\dev\trunk, sublime text will open with folder that is based on c:\dev\trunk.

To run the script, copy the below, and in the command prompt (not powershell prompt), right click and paste it. You must have powershell installed if you are not on windows 7 or Vista

 
 
REM Begin Copy
powershell
Set-Content "C:\Program Files\Sublime Text 2\subl.bat" "@echo off"
Add-Content "C:\Program Files\Sublime Text 2\subl.bat" "start sublime_text.exe %1"
if (!($Env:Path.Contains("Sublime"))) {[System.Environment]::SetEnvironmentVariable("PATH", $Env:Path + ";C:\Program Files\Sublime Text 2", "Machine")}
exit
REM End Copy

You will have to close the command prompt as the path change would take effect when a new command prompt starts.

Open sublime text 2 from command line on windows

Powershell script tips

Write to a file

Use Set-Content and Add-Content

Set-Content "C:\Program Files\Sublime Text 2\subl.bat" "@echo off"
Add-Content "C:\Program Files\Sublime Text 2\subl.bat" "sublime_text.exe %1"

Adding to environmental path variable

from http://softwaresalariman.blogspot.co.uk/2008/01/add-path-environment-variable-in.html

Set-Content "C:\Program Files\Sublime Text 2\subl.bat" "@echo off"
Add-Content "C:\Program Files\Sublime Text 2\subl.bat" "sublime_text.exe %1"
[System.Environment]::SetEnvironmentVariable("PATH", $Env:Path + ";C:\Program Files\Sublime Text 2", "Machine")
Powershell script tips