Saritasa.Build.psm1

<#
.SYNOPSIS
Downloads nuget.exe to specified location.
#>

function Install-NugetCli
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [string] $Destination
    )

    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $nugetExePath = "$Destination\nuget.exe"

    if (!(Test-Path $nugetExePath))
    {
        Write-Information 'Downloading nuget.exe...'
        Invoke-WebRequest 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' -OutFile $nugetExePath
        Write-Information 'Done.'
    }

    $nugetVersion = ((Get-Item $nugetExePath).VersionInfo.ProductVersion).Split('.')[0]
    if ($nugetVersion -lt 4)
    {
        Write-Information 'Downloading nuget.exe...'
        Invoke-WebRequest 'https://dist.nuget.org/win-x86-commandline/v4.0.0/nuget.exe' -OutFile $nugetExePath
        Write-Information 'Done.'
    }
}

<#
.SYNOPSIS
Restores packages for solution, project or packages.config.
#>

function Invoke-NugetRestore
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, HelpMessage = 'Path to solution. All NuGet packages from included projects will be restored.', ParameterSetName = 'Solution')]
        [string] $SolutionPath,
        [Parameter(Mandatory = $true, HelpMessage = 'Path to project or packages.config.', ParameterSetName = 'Project')]
        [string] $ProjectPath,
        [Parameter(Mandatory = $true, HelpMessage = 'Path to the solution directory. Not valid when restoring packages for a solution.', ParameterSetName = 'Project')]
        [string] $SolutionDirectory
    )

    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    Install-NugetCli -Destination $PSScriptRoot
    $nugetExePath = "$PSScriptRoot\nuget.exe"

    $params = @('restore')
    if ($SolutionPath)
    {
        $params += $SolutionPath
    }
    else
    {
        $params += @($ProjectPath, '-SolutionDirectory', $SolutionDirectory)
    }

    &$nugetExePath $params
    if ($LASTEXITCODE)
    {
        throw 'Nuget restore failed.'
    }
}

function Invoke-SolutionBuild
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, HelpMessage = 'Path to solution.')]
        [string] $SolutionPath,
        [Parameter(HelpMessage = 'Build configuration (Release, Debug, etc.)')]
        [string] $Configuration
    )

    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    Invoke-ProjectBuild $SolutionPath $Configuration
}

function Invoke-ProjectBuild
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, HelpMessage = 'Path to project.')]
        [string] $ProjectPath,
        [Parameter(HelpMessage = 'Build configuration (Release, Debug, etc.)')]
        [string] $Configuration,
        [string] $Target = 'Build',
        [string[]] $BuildParams
    )

    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    msbuild.exe $ProjectPath '/m' "/t:$Target" "/p:Configuration=$Configuration" '/verbosity:normal' $BuildParams
    if ($LASTEXITCODE)
    {
        throw 'Build failed.'
    }
}

<#
.SYNOPSIS
Update version numbers of AssemblyInfo.cs and AssemblyInfo.vb.
 
.NOTES
Based on SetVersion script.
http://www.luisrocha.net/2009/11/setting-assembly-version-with-windows.html
Copyright (c) 2009 Luis Rocha
#>

function Update-AssemblyInfoFile
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param
    (
        [Parameter(Mandatory = $true, HelpMessage = 'Version string in major.minor.build.revision format.')]
        [string] $Version
    )

    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $assemblyVersionPattern = 'AssemblyVersion\("[0-9]+(\.([0-9]+|\*)){1,3}"\)'
    $fileVersionPattern = 'AssemblyFileVersion\("[0-9]+(\.([0-9]+|\*)){1,3}"\)'
    $assemblyVersion = 'AssemblyVersion("' + $Version + '")';
    $fileVersion = 'AssemblyFileVersion("' + $Version + '")';

    Get-ChildItem -r -Include AssemblyInfo.cs, AssemblyInfo.vb | ForEach-Object `
        {
            $filename = $_.Directory.ToString() + '\' + $_.Name

            # If you are using a source control that requires to check-out files before
            # modifying them, make sure to check-out the file here.
            # For example, TFS will require the following command:
            # tf checkout $filename

            if ($PSCmdlet.ShouldProcess($filename))
            {
                (Get-Content $filename) | ForEach-Object `
                    {
                        ForEach-Object { $_ -replace $assemblyVersionPattern, $assemblyVersion } |
                        ForEach-Object { $_ -replace $fileVersionPattern, $fileVersion }
                    } | Set-Content $filename -Encoding UTF8

                Write-Information ($filename + ' -> ' + $Version)
            }
        }
}

function Copy-DotnetConfig
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, HelpMessage = 'Path to App.config.template or Web.config.template file.')]
        [string] $TemplateFilename
    )

    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $configFilename = $TemplateFilename -replace '\.template', ''
    if (!(Test-Path $configFilename))
    {
        Copy-Item $TemplateFilename $configFilename
    }
}

<#
.SYNOPSIS
Run Entity Framework migrations.
 
.NOTES
In essential this command tries to find migrate.exe in packages and run it against specified
configuration file.
#>

function Invoke-EFMigrate
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, HelpMessage = 'Path to assembly file with migrations.')]
        [string] $MigrationAssembly,
        [Parameter(HelpMessage = 'Path to assembly .config file. If not specified default or parent Web.config will be used.')]
        [string] $ConfigFilename
    )

    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    # Format and validate params
    if (!$ConfigFilename)
    {
        $ConfigFilename = $MigrationAssembly + '.config'
        if (!(Test-Path $ConfigFilename))
        {
            $ConfigFilename = Join-Path (Split-Path $MigrationAssembly) '..\Web.config'
        }
    }
    if (!(Test-Path $ConfigFilename))
    {
        throw "$ConfigFilename does not exist."
    }
    if (!(Test-Path $MigrationAssembly))
    {
        throw "$MigrationAssembly does not exist."
    }

    # Find migrate.exe
    $packagesDirectory = Get-ChildItem 'packages' -Recurse -Depth 3 |
        Where-Object { $_.PSIsContainer } | Select-Object -First 1
    if (!$packagesDirectory)
    {
        throw 'Cannot find packages directory.'
    }
    Write-Information "Found $packagesDirectory.FullName"
    $migrateExeDirectory = Get-ChildItem $packagesDirectory.FullName 'EntityFramework.*' |
        Sort-Object { $_.Name } | Select-Object -Last 1
    if (!$migrateExeDirectory)
    {
        throw 'Cannot find entity framework package.'
    }
    $migrateExe = Join-Path $migrateExeDirectory.FullName '.\tools\migrate.exe'
    Write-Information "Found $migrateExeDirectory.FullName"

    # Run migrate
    $workingDirectory = Get-Location
    $args = @(
        [System.IO.Path]::GetFileName($MigrationAssembly)
        '/startUpDirectory:"{0}"' -f (Join-Path $workingDirectory (Split-Path $MigrationAssembly))
        '/startUpConfigurationFile:"{0}"' -f (Join-Path $workingDirectory $ConfigFilename)
    );
    &"$migrateExe" $args
    if ($LASTEXITCODE)
    {
        throw "Migration failed."
    }
}

<#
.SYNOPSIS
Replaces placeholders $(UserName) with values from hashtable.
.EXAMPLE
Update-VariablesInFile -Path Config.xml @{UserName='sa'}
#>

function Update-VariablesInFile
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [string] $Path,
        [Parameter(Mandatory = $true)]
        [hashtable] $Variables
    )

    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $content = Get-Content $Path

    foreach ($key in $Variables.Keys)
    {
        $escapedValue = $Variables[$key] -replace '\$', '$$$$'
        $content = $content -ireplace"\`$\($key\)", $escapedValue
    }

    $content | Set-Content $Path
}

<#
.SYNOPSIS
Adds correct path to MSBuild to Path environment variable.
#>

function Initialize-MSBuild
{
    [CmdletBinding()]
    param ()

    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $vsPath = (@((Get-VSSetupInstance | Select-VSSetupInstance -Version 15.0 -Require Microsoft.Component.MSBuild).InstallationPath,
        (Get-VSSetupInstance | Select-VSSetupInstance -Version 15.0 -Product Microsoft.VisualStudio.Product.BuildTools).InstallationPath) -ne $null)[0]

    if (!$vsPath)
    {
        Write-Information 'VS 2017 not found.'
        return
    }

    if ([System.IntPtr]::Size -eq 8)
    {
        $msbuildPath = Join-Path $vsPath 'MSBuild\15.0\Bin\amd64'
    }
    else
    {
        $msbuildPath = Join-Path $vsPath 'MSBuild\15.0\Bin'
    }

    $env:Path = $msbuildPath + ";$env:Path"
}

<#
.SYNOPSIS
Loads packages from many packages.config and saves to a single file.
.EXAMPLE
Merge-PackageConfigs -SolutionDirectory .\src -OutputPath .\src\packages.merged.config
.EXAMPLE
Merge-PackageConfigs -SolutionDirectory .\src -OutputPath .\src\packages.merged.net40.config -Framework net40
Merge-PackageConfigs -SolutionDirectory .\src -OutputPath .\src\packages.merged.net452.config -Framework net452
#>

function Merge-PackageConfigs
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [string] $SolutionDirectory,
        [Parameter(Mandatory = $true)]
        [string] $OutputPath,
        [Parameter]
        [string] $Framework
    )

    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $files = Get-ChildItem $src -Recurse packages.config

    $packagesSet = New-Object 'System.Collections.Generic.HashSet[string]'

    foreach ($file in $files)
    {
        [xml] $xml = Get-Content $file.FullName
        $xml.packages.package | % `
            {
                if (!$Framework -or $_.targetFramework -eq $Framework)
                {
                    $packagesSet.Add($_.OuterXml) | Out-Null
                }
            }
    }

    [xml] $finalXml = '<?xml version="1.0" encoding="utf-8"?><packages>' + $packagesSet + '</packages>'
    $finalXml.Save($OutputPath)
}