This post is english translation of my article for czech Technet Flash Magazine.

The main reason why Microsoft started working on PowerShell was to give administrators better tool for their tasks than they had. With PowerShell they can automate their daily tasks very easily. PowerShell proved that it is really amazing tools and that's why Microsoft integrated PowerShell to Windows Serve 2008, Windows 7 or SQL Server 2008. There are other libraries for IIS, Exchange, VMware management and many more others.

So, when PowerShell is so great why only administrators should use it? Why not programmers or advanced users? If you have created any .bat file or were scriptinng in old command line, Powershell is the right choice for you. It's syntax is much more comfortable and related to common programming languages. Besides that there are very interesting commands (cmdlets) integrated into the core.

As you guess this article is written for programmers. It should point out some tips there could make their work easier.

A simple console application problems

First of all I will emphasize the main reason why PowerShell is so popular (among programmers) – tasks that you normally solve with a single-purpose command line C# application can be solved in PowerShell very easily. What does it mean "very easily"? You only need to create a ps1 file and then run it in PowerShell console. (of course using script is not necessary, you can write the commands directly into the console)

How we did it without PowerShell? In Visual Studio we created a C# command line project and add some code into generated Main method. We needed to parse arguments again, browse the directory tree manually as in our previous project again, open/read files, process errors and exceptions etc. Then build the project and run the exe file. It is so tedious for one simple task and there are lots of files (source, project, etc.).

C# is great for complex processing where we can use all its nature. If you compare it to PowerShell there is significant code noise. Generally speaking if the task can be done in .NET, then it can be done in PowerShell. The typical task might be: go through all the files in given directory, open them and replace string XYZ with ABC.

Text replacement was just an example. Some time ago I saw someone who was trying to deal with duplicate pictures. He had many pictures from his camera and some of them were duplicated in more directories. He wanted to find the duplicates and ignore Exif because some of them were already edited. If you are interested in the resulting script, just have a look at stack overflow.

As a very short example let's try to sort pictures by their orientation (vertical vs. horizontal) and sort the result by the orientation and name. The pictures can be anywhere in the directory structure:

$d = gci D:\temp\ dscn*.jpg -rec | 
    % {
       $b = new-object System.Drawing.Bitmap $_.FullName
       New-Object PsObject -prop @{
        Orientation=if($b.Height -gt $b.Width) { 'v' } else {'h' };
        Path=$_.fullname }
       $b.Dispose()
    } | 
    sort -Property Orientation,Path

The code could be even shorter but then it would not be readable.
Can you see how easily we can gain the results? Did I hear woow? ;)
You can find many similar problems in you daily life, just use your phantasy. In the following I'll cover some concrete howtos and PowerShell features.

Exploring libraries

Sometimes you have to deal with unknown assemblies (or COM) or you have only some examples that you would like to check. For example I found a library that works with StackOverflow API. You don't know nothing about it, just some examples. So you download the zip and...

PS> Add-Type -Path c:\temp\StackOverflowApi\StackExchangeApi.dll
PS> $uri = new-object System.Uri 'http://code.google.com/p/stackexchange-api/'
PS> [StackExchangeApi.SXAPI]::Initialize('SXAPI Example Code', $uri)

We initialized the library as in the example and lets have a look at how we can get a user:

PS> [STackExchangeApi.User] | gm -static

   TypeName: StackExchangeApi.User
Name            MemberType Definition
----            ---------- ----------
Equals          Method     static bool Equals(System.Object objA, System.Object objB)
GetUsersForName Method     static System.Collections.Generic.ICollection`1[[StackExcha...
GetUserWithId   Method     static StackExchangeApi.User GetUserWithId(long id, StackEx...
ReferenceEquals Method     static bool ReferenceEquals(System.Object objA, System.Obje...

PS> [STackExchangeApi.User]::GetUsersForName

MemberType          : Method
OverloadDefinitions : {....GetUsersForName(string name, StackExchangeApi.StackExchangeSite site)}
...

Ok, that means that there is something new - StackExchangeApi.StackExchangeSite. What is it?

PS>  [STackExchangeApi.StackExchangeSite] | gm -sta

   TypeName: StackExchangeApi.StackExchangeSite

Name              MemberType Definition
----              ---------- ----------
...
ToObject          Method     static System.Object ToObject(type enumType, System.Object v...
Meta              Property   static StackExchangeApi.StackExchangeSite Meta {get;}
ServerFault       Property   static StackExchangeApi.StackExchangeSite ServerFault {get;}
StackOverflow     Property   static StackExchangeApi.StackExchangeSite StackOverflow {get...
SuperUser         Property   static StackExchangeApi.StackExchangeSite SuperUser {get;}

Ok, we got the idea. It is the site where we want to look for the user. Let's try it.

PS> [STackExchangeApi.User]::GetUsersForName('skeet', 'StackOverflow')

Site              : StackOverflow
Id                : 22656
Badges            : {Nice Answer, Nice Answer, Nice Answer, Nice Answer...}
RecentActivity    : {2341601, 2341288, 2341035, 2340025...}
ReputationGraph   : StackExchangeApi.ReputationGraph
Name              : Jon Skeet
Answers           : {7244, 9033, 131871, 137448...}
Questions         : {194484, 215548, 236907, 247621...}
FavoriteQuestions : {212726, 282329, 348175, 406760...}
Gravatar          : http://www.gravatar.com/avatar/6d8ebb117e8d83d74ea95fbdd0f87e13?s=50&d=ide...
Reputation        : 141022
GoldBadgeCount    : 29
SilverBadgeCount  : 542
BronzeBadgeCount  : 1073

# and 4 more users are returned

PowerShell is based on .NET so you can easily look at the properties, call the methods, even use events etc. Do you know any quicker way how to experiment with unknown API?

The same holds for COM objects. Look at quick example how to work with Skype.

Working with XML

One of the feature that you will like is how PowerShell deals with XML. Imagine that you can work with XML like with an object – attributes become string properties and nested elements become object properties. Sounds familiar? Yes, it is similar to (de)serialization. But when deserializing a new object of some type is created. When working with XML from PowerShell we get XmlDocument which is very well known in .NET world.

It's time for an example. Let's create a new test file.

PS> @"
<root>
   <article date="2010-12-01">
    <name>Discover new dimensions</name>
    <body>Discover them now. Go!</body>
   </article>
   <article date="2000-01-01">
    <name>Future</name>
    <body>what will be with us in ten years?</body>
   </article>
</root>
"@ | Set-Content c:\temp\flashtest.xml

We will read the file and cast it to xml (this is how accelerators work) and look at the result.

PS>$x = [xml](gc c:\temp\flashtest.xml)
PS>$node = $x.root.article | Where-Object { [datetime]$_.date -lt [datetime]'2005-01-01' }
PS>$node
date         name                     body
----         ----                     ----
2000-01-01   Future                   what will be with us in ten years?
PS>$node.name = 'Near ' + $node.name
PS>$x.root.article[0].date = (get-date).ToString('yyyy-MM-dd')

Variable $x is of type XmlDocument, whereas $node of type XmlElement. If you want to set a new value of an attribute or of an element string, just use simple assignment. If you need to add a new element, then we have to switch to .NET methods.

PS>$note = $x.CreateElement('note')
PS>$note.InnerText = 'poor content'
PS>$node.AppendChild($note)
PS>$x.root.article[1]
date        name             body                                note
----        ----             ----                                ----
2000-01-01  Near Future      what will be with us in ten years?  poor content

Sometimes you would like to use XPath. In V1 you needed to do it using some .NET objects. In V2 it is much more easier, we were given cmdlet Select-Xml.

PS>Select-Xml -Xml $x -XPath '//article[contains(body/text(),"ten")]'
PS>#usage of namespace
PS>$ns = @{ e = "http://www.w3.org/1999/xhtml" } 
PS>Select-Xml -Path $somePath -Xpath //e:div[@id] -names $ns

If you wonder what can be used as a source for Select-Xml, just have a look at Get-Help Select-Xml. You will see three options: xml object(s) itself (as in the example above), path to file(s) or xml(s) as a string.

After you are done and you want to save the result, you have to use .NET method again:

PS>$x.Save('c:\temp\res.xml')

Regex tester

PowerShell is very handy when there is a need to test regular expressions. I won't describe what regular expressions are and what they solve. You can find more info at Regular-expressions.info.
I'll show you how to check that the regular expression does what expected.

Checking the regex in PowerShell is the quickest way if you have PowerShell console open (note: if you lost the console among the other windows, have a look at AutoHotkey). It is much more lengthy to test regexes on web online or run special programs. There are several possibilities.

Operator -match

This is the simplest approach. Left operand is (array of) string(s) and right operand is the regular expression.

# silly regex just for demo
PS>'jeho email je karel@novak.cz' -match ' \w+@[a-zA-Z_]+\.(?<d>[a-zA-Z]{2,3})' 
True
PS>$matches
Name                           Value
----                           -----
d                              cz
0                              karel@novak.cz

PowerShell uses collection $matches with regex groups from last evaluation with -match operator. Group 0 contains all the matching string. Regex is not case sensitive. In fact it corresponds to regular expression without any special options.

In case you will need case sensitivity, use operator -cmatch.

Operator -match can be also used as a filter. Open your console and try it, you will figure it out immediatelly:

PS>'1a','2b','1c' -match '1\w'
PS>'1a' -match '1\w'

Accelerator [regex]

You can use [regex] to create a regular expression object:

PS>$r = [regex]'^\w+@[a-zA-Z_]+\.(?<d>[a-zA-Z]{2,3})$'
PS>$r.GetType().FullName
System.Text.RegularExpressions.Regex

It creates instance of class Regex (well known for .NET programators) and saves it to variable $r. Again there are no special options. Let's have a look on the options:

PS>$r | fl Options

You can work with the object as you know from .NET. In case you don't remember some signatures, you may have a look at the members:

PS>$r | gm
   TypeName: System.Text.RegularExpressions.Regex
Name                MemberType Definition
----                ---------- ----------
...
GetGroupNames       Method     string[] GetGroupNames()
GetGroupNumbers     Method     int[] GetGroupNumbers()
…
IsMatch             Method     bool IsMatch(string input), bool IsMatch(strin
Match               Method     System.Text.RegularExpressions.Match Match(st
Matches             Method     System.Text.RegularExpressions.MatchCollection
Replace             Method     string Replace(string input, string replacemen
...

Object creation

The last option, that is closely related to the previous one, is to create regex using cmdlet New-Object. Then you can use any constructor of Regex class – e.g. specify options like multiline, singleline etc.

PS>$opts = [System.Text.RegularExpressions.RegexOptions]'MultiLine,SingleLine'
PS>$r = new-object Text.RegularExpressions '^\w+@[a-zA-Z_]+?\.(?<d>[a-zA-Z]{2,3})$',$opts

Look at the interesting syntax sugar how to specify regex options – I haven't found it documented anywhere. I found it accidentally at Oisin's blog Using Enumerated types (Enums) in PowerShell.

Generally it's a joy to work with enums in PowerShell. You don't need to specify type of the enum, just its name is enough.
Furthermore – try this:

PS>[string]::Compare('a','a',[Globalization.CultureInfo]::CurrentCulture, 'test')
Cannot convert argument "3", with value: "test"..... The possible enumeration values are "None,
IgnoreCase, IgnoreNonSpace, ... Ordinal"."
PS>[string]::Compare('a','a',[Globalization.CultureInfo]::CurrentCulture,'IgnoreCase')

PowerShell will suggest you valid values – so you don't need to look into documentation! This will speed up your productivity if you work with enums.

Note #1: You can of course just use static method of class [regex]:

PS>[regex]::IsMatch('karel@novak.cz', '^\w+@[a-zA-Z_]+\.(?<d>[a-zA-Z]{2,3})$')

Note #2: You know operator -match. There is another very useful operator -replace, that is used for text replacement. Here is just a quick and simple illustration:

PS>(Get-Content c:\test.txt) -replace 'abc','def' | Set-Content c:\test.txt

Function FindR

Later in section about clipboard I use FindR. It's a tiny filter that returns data that match given regular expression.

filter FindR($regex) {
	[Regex]::Matches($_, $regex, 'IgnoreCase') | % { $_.Value }
}

You can pipe almost anything to FindR. PowerShell will try to convert it to string because method Matches expects string as its parameter. If we pipe objects, PowerShell runtime decides how to convert the objects to string.

For example dir | findr '.*gs.*' will work, but it will return only names of files and directories (that match the regex).

But Get-WinEvent -LogName Application -MaxEvents 10 | findr '.*instal.*' will not work, because PowerShell will pass string 'System.Diagnostics.Eventing.Reader.EventLogRecord' to method Matches. This string is probably just a result of ToString() called on objects from Get-WinEvent.

You are able to come up with some realistic scenarios for FindR, I'm sure. I use it when I need to parse logs from our customers. In the last case I had some file sizes in the log and I needed to find out average size and total sum of the sizes. To parse concrete sizes I used look behind.

gc c:\dev\WO\wpdataimport.log | 
  findr -r '(?<=Job content file size: )\d+' | 
  Measure-Object -Sum -Average

First command reads the file, the second one finds only lines that contain 'Job content file size' and returns only numbers that follow the string and the last command does the statistics. Quick and easy. This, Jane, this is PowerShell :)

Encoding, decoding, conversions …

Especially web developers need to work with base64 strings or encode/decode urls. You can do it of course via some online tools or specialized programs. But again, it is much more easier to add this functions to your profile and the conversions will be available all the time.

# imports assembly needed for url stuff
Add-Type -AssemblyName System.Web

function FromBase64([string]$str) {
  [text.encoding]::utf8.getstring([convert]::FromBase64String($str))
}
function ToBase64([string]$str) {
  [convert]::ToBase64String([text.encoding]::utf8.getBytes($str))
}
function UrlDecode([string]$url) {
  [Web.Httputility]::UrlDecode($url)
}
function UrlEncode([string]$url) {
  [Web.Httputility]::UrlEncode($url)
}
function HtmlDecode([string]$url) {
  [Web.Httputility]::HtmlDecode($url)
}
function HtmlEncode([string]$url) {
  [Web.Httputility]::HtmlEncode($url)
}

Admins working solely in command line might use this function that helps with searching on Google:

PS> function Run-GoogleQuery {
  Start-Process ('http://www.google.cz/search?q=' + (UrlEncode ($args -join " ")))
}

PS> Set-Alias qg Run-GoogleQuery
PS> qg this is test # runs default browser and searches for 'this is test'

I used HtmlEncode when I was working on this article and I needed to insert PowerShell code into <pre ..> tag.
With function clip (you will find it later) it was very easy:

HtmlEncode (clip) | clip

Conversion Html2Xml

Sometimes you will need to convert html to xml. Regexes? No! I hope you have heard that parsing HTML with regexes is very tricky. Instead, you may use free library SgmlReader.

function Convert-Html2Xml {
  param(
    [Parameter(ValueFromPipeline=$true)][object[]]$html
  )
  begin   { $sb = new-object Text.StringBuilder(20kb) }
  process { $html | % { $null = $sb.AppendLine($_) } }
  end {
    # no default namespace, thx.
    $str = $sb.ToString().Replace(' xmlns="http://www.w3.org/1999/xhtml"', '')
    Add-Type -Path G:\bin\SgmlReaderDll.dll 
    
    $sr = new-object io.stringreader $str

    $sgml = new-object Sgml.SgmlReader
    $sgml.DocType = 'HTML';
    $sgml.WhitespaceHandling = 'All';
    $sgml.CaseFolding = 'ToLower';
    $sgml.InputStream = $sr;

    $xml = new-object Xml.XmlDocument;
    $xml.PreserveWhitespace = $true;
    $xml.XmlResolver = $null;
    $xml.Load($sgml);

    $sgml.Close()
    $sr.Close()
    
    $xml
  }
}

There are two ways how to work with the function:

PS>$x1 = gc c:\temp\testhtmlsource.delete.html | Convert-Html2Xml
PS>$x1.Save('c:\temp\test1.xml')
PS>$x3 = Convert-Html2Xml (gc c:\temp\testhtmlsource.html)
PS>$x3.Save('c:\temp\test2.xml')

You can use the conversion in cases where given web site doesn't have public API and you need to work with it only through its WEB UI (nightmare!). One example could be translator http://www.slovnik.cz. Complete solution (quick & dirty) uses XPath via cmdlet Select-Xml.
Some other quick & dirty code? I built up one for my colleague who creates from time to time links to movies database CSFD manually. Don't look for beautiful code, you will find only automation.

And last quick example – you found a blog with many posts and all the posts contain full text. It can look like this. Rss feed doesn't help because it returns only some of them. You would like to see headings of the posts because you are just curious. Obviously you can scroll down and catch the headings. But what about this:

$xml = Convert-Html2Xml (download-page 'http://www.nivot.org/CategoryView,category,PowerShell.aspx' )
Select-Xml -Xml $xml -XPath '//div[@class="itemTitle"]/a/text()' | % { $_.Node.Value }

PowerShell 2.0 - About Dynamic Parameters
PowerShell 2.0 – Introducing the PModem File Transfer Protocol
PowerShell 2.0 - Enabling Remoting with Virtual XP Mode on Windows 7
PowerShell 2.0 goes RTM for ALL Platforms
PowerShell 2.0 - Module Initializers
PowerShell 2.0 – Getting and setting text to and from the clipboard
PowerShell 2.0 – Asynchronous Callbacks from .NET
PowerShell – Function Parameters &amp; .NET Attributes
PowerShell 2.0: A Configurable and Flexible Script Logger Module
....

Clipboard

For the first time you will wonder: "Clipboard? Why should I care?". I use it very often as a transport mechanism between an application and PowerShell. You have seen several times that I use clip. One more sample will follow:

I have a problem with deadlocks in sql server. I run sql server from console with switches for deadlock detections. After sql server prints some info about resolved deadlocks, I select the text in console, copy it and go to PowerShell. I would like to find out the SPIDs.
(clip) | FindR -r 'SPID: \d+' | select -unique
This will return IDs of all processes involved.

Take it just as an example, I know that I could use SQL analyzer.

And how the functions for clipboard management look like?

Add-Type –a system.windows.forms
function Set-ClipBoard { 
  param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)][object]$s
  )
  begin { $sb = new-object Text.StringBuilder }
  process { 
    $s | % { 
      if ($sb.Length -gt 0) { $null = $sb.AppendLine(); }
      $null = $sb.Append($_) 
    }
  }
  end { [windows.forms.clipboard]::SetText($sb.Tostring()) }
}
function Get-ClipBoard { 
  [windows.forms.clipboard]::GetText() 
}
# clip works as Get-Clipboard and Set-Clipboard; it depends on the context:
# gc c:\test.txt | clip 
# clip | sc c:\test.txt
function clip {
  param(
    [Parameter(Mandatory=$false,ValueFromPipeline=$true)][object]$s
  )
  begin { $sb = new-object Text.StringBuilder }
  process {
    $s | % { 
      if ($sb.Length -gt 0) { $null = $sb.AppendLine(); }
      $null = $sb.Append($_) 
    }
  }
  end {
    if ($sb.Length -gt 0) { $sb.Tostring() | Set-ClipBoard} 
    else                  { Get-ClipBoard  }
  }
}

Note that clipboard management is available only if PowerShell is run with -STA switch or in ISE environment. It's because it uses WinForms in the background.
In case you need to have clipboard available in MTA (default mode), look at PowerShell 2.0 – Getting and setting text to and from the clipboard
.

Is there something missing?

Yes, of course. There are some more topics that deserve its own article. There is a great cmdlet New-WebServiceProxy, you can work with SQL server (using .NET classes), you can create GUIs (WinForms or WPF), use remoting (for deployment and continuous integration), … and much more. Hopefully I will cover some of them next time.

You have seen basic features that I find very attractive. However, it's up to you if you will became fan of PowerShell or if you think that the features are not worth learning. But remember, that the most important reason why you should use PowerShell is the first part – quick and effective solution of common tasks. That's where PowerShell is excellent.

[22.02.2010] Pepa

Fun with PowerShell and Skype

When skyping with my boss I remembered that I can access Skype through COM interface. What can you do with it? You can start with some fun. You can look at it in action.

$skype = New-Object -ComObject Skype4Com.Skype
$timer  = New-Object Timers.Timer
$timer.Interval = 700
$job = Register-ObjectEvent $timer Elapsed -Action {$skype.ChangeUserStatus((1,2,4)[$global:i++%3])}
$timer.Enabled = $true

I think everything is pretty clear. The script changes each 700 milliseconds your status to Online/Away/DND, so the icon changes from green to yellow and red. Nice effect, isn't it?
It seems that it works only for some time. After several minutes your friends sometimes don't see the changes any more, even though it appears working at your computer.

Have fun with PowerShell!

Welcome to another quick & dirty example how you can use PowerShell. Once in a month my friend/colleague @nikdo sends an email about some movies from CSFD site. The email looks something like this (just a part of it):

Code

He writes the code by hand on move after another. In case there are more movies this could be boooring, agree?
Rather than doing it by hand you can create a PowerShell script. You won't be proud of it, but it will do its job.

function Process-Csfd {
    [cmdletbinding()]
    param($name, $dir)
    function downloader {
            $cli = New-Object net.webclient
            $cli.Headers = New-Object net.webheadercollection
            $cli.Headers.Add('User-Agent', 'Mozilla/5.0 (Windows; U; Windows NT 6.0; cs; rv:1.9.1.7) ' +
                'Gecko/20091221 Firefox/3.5.7 (.NET CLR 3.5.30729)')
            $cli.Headers.Add('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8')
            $cli.Headers.Add('Accept-Language', 'cs,en-us;q=0.7,en;q=0.3')
            $cli.Headers.Add('Accept-Encoding', 'gzip,deflate')
            $cli.Encoding = [Text.encoding]::UTF8
            $cli
        }

    function get-csfdImageUrl {
        param($url)
        $cli = (downloader)
        Write-Verbose "Downloading $url"
        $x = convert2xml ($cli.DownloadString($url))
        @{ImageUrl=(Select-Xml $x -xpath //table[`@background]).Node.Background }
    }
    function search-csfdMovie {
        param($what)
        Add-Type -AssemblyName 'system.web'
        $cli = (downloader)
        $url = "http://www.csfd.cz/hledani-filmu-hercu-reziseru-ve-filmove-databazi/?search=" `
            + [system.Web.Httputility]::UrlEncode($what)
        Write-Verbose "Searching csfd, url: $url"
        $x = convert2xml ($cli.DownloadString($url))
        $link = Select-Xml $x -XPath `
            '/html/body/table/tr[2]/td/table/tr/td/table/tr/td/table[2]/tr[2]/td/table/tr/td' |
            select -exp Node | 
            select -exp a | 
            ? { $_.'#text' -eq $what }
        if ($link.Count) {
            Write-Host "there are more links"
            $link | % { write-host $_.OuterXml }
            throw $link
        }
        @{Link='http://www.csfd.cz'+$link.href}
    }
    $where = search-csfdMovie $name
    $imgInfo = get-csfdImageUrl $where.Link
    New-Object PSObject -Property ($where + $imgInfo + @{Name=$what})
}

'Herkules 3D', 'Appaloosa', 'Bílý drak', 'Nerozhodný drak', 'Delfín Filip',`
'Wyatt Earp','Tropická bouře','Naprosto osvětleno','Maratónec' | 
    % { Process-Csfd -name $_ -dir 'c:\temp\dir' -verbose } | 
    % -begin { $r = '<html><body>' } `
      -process { $r += '<a href="{0}" style="display:block;float:left;margin:3px;" title="{1}">
          <img src="{2}" style="width:121px;height:180px;border-style:none" /></a>' `
        -f $_.Link,$_.Name,$_.ImageUrl } `
      -end { $r +'</body></html>' } |
    Set-Content c:\temp\csfd.html

It's very straightforward.

  • Function downloader creates a System.Net.WebClient that mimics browser so that the site doesn't refuses the download.
  • Function search-csfdMovie tries to find a movie and return a link to the movie (e.g. return http://www.csfd.cz/film/8083-maratonec-marathon-man/). Note that I use function convert2xml that you can get from previous post about dictionary. It just converts html to xml.
  • Function get-csfdImageUrl uses the link provided by search-csfdMovie and grabs image url of the movie (e.g. http://img.csfd.cz/posters/0/8083.jpg).
  • In the rest I pipe movies names to the Process-Csfd function, format the output html and store it in a html file.

Can you see the xpath in search-csfdMovie? This is the main reason why this approach is quick&dirty. There are no html classes or ids that can be used when constructing xpath. They maybe just wanted to stop such tools as we created now.
When the site changes its markup, the script will fail. But don't worry, it is very easy to repair it ;).

Saving to disk

In case you need to store the files in a directory, just use the following function and alter the code accordingly.

function download-csfdImage {
    param($url)
    $cli = (downloader)
    Write-Verbose "Downloading $url"
    $x =convert2xml ($cli.DownloadString($url))
    $image = (Select-Xml $x -xpath //table[`@background]).Node.Background
    Write-Verbose "Downloading $image"
    $path = $dir+[io.path]::GetFileName($image)
    $cli.DownloadFile($image, $path)
    @{Path = $path; ImageUrl=$url }
}

And that's all. Keep in mind, that for really simple tasks it is much more efficient to do them by hand then to write the script. However, isn't it fun?

I work as C# programmer and I sometimes need to deal with assemblies created by my colleagues. They are a separate team and they provide us with some assemblies that act as a middle layer between us and a windows service.

They don't sign their assemblies (but they will eventually, one day), so their assemblies are not so vulnerable to problems with assembly versions. That means that at runtime if assembly A references assembly B version 1.0.0.1 and there is only assembly B version 1.0.0.2, that doesn't cause them any problem.

However, we need to sign their assemblies via Signer. That is still no problem. But when the application is starting and assemblies are being loaded then when the assembly A tries to load assembly B version 1.0.0.1 and there is only version 1.0.0.2 an exception is thrown. The versions simply have to match.

This problems are caused by the fact that they compile their projects in an incorrect (incorrect for us ;)) order and some of their projects reference dlls in a common bin instead of related project.
The rest of the post is about how to go through the csproj files and explore the right build order so that the projects can be built with respect to their project and assembly dependencies (= references to common bin folder).

If you are only interested in a code and know where the problem is, just go to the code or download.

What causes multiple version dependencies

Look at the picture:

dependencies of csprojs

The build steps are named A, B, C, D.
Assemblies (projects) are named 1, 2, 3.
To simplify things assume that all the projects reference only assemblies from bin folder. Assembly 1 is independent, assembly 2 and 3 depend on 1 (through assembly reference). So, the steps in detail are:

  1. Assembly 1 is built and copied (in postbuild event) to bin. Its version is 1.0.0.1
  2. Assembly 2 is built. It creates a dependency on 1:1.0.0.1 and is copied to bin
  3. Assembly 1 is built and copied (in postbuild event) to bin. Its version is 1.0.0.2
  4. Assembly 3 is built. It creates a dependency on 1:1.0.0.2 and is copied to bin

If there is any other project that references 1,2,3 and you have to sign the assemblies, then you have a problem.

If you think about "why don't you avoid referencing the assemblies in bin folder?" then my answer is that in real life scenarie when there are many projects, then it's much more convenient to have multiple solutions with only subset of projects.
So Solution X might contain projects 1 and 2 and Solution Y projects 1 and 3.

How to find the dependencies

We know there are two types of dependencies:

  • Dependency on project. This is identified by ProjectReference element in csproj file.
    <ItemGroup>
     <ProjectReference Include="..\..\MyProj.csproj">
      <Project>{3B347485-93A1-436F-96DF-7C382A9E7304}</Project>
      <Name>MyProj</Name>
     </ProjectReference>
     ...
    </ItemGroup>
  • Dependency on assembly (dll). The element name is simly Reference. (our enemy)
    <ItemGroup>
     <Reference Include="Castle.Core, Version=1.0.3.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, ...">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\..\..\..\Lib\NHibernate\Castle.Core.dll</HintPath>
     </Reference>
     ...
    </ItemGroup>

So the solution here is just 'convert' assembly dependencies to project dependencies.

  1. We will look at Reference/HintPath in the csproj file
  2. extract the assembly name (it is possible from Reference/@Include as well)
  3. find a csproj file B in our directory that produces assembly with name from (2)
  4. and note down that the csproj from (1) is dependent on B project (from (3))

Code, finally

[CmdletBinding()]
param(
    [string]$dir='c:\dev\trunk\',
    [string]$skip #regex that filters some projects out
)

<# removes relative parts:  a\b\c\..\..\1\2 -> \a\1\2
   if you know a better approch, let me know ;) http://twitter.com/stejcz
#>
function RemoveRelativePaths($str) {
    $counter = 0
    do {
        if (++$counter -gt 20) { throw 'possibly unknown pattern: '+$str }
        $len = $str.Length
        $str = $str -replace '\\([\w\d]+\.)*[\w\d]+\\\.\.',''
    }while($str.Length -ne $len)
    $str
}

The function RemoveRelativePaths is just a helper to shorten paths like 'a\b\c\..\..\1\2' to '\a\1\2'. As I'm looking at it now, the regex might be simpler I think, like '\\[\w\d]+\\\.\.' but I'll leave it as it is.

In part #1 we get all the csproj files in specified directory and parse them – we read project referencies (XPath '//e:ProjectReference'), assembly name (XPath '//e:AssemblyName') to convert assembly dependency to project dependency, and parse assembly references (XPath '//e:Reference').

#part #1
Write-Host "Reading csprojs.."
$binReference = @{}
$skipProjs = @{}
$dependencies = Get-ChildItem $dir *.csproj -Recurse | % { 
    $content = [xml](gc $_.FullName)
    if (!($content.Project)) {
        Write-Warning "Project $($_.FullName) skipped. Does not contain root tag with name Project"
        return
    }
    
    Write-Debug "Reading $($_.FullName)"
    $ret = '' | select FullName, References, AssemblyName, Skipped
    $ret.FullName = $_.fullname    
    $ns = @{'e'="http://schemas.microsoft.com/developer/msbuild/2003" }
    $ret.References = @(
        @(Select-Xml -Xml $content -XPath '//e:ProjectReference' -Namespace $ns) |
        select -ExpandProperty Node | 
        select -ExpandProperty Include | 
        % { RemoveRelativePaths (Join-Path (Split-Path $ret.FullName -Parent) $_ ) })
    $ret.AssemblyName = Select-Xml -Xml $content -XPath '//e:AssemblyName' -Namespace $ns |
        select -ExpandProperty Node -First 1 | 
        select -Expand    Property '#text'
        
    $skipProjs[$ret.FullName] = $skip -and $ret.FullName -match $skip
    
    # processing references to bin
    @(Select-Xml -Xml $content -XPath '//e:Reference' -Namespace $ns) |
        select -ExpandProperty Node | 
        ? { $_.HintPath} |
        select -ExpandProperty HintPath | 
        % {
            $assemblyName = [IO.Path]::GetFileNameWithoutExtension($_) # napr. Gmc.System
            if (!$binReference.ContainsKey($assemblyName)) { 
                #Write-Host "New bin reference for $assemblyName"
                $binReference[$assemblyName] = @()
            }
            $binReference[$assemblyName] += $ret
        }
    
    $ret
    
    Write-Debug "Count of referencies: $($ret.References.Count)"
    if ($skipProjs[$ret.FullName]) { Write-Verbose "Skipped $($ret.Fullname)" }
}

Now we create the project dependencies from assembly dependencies. If you reference some 3rd party assemblies and there is no source csproj for them, they will just be written to the console saying something like 'there is no related project'.

#part #2
#resolve dependencies by assembly
$binReference.Keys | % {
    $assemblyName = $_
    $assemblyCsproj = $dependencies | ? { $_.AssemblyName -eq $assemblyName }
    if (!$assemblyCsproj) { 
        return $assemblyName
    } else {
        $binReference[$assemblyName] | % { $_.References += $assemblyCsproj.FullName }
    }
} | sort | % `
    -Begin { Write-Verbose "These assemblies are referenced but don't have related projects" } `
    -Process { Write-Verbose " $_" }

If you specified something to $skip parameter, then right the projects will be removed from list.

#part #3
<#
 there is an array in $dependencies, e.g.
FullName                         References                               AssemblyName
----                             ----------                               ----------
C:\dev\trunk\Base.csproj         {}                                       Base
C:\dev\trunk\Proj1.csproj        {C:\dev\trunk\Base.csproj}               Proj1
C:\dev\trunk\Proj2.csproj        {C:\dev\trunk\Base.csproj}               Proj2
C:\dev\trunk\ProjXYZ.csproj      {C:\dev\trunk\Base.csproj, C:\dev\t...}  ProjXYZ
#>

# removes all the projects that should be skipped; they are removed from references as well
# this could be done in csproj reading phase as well
Write-Host "Removing skipped projects.."
Write-Debug "Count before: $($dependencies.Count)"
$dependencies = $dependencies | 
    ? { !$skipProjs[$_.FullName] }  |
    % { $_.References = @($_.References | ? { !$skipProjs[$_]});
            $_ }
Write-Debug "Count after: $($dependencies.Count)"

Last part. Now we have all the data available so we can go through them and order the projects by referencies. The first set of projects will be the ones that don't depend on any other projects. The second set will be projects that depend only on first set and so on.
The output of the script is array of these sets.

#part #4
$steps, $stepNum = @(), 0
while($dependencies.Count -gt 0) {
    $indep = @($dependencies | ? { $_.References.Count -eq 0} | select -ExpandProperty FullName)
    if ($indep.Count -eq 0) {
        $global:RemainingDependencies = $dependencies
        throw "There is no independent project. Check `$global:RemainingDependencies variable `
        for remaining csproj dependencies"
    }
    Write-Verbose ("Count of independent projs: $($indep.Count), " +
        "step: $stepNum, dependencies count: $($dependencies.count)")
    $toAdd = ''|select Order,Projects
    $toAdd.Order,$toAdd.Projects = $stepNum++, @($indep)
    $steps += $toAdd 
    
    # Remove projects that are independent on any other project
    $dependencies = @($dependencies | ? { $indep -notcontains $_.FullName })
    
    # Remove the independent projects from referencies of other projects
    $dependencies | % { 
        Write-Debug "Checking project $($_.FullName), referencies count: $($_.References.Count)"
        $_.References = @($_.References | ? { $indep -notcontains $_ })
        Write-Debug "  new count of referencies: $($_.References.Count)"
    }
}
$steps

Example

I created a very tiny project to test it (you can download it). The output looks like this:

[1]: $dep =.\resolve-CsprojDependencies.ps1 -dir m:\temp\blog\117TestProj -verbose
Reading csprojs..
VERBOSE: These assemblies are referenced but don't have related projects
VERBOSE:  log4net
Removing skipped projects..
VERBOSE: Count of independent projs: 1, step: 0, dependencies count: 4
VERBOSE: Count of independent projs: 2, step: 1, dependencies count: 3
VERBOSE: Count of independent projs: 1, step: 2, dependencies count: 1
[2]: $dep | fl

Order    : 0
Projects : {M:\temp\blog\117TestProj\Base1\Base-1.csproj}

Order    : 1
Projects : {M:\temp\blog\117TestProj\Dep-2-1bin\Dep-2-1bin.csproj, 
            M:\temp\blog\117TestProj\Dep-3-1bin\Dep-3-1bin.csproj}

Order    : 2
Projects : {M:\temp\blog\117TestProj\DepAll\DepAll.csproj}

After you build the projects in this order, you can check that all the assemblies are dependent only on one version of Base-1.dll.

Download

I sometimes need an english-czech and czech-english dictionary. There is a great site slovnik.cz that does its job pretty well. However, it's quite slow to navigate to browser, navigate to the site, wait to load, etc.

After I got bored with waiting I wrote translator from czech to english and vice versa. Here is the quick and dirty code:

. (Join-Path $powershellDir helpers\Format-Columns.ps1)

function download($url) {
    Write-Debug "Downloading $url"
    $webRequest = New-Object Net.WebClient
    $webRequest.Headers.Add("User-Agent", 'Mozilla/5.0 (Windows; U; Windows NT 6.0; cs; rv:1.9.1.5) '+
                            'Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729) from Posh')
    $webRequest.Headers.Add("Accept", 'text/html,application/xhtml+xml,application/xml;')
    $webRequest.Headers.Add("Accept-Language", 'cs')
    $webRequest.Headers.Add("Accept-Encoding", 'deflate')
    $webRequest.Headers.Add("Accept-Charset", 'windows-1250') #utf-8
    $webRequest.Encoding = [system.text.encoding]::UTF8
    $str = $webRequest.DownloadString($url)
    $str
}
function convert2xml($s) {
    $s = $s.Replace(' xmlns="http://www.w3.org/1999/xhtml"', '')
    Add-Type -Path (Join-Path $powershellDir bin\SgmlReaderDll.dll)
    
    $sr = new-object io.stringreader $s
    
    $sgmlReader = new-object Sgml.SgmlReader
    $sgmlReader.DocType = 'HTML';
    $sgmlReader.WhitespaceHandling = 'All';
    $sgmlReader.CaseFolding = 'ToLower';
    $sgmlReader.InputStream = $sr;
    
    $xml = new-object Xml.XmlDocument;
    $xml.PreserveWhitespace = $true;
    $xml.XmlResolver = $null;
    $xml.Load($sgmlReader);
    
    $sgmlReader.Close()
    $sr.Close()
    
    $xml
}

function parse($xml, $word) {
    $nodes = Select-Xml -Xml $xml -XPath '//div[@id="vocables_main"]/div[@class="pair"]'
    $res = @($nodes | 
        ? { $_.Node.span[0].InnerText -eq $word } |
        % { $_.Node.span[1].InnerText })
    $res += @($nodes | 
        ? { ($_.Node.span[0].InnerText -ne $word) -and $_.Node.span[0].InnerText.StartsWith($word) } |
        % { "[{0}] {1}" -f $($_.Node.span[0].InnerText), $_.Node.span[1].InnerText })
    $res
}

function run($url, $word) {
    $res = @(parse (convert2xml (download $url)) $word)
    if ($res.Count -lt 20) { $res | Format-Columns -autosize -maxcol 4 }
    else                   { $res | Format-Columns -autosize }
}

function Translate-ToEnglish {
    run "http`://slovnik.cz/bin/mld.fpl?vcb=$($args -join '+')&dictdir=encz.cz&lines=50" ($args -join ' ')
}

function Translate-ToCzech {
    run "http://slovnik.cz/bin/mld.fpl?vcb=$($args -join '+')&dictdir=encz.en&lines=50" ($args -join ' ')
}

Export-ModuleMember Translate-ToCzech, Translate-ToEnglish

There are two external dependencies:

  • Cmdlet Format-Columns. I wrote about it some time ago, you can download it here.
  • Assembly SgmlReader. I'll provide a download link to my copy below, however you can download latest version as well.

I inspected some APIs to get rid of working with HTML, but they returned only one possible translation (that most of the time wasn't the best choice). Anyway, I'm not done with them and maybe in near future I'll come up with translator based only on a decent web API.

Some examples

[1]: Translate-ToCzech put up
naložit                                              smířit se s                                          
navrhnout                                            smluvit předem                                       
podat                                                snést klidně                                         
předložit                                            spokojit se                                          
spokojit se s                                        ubytovat                                             
uvést (na scénu)                                     ubytovat se                                          
vystavit (na odiv)                                   uložit ke spaní                                      
zarazit (zastavit, hovor.)                           uspořádat                                            
zastrčit (meč)                                       vyvěsit                                              
zvednout (ceny)                                      vztyčit                                              
postavit                                             zabalit                                              
zvýšit (ceny)                                        zavařit                                              
vykasat                                              zbudovat (přen.)                                     
pobízet                                              [put up a post for] vypsat konkurz                   
poskytnout nocleh                                    [put up a resistance] klást odpor                    
konzervovat                                          [put up at] zarazit kde (zastavit, hovor.)           
balit                                                [put up at] ubytovat kde                             
nabízet se                                           [put up at] ubytovat se kde                          
najít úkryt                                          [put up for sale] prodávat                           
nastrojit (podfuk, přen.)                            [put up for sale] dát do prodeje                     
navádět                                              [put up resistance] vzepřít se (slov.) g             
plašit                                               [put up the message] zobrazí zprávu                  
položit nahoru                                       [put up the shutters] nechat obchodu (přen.)         
ponoukat                                             [put up the shutters] stáhnout rolety (u obchodu)    
předvolat                                            [put up the shutters] zavřít krám

[2]: Translate-ToEnglish smířit se
bury the hatchet                                     [smířit se s] make one's peace with                  
do                                                   [smířit se s] make peace with                        
reconcile oneself g                                  [smířit se s] put up                                 
make it up                                           [smířit se s] reconcile to                           
make one's peace                                     [smířit se s] reconcile with                         
make peace                                           [smířit se s] sit down under                         
make up                                              [smířit se s] acquiesce (čím)                        
reconcile                                            [smířit se s] put up with (čím)                      
reconcile to                                         [smířit se s] sit down under (st.) (čím)             
resign to                                            [smířit se s čím] acquiesce                          
make it up (po hádce)                                [smířit se s čím] do with st.                        
take (s čím)                                         [smířit se s čím] put up with st.                    
[smířit se s] do with                                [smířit se s čím] sit down under st.                 
[smířit se s] make it up                             [smířit se se ztrátou] sacrifice

I load this module in my profile by default and create aliases like this:

Set-Alias tocz Translate-ToCzech
Set-Alias toen Translate-ToEnglish

In case you have Posh console opened somewhere and you don't want to look for it in you taskbar, you may appreciate hiding and bringing up the console via AutoHotkey.

Download

Yesterday I needed to go through bunch of *.csproj files and to change reference from 'Spring.Data.NHibernate20.dll' to 'Spring.Data.NHibernate21.dll'. So I created an almost-oneliner very quickly and ran it.

gci c:\dev\Ginger\ *.csproj -rec | 
  % { 
    ($_|gc) -replace 'Spring.Data.NHibernate20.dll','Spring.Data.NHibernate21.dll' | sc $_.fullname 
  }

That's why PowerShell is my choice #1 when it comes to some processing. There is no better option! :) That's my message to all others who hesitate: just use it, it will pay off. Definitely.

My second look

After a while I looked at it once again. In the Foreach-Object body there is a nested pipeline. I read the file, replace something and pipe it to Set-Content. What attracted me was the usage of $_.FullName.
Until today I considered $_ variable as the current pipeline item. So when I pipe replaced text to sc there shouldn't be any FullName property, because it is not [FileInfo] (from gc), but [string] (from replacement), right?. So the script should not work (?).

I though that the scopes work like this (same scopes where $_ has the same value are in same color):

  get-sth | % {  $_ | do-sth3 | do-sth4 $_ | % { $_ | do-sth5 $_ | do-sh6 { $_...} } }

But my oneliner worked so I had to be wrong, obviously. After Vadim Podans and Paul Chavez answered my question I created a test script. Everything started to make sense.

Solution – How $_ is assgined

Here is the test script:

filter Pi { 
  param(
    [Parameter(ValueFromPipeline=$true)]$pip, 
    $par
  ) 
  write-host $add Pipeline: $pip -fore Green; 
  write-host $add Parameter: $par -fore Blue; 
  $pip
}
'test' | 
  % {
    write-host first $_
    'test2' |
      Pi -par $_ |             # filter 1
      % {
        write-host second $_
        'test3'| Pi -par $_    # filter 2
      }
  }

Just run the script and you will see what it produces:

first test
Pipeline: test2
Parameter: test
second test2
Pipeline: test3
Parameter: test2
test3

It is pretty simple:

  • If you use $_ in a script block that is executed in a cmdlet of the pipeline (e.g. scriptblock in Foreach-Object) then the most nested piped object is accessed.
  • If you use $_ as parameter of a cmdlet, it is value from outer pipeline. It wouldn't make sense if it was a value from current pipeline. Why? Because in my script sc $_.fullname the $_ would change with every row from replacement.
    Look at first call of Pi -par $_ from my test example. Value of $_ inside the filter is 'test2', but value passed as parameter -par is 'test' – value from the same scope as write-host first $_ is.

So the scope looks better like this:

  get-sth | % {  $_ | do-sth3 { $_...} | do-sth4 $_ | % { $_ | do-sth5 $_ | do-sh6 { $_...} } }

And this is only the beginning...

I saw an article about arrays comparison. The solution is not bad, but there is other one how to do it in a more PowerShell way.

Assume you have two lists of filenames stored in two separate files and you want to compare them – to find items that are only in one of the list. The diff in WinMerge might look like this:

Comparison of sorted arrays

In PowerShell you will get it via this code:

[1]: $file1 = gc D:\temp\compareArraysTest\a1.txt
[2]: $file2 = gc D:\temp\compareArraysTest\a2.txt
[3]: Compare-Object -ReferenceObject $file1 -DifferenceObject $file2

InputObject                                      SideIndicator
-----------                                      -------------
AutoHotkey Website.url                           =>
SmartGUI.exe                                     =>
PowershellDemo.ahk                               =>
DocsHotkeys.htm                                  <=
startup.ahk                                      <=

Note that it works on lists that are not in the same order as well. That means that the arrays behave like sets.

Comparison of arrays with different order

[4]: $file2random = gc D:\temp\compareArraysTest\a2random.txt
[5]: Compare-Object -ReferenceObject $file1 -DifferenceObject $file2Random

InputObject                                      SideIndicator
-----------                                      -------------
AutoHotkey Website.url                           =>
SmartGUI.exe                                     =>
PowershellDemo.ahk                               =>
DocsHotkeys.htm                                  <=
startup.ahk                                      <=

Sometimes only visual output is not enough. Afterall, PowerShell is based on objects. So people appreciate output based on objects. How to do it?
Warning: this is based on string manipulation that is far from ideal.

function Compare-Arrays {
    param(
        [Parameter(Mandatory=$true)][Object[]]$a1,
        [Parameter(Mandatory=$true)][Object[]]$a2
    )
    @(Compare-Object -ReferenceObject $a1 -DifferenceObject $a2 | 
        % -begin { 
            $toreturn = New-Object PSObject -Property @{Left=@(); Right=@() } 
          } -process { 
            if ($_.SideIndicator -eq '<=') { $toreturn.Left += $_.InputObject }
            else                           { $toreturn.Right += $_.InputObject }
          } -end { 
            $toreturn 
          }
    )
}
[6]: Compare-Arrays $file1 $file2

Left                                                      Right
----                                                      -----
{DocsHotkeys.htm, startup.ahk}                            {AutoHotkey Website.url, SmartGUI.exe, PowershellDemo.ahk}

This way you can automate the processing much better. However, this approach is a little bit ugly (because of string comparisons).

Custom diff

We may try to do it in a little more standard way and to use some existing .NET class. In .NET 3.5 there is a new class HashSet that could suite our needs. First we will load the assembly (I hope you have version 3.5 installed). Then we will create the set so that we can examine its members.

[7]: [reflection.assembly]::Load('System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
[8]: $set = New-Object System.Collections.Generic.HashSet`[int]
[9]: Get-Member -input $set
   TypeName: System.Collections.Generic.HashSet`1[[System.Int32, mscorlib, Version=2.0.0.0, Cul

Name                MemberType Definition
----                ---------- ----------
Add                 Method     bool Add(int item)
Clear               Method     System.Void Clear()
...
ExceptWith          Method     System.Void ExceptWith(System.Collections.Generic.IEnumerable[in..
...
UnionWith           Method     System.Void UnionWith(System.Collections.Generic.IEnumerable[int..
Comparer            Property   System.Collections.Generic.IEqualityComparer`1[[System.Int32, ms..
Count               Property   System.Int32 Count {get;}

Based on the members listing we will try to create our function that uses the HashSet.

function Compare-Arrays2{
    param(
        [Parameter(Mandatory=$true)][Object[]]$s1,
        [Parameter(Mandatory=$true)][Object[]]$s2
    )
    $null = [reflection.assembly]::Load('System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
    $set1 = New-Object System.Collections.Generic.HashSet`[object]
    $set2 = New-Object System.Collections.Generic.HashSet`[object]
    $s1 | % { $null = $set1.add($_) }
    $s2 | % { $null = $set2.add($_) }
    $set1.ExceptWith($s2)
    $set2.ExceptWith($s1)
    New-Object PSObject -Property @{Left=$set1; Right=$set2}
}

What about performance?

We created custom function that could be possibly faster than the one that works with strings, right? Let's check it.

[10]: (Measure-Command -Expression { 1..100 | % { $null=Compare-Arrays (100..1) (1..101) } }).TotalSeconds
1,215109
[11]: (Measure-Command -Expression { 1..100 | % { $null = Compare-Arrays2 (100..1) (1..101) } }).TotalSeconds
4,3265875

I know that the testing method is not optimal and I should try different outputs. However, its pretty clear that at least in some scenarios the function based od HashSet will be slower than the original function.
No, assembly loading doesn't cause performance penalties – once loaded, it is not reloaded again.
The function accepts object[]. However, it will work correctly with primitive types like strings, ints etc.

What's the moral? Measure, measure, measure, if you come up with your own solution.

From time to time I use Format-Wide to display list of some data. It works great but sometimes my eyes have to jump quite a long distance between the columns – that's because in some cases the order matters. Example:

[1]: 'The basic promise behind implicit remoting is that you can work '+
   'with remote commands using local syntax.' -split ' ' | Format-Wide -force -prop {$_} -col 3
  
  The                        basic                      promise
  behind                     implicit                   remoting
  is                         that                       you
  can                        work                       with
  remote                     commands                   using
  local                      syntax.

In case you have to read the values in correct order, you will not feel comfortable, right?
So, check this:

[2]: 'The basic promise behind implicit remoting is that you can work '+
   'with remote commands using local syntax.' -split ' ' | . Format-Columns -col 3
  
  The                       is                        remote
  basic                     that                      commands
  promise                   you                       using
  behind                    can                       local
  implicit                  work                      syntax.
  remoting                  with

This version reads much better. You just follow whole columns, one by one.

Code

In case you find it helpful, here is the code:

function Format-Columns {
################################################################
#.Synopsis
#  Formats incoming data to columns.
#.Description
#  It works similarly as Format-Wide but it works vertically. Format-Wide outputs
#  the data row by row, but Format-Columns outputs them column by column.
#.Parameter Property
#  Name of property to get from the object.
#  It may be 
#   -- string - name of property.
#   -- scriptblock
#   -- hashtable with keys 'Expression' (value is string=property name or scriptblock)
#      and 'FormatString' (used in -f operator)
#.Parameter Column
#  Count of columns
#.Parameter Autosize
#  Determines if count of columns is computed automatically.
#.Parameter MaxColumn
#  Maximal count of columns if Autosize is specified
#.Parameter InputObject
#  Data to display
#.Example
#  PS> 1..150 | Format-Columns -Autosize
#.Example 
#  PS> Format-Columns -Col 3 -Input 1..130
#.Example
#  PS> Get-Process | Format-Columns -prop @{Expression='Handles'; FormatString='{0:00000}'} -auto
#.Example
#  PS> Get-Process | Format-Columns -prop {$_.Handles} -auto
#.Notes
# Name: Get-Columns
# Author: stej, http://twitter.com/stejcz
# Lastedit: 2010-01-14
# Version 0.2 - 2010-01-14
#  - added MaxColumn
#  - fixed bug - displaying collection of 1 item was incorrect
# Version 0.1 - 2010-01-06
################################################################
    param(
        [Parameter(Mandatory=$false,Position=0)][Object]$Property,
        [Parameter()][switch]$Autosize,
        [Parameter(Mandatory=$false)][int]$Column,
        [Parameter(Mandatory=$false)][int]$MaxColumn,
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)][PsObject[]]$InputObject
    )
    begin   { $values = @() }
    process { $values += $InputObject }
    end {
        function ProcessValues {
            $ret = $values
            $p = $Property
            if ($p -is [Hashtable]) {
                $exp = $p.Expression
                if ($exp) {
                    if ($exp -is [string])          { $ret = $ret | % { $_.($exp) } }
                    elseif ($exp -is [scriptblock]) { $ret = $ret | % { & $exp $_} }
                    else                            { throw 'Invalid Expression value' }
                }
                if ($p.FormatString) {
                    if ($p.FormatString -is [string]) {    $ret = $ret | % { $p.FormatString -f $_ } }
                    else {                              throw 'Invalid format string' }
                }
            }
            elseif ($p -is [scriptblock]) { $ret = $ret | % { & $p $_} }
            elseif ($p -is [string]) {      $ret = $ret | % { $_.$p } }
            elseif ($p -ne $null) {         throw 'Invalid -property type' }
            # in case there were some numbers, objects, etc., convert them to string
            $ret | % { $_.ToString() }
        }
        function Base($i) { [Math]::Floor($i) }
        function Max($i1, $i2) {  [Math]::Max($i1, $i2) }
        if (!$Column) { $Autosize = $true }
        $values = ProcessValues
        
        $valuesCount = @($values).Count
        if ($valuesCount -eq 1) {
            $values | Out-Host
            return
        }
        
        # from some reason the console host doesn't use the last column and writes to new line
        $consoleWidth          = $host.ui.RawUI.maxWindowSize.Width -1; 
        $spaceWidthBetweenCols = 2
            
        # get length of the longest string
        $values | % -Begin { [int]$maxLength = -1 } -Process { $maxLength = Max $maxLength $_.Length }
        
        # get count of columns if not provided
        if ($Autosize) {
            $Column         = Max (Base ($consoleWidth/($maxLength+$spaceWidthBetweenCols))) 1
            $remainingSpace = $consoleWidth - $Column*($maxLength+$spaceWidthBetweenCols);
            if ($remainingSpace -ge $maxLength) { 
                $Column++ 
            }
            if ($MaxColumn -and $MaxColumn -lt $Column) {
                Write-Debug "Columns corrected to $MaxColumn (original: $Column)"
                $Column = $MaxColumn
            }
        }
        $countOfRows       = [Math]::Ceiling($valuesCount / $Column)
        $maxPossibleLength = Base ($consoleWidth / $Column)
        
        # cut too long values, considers count of columns and space between them
        $values = $values | % { 
            if ($_.length -gt $maxPossibleLength) { $_.Remove($maxPossibleLength-2) + '..' }
            else { $_ }
        }
        
        #add empty values so that the values fill rectangle (2 dim array) without space
        if ($Column -gt 1) {
            $values += (@('') * ($countOfRows*$Column - $valuesCount))
        }
        # in case there is only one item, make it array
        $values = @($values)
        <#
        now we have values like this: 1, 2, 3, 4, 5, 6, 7, ''
        and we want to display them like this:
        1 3 5 7
        2 4 6 ''
        #>
        
        $formatString = (1..$Column | %{"{$($_-1),-$maxPossibleLength}"}) -join ''
        1..$countOfRows | % { 
            $r    = $_-1
            $line = @(1..$Column | % { $values[$r + ($_-1)*$countOfRows]} )
            $formatString -f $line | Out-Host
        }
    }
}

At at the end one (almost) real example:

[3]: Get-Process | 
    Sort-Object Handles -desc | 
    Select-Object -first 30 | 
    Format-Columns -Property { '{0,-10} - {1,5}' -f $_.Name,$_.Handles} -auto
  
  System     -  2289    firefox    -   690    taskeng    -   451    MSASCui    -   360
  svchost    -  1216    lsass      -   672    cmdagent   -   444    PCSuite    -   347
  explorer   -  1122    svchost    -   642    Jing       -   441    procexp    -   341
  ScriptEditor -   870  Skype      -   628    CompPtcVUI -   431    svchost    -   333
  powershell -   867    powershell -   565    svchost    -   424    svchost    -   328
  csrss      -   745    svchost    -   528    svchost    -   414    spoolsv    -   312
  csrss      -   727    svchost    -   500    svchost    -   410
  trillian   -   708    upeksvr    -   482    wlanext    -   397

Download

[26.12.2009] Pepa

Vánoční trika

Tyto Vánoce jsem se pokusil vyrobit svým drahým půlovičkám trika. Jaký byl výsledek?

Výsledná trika

Emka to sice má jak pyžamo, ale nevadí. Jednou doroste. Obrázek vypadá takto (samozřejmě na každý triko jedna půlka):

Obrázek na trika

Pokud by snad někdo měl zájem, klidně můžu poupravit, vyměnit jméno, nebo i poslat zdrojový svg. S autorskými právy zatím problém nemám.

I usually have a lot of applications open that I use from time to time, not too frequently. It may be email client, one of my browsers or a PowerShell console that I use for some tests, conversions, etc.
Why not to hide them and bring to front only when you need it? Just press a hotkey and the window is shown. Just press a hotkey and the windows is hidden (you don't see it in your taskbar, but the process is running).

(not only) For this purpose I use AutoHotkey. If you don't know this tool, it's worth checking. It's not just a simple mapping "if this key is pressed, run application at this path"; you will see a pretty scripting language.

Show / hide PowerShell console

As I said I have usually at least one PowerShell console open. Most of the time I use browser or Visual studio, so the Posh window might be hidden. How to do that?

First I need a way how to identify the Posh console. The window class would be ok, but Posh console has the same class as the window where cmd.exe is hosted. Besides that if more than one Posh console is open, I would not be able to distinguish between them.
That's why I make the window title unique:

[1]: $host.ui.RawUI.WindowTitle = 'myuniquewindowtitle'

From now on the title of the console is 'myuniquewindowtitle' (check the taskbar for example).

Then I will add a function to AutoHotkey script that hides or shows the window depending if it is visible or not.

LAlt & `::
  DetectHiddenWindows, on
  PoshWinName := "myuniquewindowtitle"
  IfWinExist, %PoshWinName%
  {
    IfWinActive, %PoshWinName%
    {
      WinHide, %PoshWinName%
      WinActivate ahk_class Shell_TrayWnd
    }
    else
    {
      WinShow, %PoshWinName%
      WinActivate, %PoshWinName%
    }
  }
  DetectHiddenWindows, off
  return

Just press left alt and tilde and you will see that it works.
I saw base for this script in some comments, thanks for that.

Similarly you can hide and show any window. If the window title is not always the same, you can specify it by window class – AutoHotkey tool will help you with finding the name of the class.