Manifesto for Software Craftsmanship

Handyman Occupation Craftsmanship Carpentry Concept

As software development professionals, we are craftsman.  As such, we ought to adhere to practices that help us to continuously produce better products.  The Manifesto for Software Craftsmanship is a short and succinct document that reminds us what are the most important things to focus on as we practice our craft.



Get all properties from MSI installer files

windowsinstallericonThere are times when you need to specify custom values for MSI properties.  If the MSI is from a third party or you do not have the original installer code, it would be nice to be able to get a full listing of all properties exposed by an MSI.  This PowerShell function uses the COM-based WindowsInstaller API to extract properties from 1 to n MSIs.


Once you find the properties you want to provide custom values for, you can then execute msiexec.exe using the custom property values:


function Get-MsiInfo {
        [parameter(Mandatory=$True, ValueFromPipeline=$true)]
            Queries parameter information from one or more MSI files

            By default will return the ProductCode,ProductVersion,ProductName,Manufacturer,ProductLanguage,FullVersion.  If an empty string
            is provided for the Property parameter, then all properties are returned

            .PARAMETER Path
            MSI Path(s) provided either explicitly or from the pipeline

            .PARAMETER Property
            The names of the MSI properties to return.  Specify empty string to return all properties

            gci *.msi | Get-MsiInfo -Property 'ProductName','ProductVersion','Manufacturer'


            Gets specific properties for all MSIs in the current directory

            gci *.msi | Get-MsiInfo


            Get all properties for all MSIs in the current directory
    Begin {
        $winInstaller = New-Object -ComObject WindowsInstaller.Installer
    Process {
        try {
            $msiDb = $winInstaller.GetType().InvokeMember('OpenDatabase', 'InvokeMethod', $null, $winInstaller, @($Path.FullName, 0))
            if($Property) {
                $propQuery = 'WHERE ' + (($Property | ForEach-Object { "Property = '$($_)'"}) -join ' OR ')
            $query = ("SELECT Property,Value FROM Property {0}" -f ($propQuery))

            $view = $msiDb.GetType().InvokeMember('OpenView', 'InvokeMethod', $null, $msiDb, ($query))
            $null = $view.GetType().InvokeMember('Execute', 'InvokeMethod', $null, $view, $null)

            $msiInfo = [PSCustomObject]@{'File' = $Path}
            do {
                $null = $view.GetType().InvokeMember('ColumnInfo', 'GetProperty', $null, $view, 0)
                $record = $view.GetType().InvokeMember('Fetch', 'InvokeMethod', $null, $view, $null)
                if(-not $record) { break; }
                $propName = $record.GetType().InvokeMember('StringData', 'GetProperty', $null, $record, 1) | select -First 1
                $value = $record.GetType().InvokeMember('StringData', 'GetProperty', $null, $record, 2) | select -First 1
                $msiInfo = $msiInfo | Add-Member -MemberType NoteProperty -Name $propName -Value $value -PassThru
            } while ($true)

            $null = $msiDb.GetType().InvokeMember('Commit', 'InvokeMethod', $null, $msiDb, $null)
            $null = $view.GetType().InvokeMember('Close', 'InvokeMethod', $null, $view, $null)           

        catch {
            Write-Warning -Message $_
            Write-Warning -Message $_.ScriptStackTrace

    End {
        try {
            $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($winInstaller)
        } catch {
            Write-Warning -Message 'Failed to release Windows Installer COM reference'
            Write-Warning -Message $_


Parallel copy files over remoting session

metro-powershell-logoA fairly straightforward method to copy files via PowerShell remote sessions to multiple computers in parallel.

$ErrorActionPreference = 'stop'

workflow Copy-Parallel {
    param ([string[]]$ComputerName, [string]$SourcePath,[string]$TargetPath)
    foreach -parallel ($computer in $ComputerName) {
        InlineScript  {
            try {
                $s = New-PSSession -ComputerName $using:computer
                Copy-Item -Path $using:SourcePath -Destination (Split-Path $using:TargetPath -Parent) -ToSession $s
            } finally {
                Remove-PSSession -Session $s

function New-BigFile {
        [int]$SizeInBytes = 30MB
    $bytes = new-object byte[] 1000000
    try {
        $fs = [io.file]::Create($filePath)
        $bytesWritten = 0
        do {
            $fs.Write($bytes, 0, $bytes.Count)
            $bytesWritten += $bytes.Count
        } while($bytesWritten -lt $SizeInBytes)
    } finally {

$filePath = 'c:\temp\bigfile.bin'
New-BigFile -Path $filePath -SizeInBytes 100MB
Copy-Parallel -ComputerName dev-bldserv2,dev-bldagnt4,dev-bldagnt5,devbuild8 -SourcePath $filePath -TargetPath $filePath

Richard Siddaway's Blog

In response to this post –

I was asked how you could copy files in parallel to multiple machines.

As soon as anyone mentions parallelism I think of workflows so I ended up with this

$computers = ‘W16TP5TGT01’, ‘W16TP5TGT02’

workflow parallelcopy {
param ([string[]]$computername)
foreach -parallel ($computer in $computername) {
InlineScript {
$s = New-PSSession -ComputerName $using:computer
Copy-Item -Path C:ScriptsNew-NanoMachine.ps1 -Destination C:Source -ToSession $s
Remove-PSSession -Session $s

parallelcopy -computername $computers

A couple of issues I found. First off –Tosession and –FromSession haven’t been added to the Copy-Item workflow activity. This means you have to use an Inline script block to access those parameters

Secondly accessing a emoting session created outside of the workflow generates a session busy error when trying to perform the copy so have to move the session creation into the Inline script.

If you had a lot of files, or a lot of…

View original post 26 more words

Integrating TeamCity with Team Foundation Server – Part 2

An interesting solution to integrating TFS (on premises) with TeamCity for tightly Application Lifecycle Management (ALM). This solution probably does need some updating for TFS’s new build model. Also, it would be nice to have an automated provisioning mechanism. Having a solution like this in place would allow gating pull requests based on whether or not they have a successful TeamCity build.

The Road to ALM

In my previous post I introduced TeamCity as  an alternative build server to use in conjunction with Team Foundation Server. This post mainly focused on getting started with TeamCity and building sources coming from Team Foundation Server. At the end of this previous post I talked about the dependency of TFS artifacts with the TFS Build System. For example, selecting a build in Test Manager, can only be a build created with the TFS Build system. In this post I will talk about how to create a tight integration between TeamCity and TFS so that you can use the TeamCity build within Test Manager and in Work Items ….or at least fake that Winking smile..

Setting up TFS Build

As said, currently you need a build created in TFS in order to tie other artifacts to it. Of course you don’t need to tie everything together, but when you want traceability…

View original post 1,124 more words

Replacing the Default PSRepository with your own (For PowerShell v 5.0.10586.117)


The script below is a template for changing the default PSRepository location.

#Script to change the default PSGallery URLs to custom ones (Needed for PowerShell 5.0.10586.117)
$xmlPath = "${env:APPDATA}\Microsoft\Windows\PowerShell\PowerShellGet\PSRepositories.xml" -ireplace "Roaming","Local"

$config = Import-Clixml -Path $xmlPath

$config.Item("PSGallery").SourceLocation = '<YOUR_INTERNAL_PROGET_URL_HERE>'
$config.Item("PSGallery").PublishLocation = '<YOUR_INTERNAL_PROGET_URL_HERE>'
$config.Item("PSGallery").ScriptSourceLocation = ''
$config.Item("PSGallery").ScriptPublishLocation = ''
$config.Item("PSGallery").Trusted = $true
$config.Item("PSGallery").Registered = $true
$config.Item("PSGallery").InstallationPolicy = 'Trusted'
$config.Item("PSGallery").PackageManagementProvider = 'NuGet'
$config.Item("PSGallery").ProviderOptions = $config.Item("PSGallery").ProviderOptions

Export-Clixml -Path $xmlPath -InputObject $config

Thanks to Richard for his deep research on this issue …

Using Agent Ransack, I found that the repository data is stored in “c:\Users\\AppData\Local\Microsoft\Windows\PowerShell\PowerShellGet\PSRepositories.xml”

Richard Siddaway's Blog

In this post – – I stated that you could unregister the default PowerShell repository. I also said that the statement in the documentation for Unregister-PSrepository that you couldn’t unregister PSGallery was incorrect.

A couple of readers have left comments stating that they tried it and got an error message stating that PSGallery can’t be unregistered.

I did my first test on Windows 10 latest preview build – build 14926

PS> $PSVersionTable

Name Value
—- —–
PSVersion 5.1.14926.1000
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
BuildVersion 10.0.14926.1000
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3

I’ve just test on Windows Server 2016 TP5

PS C:Windowssystem32> $PSVersionTable

Name Value
—- —–
PSVersion 5.1.14300.1000
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
CLRVersion 4.0.30319.42000
BuildVersion 10.0.14300.1000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3

The version of PowerShell 5.1 on Server 2016 TP5 doesn’t have the –Default parameter on Register-PSrepositiry but this works


View original post 25 more words