Publish-Website.psm1

<#
.SYNOPSIS
    Parse project and all child projects and return dependent libraries
.DESCRIPTION
    Parse project and all child projects and return dependent libraries
.PARAMETER Project
    Path to project file
.PARAMETER Configuration
    Build configuration.
 
    Valid values are Release and Debug.
     
    Default is Release.
.EXAMPLE
    Get-Project-Libraries -Project .\Components\DMS\DMS\DMS.csproj -Configuration 'Release'
.EXAMPLE
    Get-Item -LiteralPath .\Components\DMS\DMS\DMS.csproj | Get-Project-Libraries
#>

function Get-Project-Libraries
{
    Param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage="Project file")]
        [string]
        $Project,
        [Parameter(HelpMessage="Build configuration")]
        [string]
        $Configuration = 'Release',
        #Level
        [int]
        $Level = 0
    )
    Begin{
    }
    Process{
        # Log with Level
        Write-Verbose "$(''.PadLeft($Level,' ')) Reading project $([IO.Path]::GetFullPath($Project))"
        # Get content

        $xmlContent = [xml] (Get-Content -LiteralPath $Project)
        $directory = Split-Path $Project -Parent

        # Referenced libraries with HintPath
        $xmlContent.Project.ItemGroup.Reference.HintPath  | ForEach-Object {
            if($_){
                Write-Output ([IO.Path]::GetFullPath([IO.Path]::Combine($directory, $_)))
            }
        }
        # Project assembly name

        $assemblyName = $null

        $xmlContent.Project.PropertyGroup.AssemblyName | ForEach-Object {
            if($_){
                $assemblyName = $_ + ".dll"

                if($xmlContent.Project.Sdk.Length){
                    Write-Output ([IO.Path]::GetFullPath([IO.Path]::Combine($directory, 'bin', $Configuration, 'netstandard2.0', $assemblyName)))
                }
                else{
                    Write-Output ([IO.Path]::GetFullPath([IO.Path]::Combine($directory, 'bin', $Configuration, $assemblyName)))
                }
            }
        }
        #
        if($null -eq $assemblyName){
            $assemblyName = [IO.Path]::GetFileNameWithoutExtension($Project) + ".dll"
            Write-Output ([IO.Path]::GetFullPath([IO.Path]::Combine($directory, 'bin', $Configuration, $assemblyName)))
        }
        $Level += 4
        # Subproject
        Get-Content -LiteralPath $Project | Where-Object { $_ -match '<ProjectReference Include="(.+)"' } | ForEach-Object {
            $anotherProjectPath = Join-Path -Path $directory -ChildPath $matches[1]
            Get-Project-Libraries -Project $anotherProjectPath -Configuration $Configuration $Level
        }
    }
    End{
    }
}

<#
.SYNOPSIS
    Automati Webform Publish Tool (AWT)
.DESCRIPTION
    Compile and publish website projects
.PARAMETER Solution
    Path to solution file
.PARAMETER Configuration
    Build configuration.
 
    Valid values are Release and Debug.
     
    Default is Release.
.EXAMPLE
    Publish-Website -Solution .\cisss.sln -Configuration 'Release'
.EXAMPLE
    Get-Item -LiteralPath .\cisss.sln | Publish-Website
#>

function Publish-Website
{
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage="Solution file")]
        [string]
        $Solution,

        [Parameter(HelpMessage="Build configuration")]
        [ValidateSet('Release','Debug')]
        [string]
        $Configuration = "Release",

        [switch]
        [Parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$false, HelpMessage="Disable restore nugets")]
        $DisableRestoreNugets,
        
        [switch]
        [Parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$false, HelpMessage="Force rebuild")]
        $ForceRebuild,

        [ValidateSet('quiet','minimal','normal',' detailed', 'diagnostic')]
        $Verbosity = "quiet",

        [switch]
        [Parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$false, HelpMessage="Disable cleanup")]
        $DisableCleanup        
    )
    Begin{
    }
    Process{
        #
        if(!(Test-Path -LiteralPath $Solution -PathType 'Leaf')){
            throw "File is missing [$Solution]."
        }
        #
        $solutionFile = ( Resolve-Path $Solution ).Path
        $solutionPath = Split-Path $solutionFile -Parent
        #
        $projectSections = Get-Content -LiteralPath $solutionFile -Delimiter "EndProject"
        #restore nugets
        if(!$DisableRestoreNugets.IsPresent){
            Write-Output "#### AWT RESTORE nugets ####"
            nuget restore $solutionFile -verbosity quiet
            if($LastExitCode) {
                throw "NUGET RESTORE failed"
            }            
        }
        #prepare publish
        $projectSections | ForEach-Object {
            # Spracuj iba WebSite projekty
            if($_ -match "Project\(`"{E24C65DC-7377-472B-9ABA-BC803B73C61A}`"\) = `"([^`"]*)`", `"([^`"]*)`""){
                $site = $Matches[1]
                $sitePath = $Matches[2]
                Write-Verbose "Found website project $site with $sitePath"

                $binPath = [IO.Path]::Combine($solutionPath, $sitePath, 'bin')
                if(!(Test-Path -LiteralPath $binPath -PathType 'Container')){
                    New-Item -Path $binPath -Type 'Directory' | Out-Null
                }

                $publishPath = $null
                if($_ -match "$Configuration.AspNetCompiler.TargetPath = `"([^`"]*)`""){
                    $publishPath = ([IO.Path]::Combine($solutionPath, $Matches[1])) 
                }    

                # cleanup
                if( -not ($DisableCleanup.IsPresent)) {
                    Write-Verbose "Remove publish [$publishPath] & bin [$binPath] folder "
                    #
                    if($null -ne $publishPath) {
                        Remove-Item -Path $publishPath -Force -Recurse -ErrorAction SilentlyContinue 
                    }
                    #
                    Get-ChildItem -Path $binPath -Recurse | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
                }

                #
                if($_ -match "ProjectReferences = `"([^`"]*)`""){
                    $childProjects = @()
                    $Matches[1] -split ';' | ForEach-Object {
                        $projectReference, $library = $_.Split('|')

                        if($library){
                            $projectSections | Where-Object { $_ -match "Project\(`"{.*}`"\) = `"[^`"]*`", `"([^`"]*)`", `"$projectReference`"" } | Select-Object -First 1 | Out-Null
                            Write-Verbose "Found required library $library from project $($Matches[1])"
                            $projectFile = [IO.Path]::Combine($solutionPath, $Matches[1])
                            $projectPath = Split-Path $projectFile -Parent

                            $fileName = [IO.Path]::GetFileName($projectFile)

                            if($childProjects -notcontains $projectFile){
                                $childProjects+= $projectFile
                            }

                            if((Get-Content -LiteralPath $projectFile) -match "<Project Sdk=`"Microsoft.NET.Sdk`">"){
                                $libraryPath = [IO.Path]::Combine($projectPath, 'bin', $Configuration, 'netstandard2.0', $library)
                                #
                                if(!(Test-Path -LiteralPath $libraryPath -PathType 'Leaf') -or $ForceRebuild.IsPresent){
                                    Write-Output "#### AWT BUILD .netstandard library [$fileName] ####"
                                    dotnet build -nologo --configuration $Configuration $projectFile
                                    if($LastExitCode) {
                                        throw "DOTNET $(Split-Path -Leaf $project) failed"
                                    }                                    
                                }
                                #
                                if(!(Test-Path -LiteralPath $libraryPath -PathType 'Leaf')){
                                    throw("Missing library $libraryPath");
                                }
                            }
                            else{
                                $libraryPath = [IO.Path]::Combine($projectPath, 'bin', $Configuration, $library)
                                #
                                if(!(Test-Path -LiteralPath $libraryPath -PathType 'Leaf') -or $ForceRebuild.IsPresent){
                                    Write-Output "#### AWT BUILD .net48 library [$fileName] ####"
                                    $buildResult = Invoke-MsBuild $projectFile  -ShowBuildOutputInCurrentWindow -MsBuildParameters "/restore /target:Restore;Build /p:Configuration=$Configuration /verbosity:$Verbosity /nologo /noWarn:CS0618;CS0169;CS0168"
                                    if($buildResult.BuildSucceeded -eq $false){
                                        throw ("Build failed after {0:N1} seconds." -f $buildResult.BuildDuration.TotalSeconds)
                                    }
                                }
                                #
                                if(!(Test-Path -LiteralPath $libraryPath -PathType 'Leaf')){
                                    throw("Missing library $libraryPath");
                                }
                            }
                        }
                    }

                    Write-Output "#### AWT PREPARE website: $site ####"

                    # Copy child dependencies
                    $childProjects | Get-Project-Libraries -Configuration $Configuration | Select-Object -Unique | ForEach-Object {
                        if(Test-Path -LiteralPath $_ -PathType 'Leaf'){
                            Write-Verbose "Copying $_ to $binPath"
                            Copy-Item -LiteralPath $_ -Destination $binPath -Force -ErrorAction SilentlyContinue
                        }
                        else{
                            Write-Warning "Missing $_!"
                        }
                    }
                }
            }
        }
        #publish
        $projectSections | ForEach-Object {
            # Spracuj iba WebSite projekty
            if($_ -match "Project\(`"{E24C65DC-7377-472B-9ABA-BC803B73C61A}`"\) = `"([^`"]*)`", `"([^`"]*)`""){
                $site = $Matches[1]

                $publishPath = $null
                if($_ -match "$Configuration.AspNetCompiler.TargetPath = `"([^`"]*)`""){
                    $publishPath = ([IO.Path]::Combine($solutionPath, $Matches[1])) 
                }    

                Write-Output "#### AWT PUBLISH website: $site ####"

                # Build Website
                $siteName = $site.Replace(".","_")
                #
                $buildResult = Invoke-MsBuild $solutionFile -ShowBuildOutputInCurrentWindow -MsBuildParameters "/restore /target:$siteName;Restore;Build;Publish /p:RestoreLockedMode=true /p:Configuration=$Configuration /verbosity:$Verbosity /nologo /noWarn:CS0618;CS0169;CS0168"
                if($buildResult.BuildSucceeded -eq $false){
                    throw ("Publish failed after {0:N1} seconds." -f $buildResult.BuildDuration.TotalSeconds)
                }

                # Cleanup
                if($null -ne $publishPath) {
                    Write-Verbose "Publish cleanup [$publishPath]"
                    Get-ChildItem -Path $publishPath -Recurse -Filter *.pdb -File -ErrorAction SilentlyContinue | Remove-Item -Force
                }    
            }
        }
    }
    End{
    }
}

Export-ModuleMember -Function Publish-WebSite

# SIG # Begin signature block
# MIIH7gYJKoZIhvcNAQcCoIIH3zCCB9sCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUVaInNEMi3GPer2ExOdLKwZnV
# h8OgggUPMIIFCzCCAvOgAwIBAgICAKUwDQYJKoZIhvcNAQELBQAwgaAxCzAJBgNV
# BAYTAlNLMREwDwYDVQQIEwhTbG92YWtpYTETMBEGA1UEBxMKQnJhdGlzbGF2YTET
# MBEGA1UEChMKTG9tdGVjLmNvbTErMCkGA1UEAxMiTG9tdGVjLmNvbSBDZXJ0aWZp
# Y2F0aW9uIEF1dGhvcml0eTEnMCUGCSqGSIb3DQEJARYYSW1yaWNoLlN6b2xpa0Bs
# b210ZWMuY29tMB4XDTE5MDMyNjE1MDQ0M1oXDTMwMDEwMTAwMDAwMFowgZMxCzAJ
# BgNVBAYTAlNLMREwDwYDVQQIEwhTbG92YWtpYTETMBEGA1UEBxMKQnJhdGlzbGF2
# YTETMBEGA1UEChMKTG9tdGVjLmNvbTEeMBwGA1UEAxMVTG9tdGVjLmNvbSBQb3dl
# cnNoZWxsMScwJQYJKoZIhvcNAQkBFhhJbXJpY2guU3pvbGlrQGxvbXRlYy5jb20w
# ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+J+Jwh25Ha39PSBzRUNj/
# 8+iONxiknLBSTkB1DJEQ9wC6FBS/1EQvbFtn2K6huVZBQCoZzNFcXoiAked3ALIT
# UoYxX+fD4Wkve947QqCXGroCFQVaBSLkPoJzWCadlXQWyDyjFuCY42TsQRJB6TFp
# n4rVylhinlOvEHJ96COjzvmEs7X1y3tTVPtRiDt3wOxNLx1T1H/6lmVzGFORJS0F
# ZvQF231+/Zk4Je+gOe/jguo/gHoZHdjj31JeMLJ6Md3zslAFAPIqI3lofez6V3f7
# PkZLsVnYLAuGElg1I4WUswZJpfbnq2KqPWvlwG9/Xb5KSvUz5d6uRzQUDJAn6fG5
# AgMBAAGjWjBYMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgeAMCsGA1UdJQQkMCIGCCsG
# AQUFBwMDBgorBgEEAYI3AgEVBgorBgEEAYI3AgEWMBEGCWCGSAGG+EIBAQQEAwIE
# sDANBgkqhkiG9w0BAQsFAAOCAgEACSWqb/bJGI4eR07CZ6g3baoe2Djl7gPIPgyg
# Y/uYAxGROVfVYQBVafFCfUaCExhiEU1SHdJanCyYlUkx8d9OVVvWiA/L6O+5H608
# l7z1zSLE7R5z5JMB+O/gqImNyKT0C41AP0OkHasCDb78KJu/1nPjqCKpT3SR+Q2w
# ayMvOXa+jPy5t0cNgaJXE0cF0wd8QIcHrYe/AT+qW3AHd9hXcppEaKSyIobjbO4O
# 2Uf0P+fNNxvjokCDERsk72kPmkIyYBaZM6ezSMIc6LoHwjleO0nPPYxKVKsNeotl
# BX89ssDpVlMmLbntUtT4zNZY4CX0aPntMFJxaYBUM1xhdW02FWkOHt5yZXgA376n
# l6B9gGHgU28/QXKIJJH1EQAt4oqG9WzbOYxNQm6xAwsOpv9N5NKDppt8UJLF8gIQ
# WLlJEcLvY3eMtxcGf7Mgxi892+nGPKNWMiDTjgNwdq/drITbdK1oSbt7/qnCFZhU
# NGipYTTUaIlZUICezYDtFFfr1NpYIrHKVEsRLcSjfuOdK+PbLtOjQOs7Mp9whC1J
# lZD8o8eX8os8LfSsVbmDlEhG99mO0An3xydarRekrkKym4G/9uMu3Ag1ujw1Ubzz
# sklJSrvbD8pCAg4781wb42BCCzOwJrWAiARkIIpgbEL0xKFyV4sADotVzc9y8XI0
# B0P2TOMxggJJMIICRQIBATCBpzCBoDELMAkGA1UEBhMCU0sxETAPBgNVBAgTCFNs
# b3Zha2lhMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpMb210ZWMuY29t
# MSswKQYDVQQDEyJMb210ZWMuY29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MScw
# JQYJKoZIhvcNAQkBFhhJbXJpY2guU3pvbGlrQGxvbXRlYy5jb20CAgClMAkGBSsO
# AwIaBQCgeDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEM
# BgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEWMCMGCSqG
# SIb3DQEJBDEWBBSQ/8Rxrqj/SRKCId0TF02xv82n3DANBgkqhkiG9w0BAQEFAASC
# AQBuWncJPPLw6wISpzXcMNpjvWXAhhvPkjoN53DqclqkysZdk6A+lK1+JeXtT9Rb
# r2hYWLGe9vz5mkTLI9moOA6xEoyr2qlhSJU6wA7KVEHDPj/SR+8HCB9ojGWwP/+X
# wJni7pOabF7FfIkNNVz7PVCggDWp6x83COLCMUF+AoQ3q+rGYYzFJH3Oaa9os6lV
# ksmQnt8keCMoVaVOGzyWG+XcPIobqKRAjnZ4UM/JCwUW6MVkY74cZMxR2IQkSi6T
# z4LzznmVeUr1GBS0ar1cdsPRTJuVIbVecEot0ZR7qHE4sJxPIlf/SAYUqdEckowl
# uCHidbFlFP87NR5Caj363QhC
# SIG # End signature block