tasks/Build-Module.ModuleBuilder.build.ps1

param
(
    [Parameter()]
    [System.String]
    $ProjectName = (property ProjectName ''),

    [Parameter()]
    [System.String]
    $SourcePath = (property SourcePath ''),

    [Parameter()]
    [System.String]
    $OutputDirectory = (property OutputDirectory (Join-Path $BuildRoot "output")),

    [Parameter()]
    [System.String]
    $BuiltModuleSubdirectory = (property BuiltModuleSubdirectory ''),

    [Parameter()]
    [System.Management.Automation.SwitchParameter]
    $VersionedOutputDirectory = (property VersionedOutputDirectory $true),

    [Parameter()]
    [System.String]
    $BuildModuleOutput = (property BuildModuleOutput (Join-Path $OutputDirectory $BuiltModuleSubdirectory)),

    [Parameter()]
    [System.String]
    $ReleaseNotesPath = (property ReleaseNotesPath (Join-Path $OutputDirectory 'ReleaseNotes.md')),

    [Parameter()]
    [System.String]
    $ModuleVersion = (property ModuleVersion ''),

    [Parameter()]
    [System.Collections.Hashtable]
    $BuildInfo = (property BuildInfo @{ })
)

# Synopsis: Build the Module based on its Build.psd1 definition
Task Build_ModuleOutput_ModuleBuilder {
    if ([System.String]::IsNullOrEmpty($ProjectName))
    {
        $ProjectName = Get-SamplerProjectName -BuildRoot $BuildRoot
    }

    if ([System.String]::IsNullOrEmpty($SourcePath))
    {
        $SourcePath = Get-SamplerSourcePath -BuildRoot $BuildRoot
    }

    $OutputDirectory = Get-SamplerAbsolutePath -Path $OutputDirectory -RelativeTo $BuildRoot
    $BuiltModuleSubdirectory = Get-SamplerAbsolutePath -Path $BuiltModuleSubdirectory -RelativeTo $OutputDirectory
    $moduleManifestPath = Get-SamplerAbsolutePath -Path "$ProjectName.psd1" -RelativeTo $SourcePath

    $getBuildVersionParameters = @{
        ModuleManifestPath = $moduleManifestPath
        ModuleVersion      = $ModuleVersion
    }

    <#
        This will get the version from $ModuleVersion if is was set as a parameter
        or as a property. If $ModuleVersion is $null or an empty string the version
        will fetched from GitVersion if it is installed. If GitVersion is _not_
        installed the version is fetched from the module manifest in SourcePath.
    #>

    $ModuleVersion = Get-BuildVersion @getBuildVersionParameters
    $ReleaseNotesPath = Get-SamplerAbsolutePath -Path $ReleaseNotesPath -RelativeTo $OutputDirectory

    "`tProject Name = '$ProjectName'"
    "`tModule Version = '$ModuleVersion'"
    "`tSource Path = '$SourcePath'"
    "`tOutput Directory = '$OutputDirectory'"
    "`tBuilt Module Subdirectory = '$BuiltModuleSubdirectory'"
    "`tModule Manifest Path (src) = '$moduleManifestPath'"
    "`tRelease Notes path = '$ReleaseNotesPath'"

    Import-Module -Name ModuleBuilder -ErrorAction 'Stop'

    $buildModuleParams = @{}

    foreach ($paramName in (Get-Command -Name Build-Module).Parameters.Keys)
    {
        if ($paramName -eq 'SourcePath')
        {
            <#
                To support building the without a build manifest the SourcePath must be
                set to the path to the source module manifest.
            #>

            $buildModuleParams.Add($paramName, $moduleManifestPath)
        }
        else
        {
            $valueFromBuildParam = Get-Variable -Name $paramName -ValueOnly -ErrorAction 'SilentlyContinue'
            $valueFromBuildInfo = $BuildInfo[$paramName]

            <#
                If Build-Module parameters are available in current session, use those
                otherwise use params from BuildInfo if specified.
            #>

            if ($valueFromBuildParam)
            {
                Write-Build -Color 'DarkGray' -Text "Adding $paramName with value $valueFromBuildParam from current Variables"

                if ($paramName -eq 'OutputDirectory')
                {
                    $buildModuleParams.Add($paramName, (Join-Path -Path $BuildModuleOutput -ChildPath $ProjectName))
                }
                else
                {
                    $buildModuleParams.Add($paramName, $valueFromBuildParam)
                }
            }
            elseif ($valueFromBuildInfo)
            {
                Write-Build -Color 'DarkGray' "Adding $paramName with value $valueFromBuildInfo from Build Info"

                $buildModuleParams.Add($paramName, $valueFromBuildInfo)
            }
            else
            {
                Write-Debug -Message "No value specified for $paramName"
            }
        }
    }

    Write-Build -Color 'Green' -text "Building Module to $($buildModuleParams['OutputDirectory'])..."

    $BuiltModule = Build-Module @buildModuleParams -SemVer $ModuleVersion -PassThru

    # if we built the PSM1 on Windows with a BOM, re-write without BOM
    if ($PSVersionTable.PSVersion.Major -le 5)
    {
        if (Split-Path -IsAbsolute -Path $BuiltModule.RootModule)
        {
            $Psm1Path = $BuiltModule.RootModule
        }
        else
        {
            $Psm1Path = Join-Path -Path $BuiltModule.ModuleBase -ChildPath $BuiltModule.RootModule
        }

        $RootModuleDefinition = Get-Content -Raw -Path $Psm1Path
        [System.IO.File]::WriteAllLines($Psm1Path, $RootModuleDefinition)
    }

    if (Test-Path -Path $ReleaseNotesPath)
    {
        $releaseNotes = Get-Content -Path $ReleaseNotesPath -Raw

        $outputManifest = $BuiltModule.Path

        Update-Metadata -Path $outputManifest -PropertyName 'PrivateData.PSData.ReleaseNotes' -Value $releaseNotes
    }
}

Task Build_NestedModules_ModuleBuilder {
    if ([System.String]::IsNullOrEmpty($ProjectName))
    {
        $ProjectName = Get-SamplerProjectName -BuildRoot $BuildRoot
    }

    if ([System.String]::IsNullOrEmpty($SourcePath))
    {
        $SourcePath = Get-SamplerSourcePath -BuildRoot $BuildRoot
    }

    $OutputDirectory = Get-SamplerAbsolutePath -Path $OutputDirectory -RelativeTo $BuildRoot
    "`tOutputDirectory = '$OutputDirectory'"
    if ($VersionedOutputDirectory)
    {
        # VersionedOutputDirectory is not [bool]'' nor $false nor [bool]$null
        # Assume true, wherever it was set
        $VersionedOutputDirectory = $true
    }
    else
    {
        # VersionedOutputDirectory may be [bool]'' but we can't tell where it's
        # coming from, so assume the build info (Build.yaml) is right
        $VersionedOutputDirectory = $BuildInfo['VersionedOutputDirectory']
    }

    $GetBuiltModuleManifestParams = @{
        OutputDirectory          = $OutputDirectory
        BuiltModuleSubdirectory  = $BuiltModuleSubDirectory
        ModuleName               = $ProjectName
        VersionedOutputDirectory = $VersionedOutputDirectory
        ErrorAction              = 'Stop'
    }

    $builtModuleManifest = Get-SamplerBuiltModuleManifest @GetBuiltModuleManifestParams
    $builtModuleManifest = (Get-Item -Path $builtModuleManifest).FullName
    "`tBuilt Module Manifest = '$builtModuleManifest'"

    $builtModuleBase = Get-SamplerBuiltModuleBase @GetBuiltModuleManifestParams
    $builtModuleBase = (Get-Item -Path $builtModuleBase).FullName
    "`tBuilt Module Base = '$builtModuleBase'"

    $moduleVersion = Get-BuiltModuleVersion @GetBuiltModuleManifestParams
    $moduleVersionObject = Split-ModuleVersion -ModuleVersion $moduleVersion
    $moduleVersionFolder = $moduleVersionObject.Version
    $preReleaseTag       = $moduleVersionObject.PreReleaseString

    "`tModule Version = '$ModuleVersion'"
    "`tModule Version Folder = '$moduleVersionFolder'"
    "`tPre-release Tag = '$preReleaseTag'"

    Import-Module -Name 'ModuleBuilder' -ErrorAction 'Stop'

    $nestedModule = $BuildInfo.NestedModule
    $nestedModulesToAdd = @()

    foreach ($nestedModuleName in $nestedModule.Keys)
    {
        $cmdParam = $nestedModule[$nestedModuleName]

        $addToManifest = [bool]$cmdParam['AddToManifest']

        # Either copy only or Build
        if ([System.Boolean] $cmdParam['CopyOnly'])
        {
            Write-Build -Color 'Yellow' -Text "Copying Nested Module files for $nestedModuleName"

            $cmd = Get-Command -Name 'Copy-Item'

            if (-not $cmdParam.ContainsKey('Path') -and -not $cmdParam.ContainsKey('SourcePath'))
            {
                $cmdParam['Path'] = '$SourcePath/Modules/$nestedModuleName'
                Write-Build -Color 'DarkGray' -Text " Default param Path is '$($cmdParam['Path'])'"
            }

            # Default to -Recurse unless the BuildInfo is alredy configured
            if (-not $cmdParam.ContainsKey('Recurse'))
            {
                $cmdParam['Recurse'] = $true
                Write-Build -Color 'DarkGray' -Text " Default param Recurse is '$($cmdParam['Recurse'])'"
            }

            # Set default Destination (substitute later)
            if (-not $cmdParam.ContainsKey('Destination'))
            {
                $cmdParam['Destination'] = '$builtModuleBase/Modules/$nestedModuleName'
                Write-Build -Color 'DarkGray' -Text " Default param Destination is '$($cmdParam['Destination'])'"
            }
        }
        else
        {
            # When we want to build the nested modules:
            $cmd = Get-Command -Name 'Build-Module'

            if (-not $cmdParam.ContainsKey('Path') -and -not $cmdParam.ContainsKey('SourcePath'))
            {
                $cmdParam['SourcePath'] = '$SourcePath/Modules/$nestedModuleName/$nestedModuleName.psd1'
                Write-Build -Color 'DarkGray' -Text " Default param SourcePath is '$($cmdParam['SourcePath'])'"
            }

            if (-not $cmdParam.ContainsKey('OutputDirectory') -and -not $cmdParam.ContainsKey('Destination'))
            {
                $cmdParam['OutputDirectory'] = '$builtModuleBase/Modules/$nestedModuleName'
                Write-Build -Color 'DarkGray' -Text " Default param OutputDirectory is '$($cmdParam['OutputDirectory'])'"
            }

            Write-Build -Color 'Yellow' -Text "Building Nested Module $nestedModuleName"
        }

        $cmdParamKeys = @() + $cmdParam.Keys

        foreach ($paramName in $cmdParamKeys)
        {
            if ($paramName -eq 'SourcePath' -or $paramName -eq 'Path')
            {
                if ($cmd.Verb -eq 'Copy')
                {
                    $cmdParam[$paramName] = Join-Path -Path $ExecutionContext.InvokeCommand.ExpandString($cmdParam[$paramName]) -ChildPath '*'
                    Write-Build -Color 'White' -Text " The $paramName is: '$($cmdParam[$paramName])'"
                }
                else
                {
                    <#
                        To support building the nestedModule without a build manifest the SourcePath must be
                        set to the path to the source nested module manifest.
                    #>

                    $nestedModuleSourceManifest = $ExecutionContext.InvokeCommand.ExpandString($cmdParam[$paramName])
                    $nestedModuleSourceManifest = Get-SamplerAbsolutePath -Path $nestedModuleSourceManifest -RelativeTo $buildRoot

                    # If the BuildInfo has been defined with the SourcePath folder, Append the Module Manifest
                    if (([System.io.FileInfo]$nestedModuleSourceManifest).Extension -ne '.psd1')
                    {
                        $nestedModuleSourceManifest = Join-Path -Path $nestedModuleSourceManifest -ChildPath ('{0}.psd1' -f $nestedModuleName)
                    }

                    $cmdParam[$paramName] = $nestedModuleSourceManifest
                    Write-Build -Color 'White' -Text " The SourcePath is: $($cmdParam[$paramName])"
                }
            }
            elseif ($paramName -notin @($cmd.Parameters.keys + $cmd.Parameters.values.aliases))
            {
                # remove param not available in command
                Write-Build -Color 'White' -Text " Removing Parameter $paramName for $($cmd.Name)"

                $cmdParam.Remove($paramName)
            }
            elseif ($paramName -in @('Destination', 'OutputDirectory', 'SemVer'))
            {
                # Substitute & Resolve Resolve Path to absolutes (relative assumed is $BuildRoot)
                Write-Build -Color 'White' -Text " Resolving Absolute path for $paramName $($cmdParam[$paramName])"

                $cmdParam[$paramName] = $ExecutionContext.InvokeCommand.ExpandString($cmdParam[$paramName])

                if ($paramName -ne 'SemVer')
                {
                    $cmdParam[$paramName] = Get-SamplerAbsolutePath -Path $cmdParam[$paramName] -RelativeTo $BuildRoot
                    if (-not (Test-Path -Path $cmdParam[$paramName]))
                    {
                        $null = New-Item -Path $cmdParam[$paramName] -ItemType Directory -Force -ErrorAction Stop
                    }
                }

                Write-Build -Color 'White' -Text " The $paramName is: $($cmdParam[$paramName])"
            }
        }

        Write-Build -Color 'Green' -Text "$($cmd.Verb) $nestedModuleName..."

        if ($cmdParam.Verbose)
        {
            Write-Verbose -Message ($CmdParam | ConvertTo-Json) -Verbose
        }

        & $cmd @cmdParam

        if ($addToManifest)
        {
            Write-Build -Color 'DarkMagenta' -Text " Preparing to Add to Manifest"

            if ($cmd.Name -eq 'Copy-Item')
            {
                $nestedModulePath = $cmdParam['Destination']
            }
            else
            {
                if ($cmdParam.ContainsKey('OutputDirectory'))
                {
                    $nestedModulePath = $cmdParam['OutputDirectory']
                }
                else
                {
                    $nestedModulePath = $cmdParam['Destination']
                }
            }

            Write-Build -Color 'DarkMagenta' -Text " Looking in '$nestedModulePath'"

            $nestedModuleFile = (Get-ChildItem -Path $nestedModulePath -Recurse -Include '*.psd1' |
                    Where-Object -FilterScript {
                        (
                            $_.Directory.Name -eq $_.BaseName -or $_.Directory.Name -as [version]) `
                            -and $(Test-ModuleManifest -Path $_.FullName -ErrorAction 'SilentlyContinue' ).Version
                    }
            ).FullName -replace [Regex]::Escape($builtModuleBase), ".$([System.IO.Path]::DirectorySeparatorChar)"


            if (-not $nestedModuleFile)
            {
                $nestedModuleFile = Get-ChildItem -Path $nestedModulePath -Recurse -Include '*.psm1' |
                    ForEach-Object -Process {
                        $_.FullName -replace [Regex]::Escape($builtModuleBase), ".$([System.IO.Path]::DirectorySeparatorChar)"
                    }
            }

            Write-Build -Color 'DarkMagenta' -Text "Found $($nestedModuleFile -join ';')"

            $nestedModulesToAdd += $nestedModuleFile
        }

        Write-Build -Color 'Green' -Text "Done `r`n"
    }

    $ModuleInfo = Get-SamplerModuleInfo -ModuleManifestPath $builtModuleManifest

    # Add to NestedModules to ModuleManifest
    if ($ModuleInfo.ContainsKey('NestedModules') -and $nestedModulesToAdd)
    {
        Write-Build -Color 'Green' -Text "Updating the Module Manifest's NestedModules key..."

        $nestedModulesToAdd = $ModuleInfo.NestedModules + $nestedModulesToAdd

        # Get Nested Module Manifest or PSM1
        $updateMetadataParams = @{
            Path         = (Get-Item -Path $BuiltModuleManifest).FullName
            PropertyName = 'NestedModules'
            Value        = $nestedModulesToAdd
            ErrorAction  = 'Stop'
        }

        Write-Build -Color 'Green' -Text " Adding $($NestedModuleToAdd -join ', ') to Module Manifest $($updateMetadataParams.Path)"

        Update-Metadata @updateMetadataParams
    }
}

Task Build_DscResourcesToExport_ModuleBuilder {
    if ([System.String]::IsNullOrEmpty($ProjectName))
    {
        $ProjectName = Get-SamplerProjectName -BuildRoot $BuildRoot
    }

    if ([System.String]::IsNullOrEmpty($SourcePath))
    {
        $SourcePath = Get-SamplerSourcePath -BuildRoot $BuildRoot
    }

    $OutputDirectory = Get-SamplerAbsolutePath -Path $OutputDirectory -RelativeTo $BuildRoot

    "`tProject Name = '$ProjectName'"
    "`tSource Path = '$SourcePath'"
    "`tOutput Directory = '$OutputDirectory'"

    Import-Module -Name 'ModuleBuilder' -ErrorAction 'Stop'

    if ($VersionedOutputDirectory)
    {
        # VersionedOutputDirectory is not [bool]'' nor $false nor [bool]$null
        # Assume true, wherever it was set
        $VersionedOutputDirectory = $true
    }
    else
    {
        # VersionedOutputDirectory may be [bool]'' but we can't tell where it's
        # coming from, so assume the build info (Build.yaml) is right
        $VersionedOutputDirectory = $BuildInfo['VersionedOutputDirectory']
    }

    $GetBuiltModuleManifestParams = @{
        OutputDirectory          = $OutputDirectory
        BuiltModuleSubDirectory  = $BuiltModuleSubDirectory
        ModuleName               = $ProjectName
        VersionedOutputDirectory = $VersionedOutputDirectory
        ErrorAction              = 'Stop'
    }

    $builtModuleBase = Get-SamplerBuiltModuleBase @GetBuiltModuleManifestParams
    $builtModuleBase = (Get-Item -Path $builtModuleBase).FullName
    "`tBuilt Module Base = '$builtModuleBase'"

    $builtModuleManifest = Get-SamplerBuiltModuleManifest @GetBuiltModuleManifestParams
    $builtModuleManifest = (Get-Item -Path $builtModuleManifest).FullName
    "`tBuilt Module Manifest = '$builtModuleManifest'"

    $builtModuleRootScriptPath = Get-SamplerModuleRootPath -ModuleManifestPath $builtModuleManifest
    $builtModuleRootScriptPath = (Get-Item -Path $builtModuleRootScriptPath).FullName
    "`tBuilt ModuleRoot script = '$builtModuleRootScriptPath'"

    $builtDscResourcesFolder = Get-SamplerAbsolutePath -Path 'DSCResources' -RelativeTo $builtModuleBase
    "`tBuilt DSC Resource Path = '$builtDscResourcesFolder'"

    $ModuleVersion = Get-BuiltModuleVersion @GetBuiltModuleManifestParams
    $ModuleVersionObject = Split-ModuleVersion -ModuleVersion $ModuleVersion
    $ModuleVersionFolder = $ModuleVersionObject.Version
    $preReleaseTag       = $ModuleVersionObject.PreReleaseString

    "`tModule Version = '$ModuleVersion'"
    "`tModule Version Folder = '$ModuleVersionFolder'"
    "`tPre-release Tag = '$preReleaseTag'"

    $DSCResourcesToAdd = @()

    #Check if there are classes based resource in psm1
    if ($builtModuleRootScriptFile = Get-Item -Path $builtModuleRootScriptPath -ErrorAction SilentlyContinue)
    {
        "`tBuilt Module Root Script = '$($builtModuleRootScriptFile.FullName)'"

        Write-Build -Color 'Yellow' -Text "Looking in $builtModuleRootScriptPath"

        $builtClassDscResourcesNames = Get-ClassBasedResourceName -Path $builtModuleRootScriptFile.FullName

        if ($builtClassDscResourcesNames)
        {
            Write-Build -Color 'White' -Text " Adding $($builtClassDscResourcesNames -join ',') to the list of DscResource will be write in module manifest."

            $DSCResourcesToAdd = $DSCResourcesToAdd + $builtClassDscResourcesNames
        }
    }

    #Check if DSCResource Folder has DSCResources
    Write-Build -Color 'Yellow' -Text "Looking in $builtDscResourcesFolder"

    if ($builtMofDscFolder = (Get-ChildItem -Path $builtDscResourcesFolder -Directory -ErrorAction SilentlyContinue))
    {
        if ($mofPath = $builtMofDscFolder | Get-ChildItem -Include '*.schema.mof' -File)
        {
            try
            {
                $builtMofDscResourcesNames = $mofPath.FullName | Get-MofSchemaName | ForEach-Object -Process {
                    if ([System.String]::IsNullOrEmpty($_['FriendlyName']))
                    {
                        $_.Name
                    }
                    else
                    {
                        $_.friendlyName
                    }
                }
            }
            catch
            {
                Write-Warning -Message ('Impossible to extract the name of the Mof based DSCResource, see the error : {0}' -f $_)
            }
        }
        else
        {
            Write-Warning -Message ('No mof file found in DscResource folder')
        }

        if ($builtMofDscResourcesNames)
        {
            Write-Build -Color 'White' -Text " Adding $($builtMofDscResourcesNames -join ',') to the list of DscResource will be write in module manifest."

            $DSCResourcesToAdd = $DSCResourcesToAdd + $builtMofDscResourcesNames
        }
    }

    $ModuleInfo = Get-SamplerModuleInfo -ModuleManifestPath $builtModuleManifest

    # Add to DscResourcesToExport to ModuleManifest
    if ($ModuleInfo.ContainsKey('DscResourcesToExport') -and $DSCResourcesToAdd)
    {
        Write-Build -Color 'Green' -Text "Updating the Module Manifest's DscResourcesToExport key..."

        $DSCResourcesToAdd = $ModuleInfo.DscResourcesToExport + $DSCResourcesToAdd | Select-Object -Unique

        $updateMetadataParams = @{
            Path         = (Get-Item -Path $BuiltModuleManifest).FullName
            PropertyName = 'DscResourcesToExport'
            Value        = [array]$DSCResourcesToAdd
            ErrorAction  = 'Stop'
        }

        Write-Build -Color 'Green' -Text " Adding $($DSCResourcesToAdd -join ', ') to Module Manifest $($updateMetadataParams.Path)"

        Update-Metadata @updateMetadataParams
    }
}

Task Build_Module_ModuleBuilder Build_ModuleOutput_ModuleBuilder, Build_DscResourcesToExport_ModuleBuilder