BuildUtils.psm1

<#
.SYNOPSIS
Get location of latest msbuild installed on the system
 
.DESCRIPTION
It requires modules VSSetup installed in the system, you can install
with standard instruction
 
Install-Module VSSetup -Scope CurrentUser -Force
 
.EXAMPLE
 
$msbuildLocation = Get-LatestMsbuildLocation
set-alias msb $msbuildLocation
 
msb yoursolution.sln
#>

function Get-LatestMsbuildLocation
{
  Param 
  (
    [bool] $allowPreviewVersions = $false
  )
    if ($allowPreviewVersions) {
      $latestVsInstallationInfo = Get-VSSetupInstance -All -Prerelease | Sort-Object -Property InstallationVersion -Descending | Select-Object -First 1
    } else {
      $latestVsInstallationInfo = Get-VSSetupInstance -All | Sort-Object -Property InstallationVersion -Descending | Select-Object -First 1
    }
    Write-Host "Latest version installed is $($latestVsInstallationInfo.InstallationVersion)"
    if ($latestVsInstallationInfo.InstallationVersion -like "15.*") {
      $msbuildLocation = "$($latestVsInstallationInfo.InstallationPath)\MSBuild\15.0\Bin\msbuild.exe"
    
      Write-Host "Located msbuild for Visual Studio 2017 in $msbuildLocation"
    } else {
      $msbuildLocation = "$($latestVsInstallationInfo.InstallationPath)\MSBuild\Current\Bin\msbuild.exe"
      Write-Host "Located msbuild in $msbuildLocation"
    }

    return $msbuildLocation
}
<#
.SYNOPSIS
Get location of latest nuget in temp folder, if nuget.exe is not found it will download
 
.DESCRIPTION
 
.EXAMPLE
 
$nugetLocation = Get-NugetLocation
set-alias nuget $nugetLocation
 
nuget yoursolution.sln
#>

function Get-NugetLocation
{
  Param 
  (
  )
    
    $nugetLocation = "$env:TEMP\nuget.exe"
    if (!(Test-Path -Path $nugetLocation)) {

      Invoke-WebRequest -Uri "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -OutFile $nugetLocation
    }

    return $nugetLocation
}


<#
.SYNOPSIS
Manipulate all assemblyinfo.vb and assemblyinfo.cs to change version
 
.DESCRIPTION
With legacy .NET project versioning is done with specific attribtues
inside assemblyinfo.cs and assemblyinfo.vb files. This cmdlet
can scan all files and perform a substitution.
 
This should be done before compiling (and usually after version is
determined with tools like gitversion)
 
This is a perfect match for Invoke-Getversion function
 
.PARAMETER SrcPath
Source path
 
.PARAMETER assemblyVersion
AssemblyVersionAttribute
 
.PARAMETER fileAssemblyVersion
FileAssemblyVersionAttribute
 
.PARAMETER assemblyInformationalVersion
AssemblyinformationalVersion
 
.EXAMPLE
 
$version = Invoke-GitVersion
Update-SourceVersion -SrcPath PathWithSource -assemblyVersion $version.assemblyVersion -fileAssemblyVersion $version.assemblyFileVersion -assemblyInformationalVersion = $version.assemblyInformationalVersion
 
#>

function Update-SourceVersion
{
  Param 
  (
    [string]$SrcPath,
    [string]$assemblyVersion, 
    [string]$fileAssemblyVersion,
    [string]$assemblyInformationalVersion,
    [bool]$modifyAssemblyVersion = $true
  )
    
    if ($fileAssemblyVersion -eq "")
    {
        $fileAssemblyVersion = $assemblyVersion
    }

        
    if ($assemblyInformationalVersion -eq "")
    {
        $assemblyInformationalVersion = $fileAssemblyVersion
    }
    
    Write-Host "Executing Update-SourceVersion in path $SrcPath, Version is $assemblyVersion and File Version is $fileAssemblyVersion and Informational Version is $assemblyInformationalVersion"
        
    $AllVersionFiles = Get-ChildItem $SrcPath\* -Include AssemblyInfo.cs,AssemblyInfo.vb -recurse
  
    foreach ($file in $AllVersionFiles)
    { 
        Write-Host "Modifying file " + $file.FullName
        #save the file for restore
        $backFile = $file.FullName + "._ORI"

        Copy-Item $file.FullName $backFile -Force
        #now load all content of the original file and rewrite modified to the same file
        $content = Get-Content $file.FullName 
        Remove-Item $file.FullName
        if ($modifyAssemblyVersion) 
        {
          $content |
            %{$_ -replace 'AssemblyVersion\("[0-9]+(\.([0-9]+|\*)){1,3}"\)', "AssemblyVersion(""$assemblyVersion"")" } |
            %{$_ -replace 'AssemblyFileVersion\("[0-9]+(\.([0-9]+|\*)){1,3}"\)', "AssemblyFileVersion(""$fileAssemblyVersion"")" } |
            %{$_ -replace 'AssemblyInformationalVersion\(".*"\)', "AssemblyInformationalVersion(""$assemblyInformationalVersion"")" } |
            Set-Content -Encoding UTF8 -Path $file.FullName -Force
        }
        else {
          $content |
            %{$_ -replace 'AssemblyFileVersion\("[0-9]+(\.([0-9]+|\*)){1,3}"\)', "AssemblyFileVersion(""$fileAssemblyVersion"")" } |
            %{$_ -replace 'AssemblyInformationalVersion\(".*"\)', "AssemblyInformationalVersion(""$assemblyInformationalVersion"")" } |
            Set-Content -Encoding UTF8 -Path $file.FullName -Force
        }
    }
}

<#
.SYNOPSIS
Simply allow for easy XML node manipulation in context of classic .NET xml configuration
files. The purpose is to substitute specific part of the configuration file with
predetermined tokens.
 
.DESCRIPTION
Basically takes an XML object and then perform a lookup of a specific part of the XML with
standard XPATH notation and perform a substitution.
 
.EXAMPLE
 
$configFile = "somedirecory/web.config"
$xml = [xml](Get-Content $configFile)
Edit-XmlNodes $xml -xpath "/configuration/appSettings/add[@key='apiClientSecret']/@value" -value "__APICLIENTSECRET__"
$xml.save($configFile)
 
.NOTES
 
#>

function Edit-XmlNodes {
    param (
        [xml] $doc = $(throw "doc is a required parameter"),
        [string] $xpath = $(throw "xpath is a required parameter"),
        [string] $value = $(throw "value is a required parameter"),
        [bool] $condition = $true
    )    
        if ($condition -eq $true) {
            $nodes = $doc.SelectNodes($xpath)
             
            foreach ($node in $nodes) {
                if ($node -ne $null) {
                    if ($node.NodeType -eq "Element") {
                        $node.InnerXml = $value
                    }
                    else {
                        $node.Value = $value
                    }
                }
            }
        }
    }
  

<#
.SYNOPSIS
Unzip a zip file using standard .NET framework classes.
 
.PARAMETER zipFile
Path of zip file
 
.PARAMETER destinationFolder
Destination folder where to uncompress files
 
.PARAMETER deleteOld
If $true the routine will delete original file.
 
.PARAMETER quietMode
if $true it will output less information on output stream
#>

function Expand-WithFramework(
    [string] $zipFile,
    [string] $destinationFolder,
    [bool] $deleteOld = $false,
    [bool] $quietMode = $false
)
{
    Add-Type -AssemblyName System.IO.Compression.FileSystem
    if ((Test-Path $destinationFolder) -and $deleteOld)
    {
          Remove-Item $destinationFolder -Recurse -Force
    }
    [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $destinationFolder)
}

<#
.SYNOPSIS
Get location of 7za.exe and download if not present
 
.DESCRIPTION
Get location of 7zip executable (7za) in temp directory,
if the executable is not present it will download and
save on disk. It will return the location of 7za executable
that can be in turn used to compress / uncompress files and dir.
 
.EXAMPLE
 
Suppose that we want to compress $source directory in a file
called $Target
 
$sevenZipExe = Get-7ZipLocation
set-alias sz $sevenZipExe
 
Write-Output "Zipping folder $Source in file $Target"
sz a -mx=9 -r -mmt=on $Target $Source
 
.NOTES
 
#>

function Get-7ZipLocation()
{
    $exeLocation = "$env:TEMP\7z"
    $sevenZipExe = "$exeLocation\7za.exe"
    Write-Debug "Testing for 7zip executable [$sevenZipExe]"
    if (-not (test-path $sevenZipExe)) 
    {
        Write-Debug "7zip executable [$sevenZipExe] not present, download from http://www.7-zip.org/a/7za920.zip to $env:TEMP\7zip.zip"
        Invoke-WebRequest -Uri "http://www.7-zip.org/a/7za920.zip" -OutFile "$env:TEMP\7zip.zip"
        Write-Debug "Unzipping $env:TEMP\7zip.zip to directory $exeLocation"
        Expand-WithFramework -zipFile "$env:TEMP\7zip.zip" -destinationFolder "$exeLocation" -quietMode $true 
        $sevenZipExe = "$exeLocation\7za.exe"
    } 
    return $sevenZipExe 
}
<#
.SYNOPSIS
Allow for asking a simple yes/no question
 
.DESCRIPTION
Given a question this function ask for a yes/no response
it handles default value, casing and accepts both y n short answer
or long yes no version
 
.EXAMPLE
 
Get-YesNoAnswer -question "Do you want to do this?" -default $true
 
.NOTES
 
#>

function Get-YesNoAnswer(
    [string] $question,
    [System.Nullable[bool]] $default = $null) 
{
  $yesValues = "y", "yes";
  $noValues = "n", "no";
  do 
  {
    $realQuestion = $question.TrimEnd(":");
    if ($default -eq $true) 
    {
      $realQuestion += ": (Y/n)" 
    }
    elseif ($default -eq $true) 
    {
      $realQuestion += ": (y/N)" 
    }
    else 
    {
      $realQuestion += ": (y/n)" 
    }
    Write-Host $realQuestion -NoNewline
    $answer = Read-Host
    if ($answer -eq '' -and $default -ne $null) 
    {
        return $default
    }
    $answer = $answer.ToLower()
  } while (!$yesValues.Contains($answer) -and !$noValues.Contains($answer))

  return $yesValues.Contains($answer);
}

<#
.SYNOPSIS
Get / download nunit test runners and return the folder
with the console runner
 
.DESCRIPTION
 
.EXAMPLE
 
$nunitConsoleRunner = GEt-NunitTestsConsoleRunner
set-alias nunit "$nunitConsoleRunner\nunit3-console-exe"
 
#>

function Get-NunitTestsConsoleRunner
{
  Param 
  (
  )
    
    $nunitLocation = "$env:TEMP\nunitrunners"
    $consoleRunner = ""
    if (Test-Path -Path $nunitLocation) {
      $consoleRunner = Get-ChildItem -Path $nunitLocation -Name nunit3-console.exe -Recurse 
      if ($consoleRunner -eq $null) {
          Write-Host "no runner found in folder $nunitLocation"
          Remove-Item $nunitLocation -Recurse
      }
    }

    if (!(Test-Path -Path $nunitLocation)) {
      $nugetLocation = Get-NugetLocation
      set-alias nunitinternal $nugetLocation
      nunitinternal install NUnit.Runners -OutputDirectory $nunitLocation
    }

    #Now we need to locate the console runner
    $consoleRunner = Get-ChildItem -Path $nunitLocation -Name nunit3-console.exe -Recurse 
    Write-Host "Found nunit runner in $nunitLocation\$consoleRunner"
    return "$nunitLocation\$consoleRunner"
}

<#
.SYNOPSIS
Execute GitVersion using dotnet core version of the tool
 
.DESCRIPTION
This command requires that gitversion tool was already configured in
.config folder of the project in standard dotnet-tools.json file. A simple
content could be
 
{
  "version": 1,
  "isRoot": true,
  "tools": {
    "gitversion.tool": {
      "version": "5.2.4",
      "commands": [
        "dotnet-gitversion"
      ]
    }
  }
}
 
.PARAMETER ConfigurationFile
Location of GitVersion.yml file, you can specify full path to the file
 
.EXAMPLE
 
$version = Invoke-GitVersion
 
Write-Host "Assembly version is $($version.assemblyVer)"
Write-Host "File version is $($version.assemblyFileVer)"
Write-Host "Nuget version is $($version.nugetVersion)"
Write-Host "Informational version is $($version.assemblyInformationalVersion)"
#>

function Invoke-Gitversion
{
    Param 
    (
        [string] $ConfigurationFile = "GitVersion.yml"
    )

    [hashtable]$return = @{}

    Write-Host "restoring tooling for gitversion"
    dotnet tool restore

    Write-Host "Running gitversion to determine version with config file $ConfigurationFile"
    $gitVersionOutput = dotnet tool run dotnet-gitversion /config $ConfigurationFile | Out-String
    $version = $gitVersionOutput | Out-String | ConvertFrom-Json
    Write-Host "Raw GitVersion output"
    Write-Host $gitVersionOutput

    Write-Host "Parsed value to be returned"
    $return.assemblyVersion = $version.AssemblySemVer
    $return.assemblyFileVersion = $version.AssemblySemFileVer
    $return.nugetVersion = $version.NuGetVersionV2
    $return.assemblyInformationalVersion = $version.FullSemVer + "." + $version.Sha
    $return.fullSemver = $version.FullSemVer

    Write-Host "Assembly version is $($return.assemblyVersion)"
    Write-Host "File version is $($return.assemblyFileVersion)"
    Write-Host "Nuget version is $($return.nugetVersion)"
    Write-Host "Informational version is $($return.assemblyInformationalVersion)"
    Write-Host "FullSemVer version is $($return.fullSemver)"

    return $return
}
Export-ModuleMember -Function * -Cmdlet *