Saritasa.Build.psm1
$assemblyVersionRegex = $null $assemblyInfoVersionRegex = $null $assemblyFileVersionRegex = $null <# .SYNOPSIS Downloads latest nuget.exe to specified location. .EXAMPLE Install-NugetCli . Install nuget into current directory #> 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.' } } <# .SYNOPSIS Restores packages for solution, project or packages.config. .EXAMPLE Invoke-NugetRestore .\..\myapp.sln Restores all packages for myapp solution. .NOTES If nuget command is not found - it will be downloaded to current directory. #> function Invoke-NugetRestore { [CmdletBinding()] param ( # Path to solution. All NuGet packages from included projects will be restored. [Parameter(Mandatory = $true, ParameterSetName = 'Solution')] [string] $SolutionPath, # Path to project or packages.config. [Parameter(Mandatory = $true, ParameterSetName = 'Project')] [string] $ProjectPath, # Path to the solution directory. Not valid when restoring packages for a solution. [Parameter(Mandatory = $true, ParameterSetName = 'Project')] [string] $SolutionDirectory ) Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $nugetCli = Get-Command nuget.exe -ErrorAction SilentlyContinue if ($nugetCli) { $nugetExePath = $nugetCli.Source } else { Write-Warning "Install NuGet globally for faster builds:`nchoco install nuget.commandline" 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.' } } <# .SYNOPSIS Builds solution. .EXAMPLE Invoke-SolutionBuild .\..\myapp.sln -Configuration Debug #> function Invoke-SolutionBuild { [CmdletBinding()] param ( # Path to solution. [Parameter(Mandatory = $true)] [string] $SolutionPath, # Build configuration (Release, Debug, etc.) [string] $Configuration ) Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Invoke-ProjectBuild $SolutionPath $Configuration } <# .SYNOPSIS Builds project. .PARAMETER Target Build the specified targets in the project. Use a semicolon or comma to separate multiple targets. .EXAMPLE Invoke-ProjectBuild .\..\Web\Web.csproj -Configuration 'Release' .NOTES For more information about Target and BuildParams parameters, see MSBuild documentation. #> function Invoke-ProjectBuild { [CmdletBinding()] param ( # Path to project. [Parameter(Mandatory = $true)] [string] $ProjectPath, # Build configuration (Release, Debug, etc.) [string] $Configuration, [string] $Target = 'Build', # Additional build parameters. [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.' } } function ReplaceOrAppend([regex] $ReplaceRegex, [string] $InputString, [string] $ReplaceString, [string] $AssemblyAddFormat) { if ($ReplaceRegex.IsMatch($InputString)) { $InputString = $ReplaceRegex.Replace($InputString, $ReplaceString) } else { $InputString += [Environment]::NewLine + [string]::Format($AssemblyAddFormat, $ReplaceString) } $InputString } function ProcessAssemblyInfoFile([string] $FileName, [string] $AssemblyVersionString, [string] $AssemblyInfoVersionString, [string] $AssemblyFileVersionString) { $assemblyAddFormat = switch ((Get-Item $FileName).Extension) { '.cs' { '[assembly: {0}]' } '.vb' { '<Assembly: {0}>' } '.fs' { '[<assembly: {0}>]' } default { $null } } if (!$assemblyAddFormat) { Write-Warning "Unknown file format: $FileName" } $content = Get-Content $FileName -Raw -Encoding UTF8 if ($AssemblyVersionString) { $content = ReplaceOrAppend $script:assemblyVersionRegex $content $AssemblyVersionString $assemblyAddFormat } if ($AssemblyFileVersionString) { $content = ReplaceOrAppend $script:assemblyFileVersionRegex $content $AssemblyFileVersionString $assemblyAddFormat } if ($AssemblyInfoVersionString) { $content = ReplaceOrAppend $script:assemblyInfoVersionRegex $content $AssemblyInfoVersionString $assemblyAddFormat } Set-Content -Path $FileName -Encoding UTF8 -Value $content } <# .SYNOPSIS Update version numbers of AssemblyInfo.cs and AssemblyInfo.vb. .DESCRIPTION Updates version numbers in AssemblyInfo files located in current directory and all subdirectories. .EXAMPLE Update-AssemblyInfoFile -AssemblyVersion '6.3.0.0' -AssemblyFileVersion '6.3.1.1' -AssemblyInfoVersion '6.3.0+master.808d1733f5c701c1882816f70c3eafc6e7fce4d4' .NOTES Based on SetVersion script. http://www.luisrocha.net/2009/11/setting-assembly-version-with-windows.html Copyright (c) 2009 Luis Rocha Based on GitVersion code. https://github.com/GitTools/GitVersion #> function Update-AssemblyInfoFile { [CmdletBinding(SupportsShouldProcess = $true)] param ( # Version string in major.minor.build.revision format. [Parameter(Mandatory = $true)] [Alias("Version")] [string] $AssemblyVersion, [string] $AssemblyInfoVersion, [string] $AssemblyFileVersion, # Path to root directory. [string] $Path ) Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $script:assemblyVersionRegex = [regex] 'AssemblyVersion(Attribute)?\s*\(.*\)\s*' $assemblyVersionString = if ($AssemblyVersion) { "AssemblyVersion(`"$AssemblyVersion`")" } $script:assemblyInfoVersionRegex = [regex] 'AssemblyInformationalVersion(Attribute)?\s*\(.*\)\s*' $assemblyInfoVersionString = if ($AssemblyInfoVersion) { "AssemblyInformationalVersion(`"$AssemblyInfoVersion`")" } $script:assemblyFileVersionRegex = [regex] 'AssemblyFileVersion(Attribute)?\s*\(.*\)\s*' $assemblyFileVersionString = if ($AssemblyFileVersion) { "AssemblyFileVersion(`"$AssemblyFileVersion`")" } $rootDirectory = if ($Path) { $Path } else { Get-Location } Get-ChildItem -Path $rootDirectory -Recurse -Include AssemblyInfo.cs, AssemblyInfo.vb, AssemblyInfo.fs | ForEach-Object ` { $fileName = $_.FullName if ($PSCmdlet.ShouldProcess($fileName)) { ProcessAssemblyInfoFile $fileName $assemblyVersionString $assemblyInfoVersionString $assemblyFileVersionString Write-Information "$fileName -> $AssemblyVersion" } } } <# .SYNOPSIS Creates file from a template. .DESCRIPTION Creates a config file from it's template. If file already exists, it will not be overridden. .EXAMPLE Copy-DotnetConfig .\..\Web\Web.config.template Creates a Web.config file in Web folder from template. #> function Copy-DotnetConfig { [CmdletBinding()] param ( # Path to App.config.template or Web.config.template file. [Parameter(Mandatory = $true)] [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. .EXAMPLE Invoke-EFMigrate ..\..\Domain\bin\Debug\Domain.dll Runs all migrations declared in Domain.dll file, using Domain.dll.config as configuration file .NOTES In essential this command tries to find migrate.exe in packages and run it against specified configuration file. #> function Invoke-EFMigrate { [CmdletBinding()] param ( # Path to assembly file with migrations. [Parameter(Mandatory = $true)] [string] $MigrationAssembly, # 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 = $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 $vsDefinitions = ` @( @{ Version = '16.0'; Product = 'Microsoft.Component.MSBuild'; MSBuildVersion = 'Current' } @{ Version = '16.0'; Product = 'Microsoft.VisualStudio.Product.BuildTools'; MSBuildVersion = 'Current' } @{ Version = '15.0'; Product = 'Microsoft.Component.MSBuild'; MSBuildVersion = '15.0' } @{ Version = '15.0'; Product = 'Microsoft.VisualStudio.Product.BuildTools'; MSBuildVersion = '15.0' } ) foreach ($vsDefinition in $vsDefinitions) { $vsPath = (Get-VSSetupInstance | Select-VSSetupInstance -Version $vsDefinition.Version -Require $vsDefinition.Product).InstallationPath if ($vsPath) { $msBuildVersion = $vsDefinition.MSBuildVersion break } } if (!$vsPath) { Write-Information 'VS 2017 or 2019 not found.' return } if ([System.IntPtr]::Size -eq 8) { $msbuildPath = Join-Path $vsPath "MSBuild\$msBuildVersion\Bin\amd64" } else { $msbuildPath = Join-Path $vsPath "MSBuild\$msBuildVersion\Bin" } $env:Path = "$msbuildPath;$env:Path" } <# .SYNOPSIS Loads packages from multiple 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 ( # Directory in which to look for packages.config files. [Parameter(Mandatory = $true)] [string] $SolutionDirectory, # Path to file in which results should be saved. If file exists, it will be overridden. [Parameter(Mandatory = $true)] [string] $OutputPath, # If specified, only packages with this framework will be included in the results. [string] $Framework ) Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $files = Get-ChildItem $SolutionDirectory -Recurse packages.config $packagesSet = New-Object 'System.Collections.Generic.HashSet[string]' foreach ($file in $files) { [xml] $xml = Get-Content $file.FullName $xml.packages.package | ForEach-Object ` { 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) } <# .SYNOPSIS Removes Roslyn analyzer references from Visual Studio project. #> function Remove-RoslynAnalyzer { [CmdletBinding()] param ( # Path to Visual Studio project. [Parameter(Mandatory = $true)] [string] $ProjectPath ) $xml = [xml](Get-Content $ProjectPath) $project = $xml.DocumentElement $nodesToRemove = @() $xml.GetElementsByTagName("ItemGroup") | ForEach-Object ` { if ($_.GetElementsByTagName('Analyzer').Count) { $nodesToRemove += $_ } } $nodesToRemove | ForEach-Object { $project.RemoveChild($_) } | Out-Null $xml.Save($ProjectPath) } |