Functions/Invoke-WhiskeyTask.ps1


function Invoke-WhiskeyTask
{
    <#
    .SYNOPSIS
    Runs a Whiskey task.
     
    .DESCRIPTION
    The `Invoke-WhiskeyTask` function runs a Whiskey task.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [object]
        # The context this task is operating in. Use `New-WhiskeyContext` to create context objects.
        $TaskContext,

        [Parameter(Mandatory=$true)]
        [string]
        # The name of the task.
        $Name,
        
        [Parameter(Mandatory=$true)]
        [hashtable]
        # The parameters/configuration to use to run the task.
        $Parameter
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    function Invoke-Event
    {
        param(
            $Prefix,
            $EventName,
            $Property
        )

        if( -not $events.ContainsKey($EventName) )
        {
            return
        }

        foreach( $commandName in $events[$EventName] )
        {
            Write-Verbose -Message $prefix
            Write-Verbose -Message ('{0} [On{1}] {2}' -f $prefix,$EventName,$commandName)
            $startedAt = Get-Date
            $result = 'FAILED'
            try
            {
                & $commandName -TaskContext $TaskContext -TaskName $Name -TaskParameter $Property
                $result = 'COMPLETED'
            }
            finally
            {
                $endedAt = Get-Date
                $duration = $endedAt - $startedAt
                Write-Verbose ('{0} {1} {2} in {3}' -f $prefix,(' ' * ($EventName.Length + 4)),$result,$duration)
            }
        }
    }

    $knownTasks = Get-WhiskeyTask

    $task = $knownTasks | Where-Object { $_.Name -eq $Name }

    $errorPrefix = '{0}: {1}[{2}]: {3}: ' -f $TaskContext.ConfigurationPath,$TaskContext.PipelineName,$TaskContext.TaskIndex,$Name

    if( -not $task )
    {
        $knownTaskNames = $knownTasks | Select-Object -ExpandProperty 'Name' | Sort-Object
        throw ('{0}: {1}[{2}]: ''{3}'' task does not exist. Supported tasks are:{4} * {5}' -f $TaskContext.ConfigurationPath,$Name,$TaskContext.TaskIndex,$Name,[Environment]::NewLine,($knownTaskNames -join ('{0} * ' -f [Environment]::NewLine)))
    }

    function Merge-Parameter
    {
        param(
            [hashtable]
            $SourceParameter,

            [hashtable]
            $TargetParameter
        )

        foreach( $key in $SourceParameter.Keys )
        {
            $sourceValue = $SourceParameter[$key]
            if( $TargetParameter.ContainsKey($key) )
            {
                $targetValue = $TargetParameter[$key]
                if( $targetValue -is [hashtable] -and $sourceValue -is [hashtable] )
                {
                    Merge-Parameter -SourceParameter $sourceValue -TargetParameter $targetValue
                }
                continue
            }

            $TargetParameter[$key] = $sourceValue
        }
    }

    $TaskContext.TaskName = $Name

    #I feel like this is missing a piece, because the current way that Whiskey tasks are named, they will never be run by this logic.
    $prefix = '[{0}]' -f $Name

    $onlyDuring = $Parameter['OnlyDuring']
    $exceptDuring = $Parameter['ExceptDuring']

    if ($onlyDuring -and $exceptDuring)
    {
        Stop-WhiskeyTask -TaskContext $TaskContext -Message 'Both ''OnlyDuring'' and ''ExceptDuring'' properties are used. These properties are mutually exclusive, i.e. you may only specify one or the other.'
    }
    elseif ($onlyDuring -and ($onlyDuring -notin @('Clean', 'Initialize')))
    {
        Stop-WhiskeyTask -TaskContext $TaskContext -Message ('Property ''OnlyDuring'' has an invalid value: ''{0}''. Valid values are the run modes ''Clean'' or ''Initialize''.' -f $onlyDuring)
    }
    elseif ($exceptDuring -and ($exceptDuring -notin @('Clean', 'Initialize')))
    {
        Stop-WhiskeyTask -TaskContext $TaskContext -Message ('Property ''ExceptDuring'' has an invalid value: ''{0}''. Valid values are the run modes ''Clean'' or ''Initialize''.' -f $exceptDuring)
    }

    if ($onlyDuring -and ($TaskContext.RunMode -ne $onlyDuring))
    {
        Write-Verbose -Message ('{0} SKIPPED OnlyDuring: {1} -- Current RunMode: {2}' -f $prefix,$onlyDuring,$TaskContext.RunMode)
        return
    }
    elseif ($exceptDuring -and ($TaskContext.RunMode -eq $exceptDuring))
    {
        Write-Verbose -Message ('{0} SKIPPED ExceptDuring: {1} -- Current RunMode: {2}' -f $prefix,$exceptDuring,$TaskContext.RunMode)
        return
    }
    
    if( $TaskContext.ShouldClean() -and -not $task.SupportsClean )
    {
        Write-Verbose -Message ('{0} SKIPPED SupportsClean: $false' -f $prefix)
        return
    }
    if( $TaskContext.ShouldInitialize() -and -not $task.SupportsInitialize )
    {
        Write-Verbose -Message ('{0} SKIPPED SupportsInitialize: $false' -f $prefix)
        return
    }

    $onlyBy = $Parameter['OnlyBy']
    if( $onlyBy )
    {
        switch( $onlyBy )
        {
            'Developer'
            {
                if( -not $TaskContext.ByDeveloper )
                {
                    Write-Verbose -Message ('{0} SKIPPED OnlyBy: {1}; ByBuildServer: {2}' -f $prefix,$onlyBy,$TaskContext.ByBuildServer)
                    return
                }
            }
            'BuildServer'
            {
                if( -not $TaskContext.ByBuildServer )
                {
                    Write-Verbose -Message ('{0} SKIPPED OnlyBy: {1}; ByDeveloper: {2}' -f $prefix,$onlyBy,$TaskContext.ByDeveloper)
                    return
                }
            }
            default
            {
                Stop-WhiskeyTask -TaskContext $TaskContext -Message ('Property ''OnlyBy'' has an invalid value: ''{0}''. Valid values are ''Developer'' (to only run the task when the build is being run by a developer) or ''BuildServer'' (to only run the task when the build is being run by a build server).' -f $onlyBy)
            }
        }
    }
    
    $branch = $TaskContext.BuildMetadata.ScmBranch
    $executeTaskOnBranch = $true
    
    if( $Parameter['OnlyOnBranch'] -and $Parameter['ExceptOnBranch'] )
    {
        Stop-WhiskeyTask -TaskContext $TaskContext -Message ('This task defines both OnlyOnBranch and ExceptOnBranch properties. Only one of these can be used. Please remove one or both of these properties and re-run your build.')
    }
    
    if( $Parameter['OnlyOnBranch'] )
    {
        $executeTaskOnBranch =$false
        Write-Verbose -Message ('OnlyOnBranch')
        foreach( $wildcard in $Parameter['OnlyOnBranch'] )
        {
            if( $branch -like $wildcard )
            {
                Write-Verbose -Message (' {0} -like {1}' -f $branch, $wildcard)
                $executeTaskOnBranch = $true
                break
            }
            else
            {
                Write-Verbose -Message (' {0} -notlike {1}' -f $branch, $wildcard)
            }
        }
    }

    if( $Parameter['ExceptOnBranch'] )
    {
        Write-Verbose -Message ('ExceptOnBranch')
        foreach( $wildcard in $Parameter['ExceptOnBranch'] )
        {
            if( $branch -like $wildcard )
            {
                Write-Verbose -Message (' {0} -like {1}' -f $branch, $wildcard)
                $executeTaskOnBranch = $false
                break
            }
            else
            {
                Write-Verbose -Message (' {0} -notlike {1}' -f $branch, $wildcard)
            }
        }
    }
    
    if( !$executeTaskOnBranch )
    {
        Write-Verbose -Message ('{0} SKIPPED {1} not configured to execute this task.' -f $prefix, $branch)
        return
    }

    $taskProperties = $Parameter.Clone()
    foreach( $commonPropertyName in @( 'OnlyBy', 'ExceptBy', 'OnlyOnBranch', 'ExceptOnBranch', 'OnlyDuring', 'ExceptDuring' ) )
    {
        $taskProperties.Remove($commonPropertyName)
    }
    
    if( $TaskContext.TaskDefaults.ContainsKey( $Name ) )
    {
        Merge-Parameter -SourceParameter $TaskContext.TaskDefaults[$Name] -TargetParameter $taskProperties
    }

    Resolve-WhiskeyVariable -Context $TaskContext -InputObject $taskProperties | Out-Null

    Invoke-Event -EventName 'BeforeTask' -Prefix $prefix -Property $taskProperties
    Invoke-Event -EventName ('Before{0}Task' -f $Name) -Prefix $prefix -Property $taskProperties

    Write-Verbose -Message $prefix
    $startedAt = Get-Date
    $result = 'FAILED'
    try
    {
        & $task.CommandName -TaskContext $TaskContext -TaskParameter $taskProperties
        $result = 'COMPLETED'
    }
    finally
    {
        $endedAt = Get-Date
        $duration = $endedAt - $startedAt
        Write-Verbose ('{0} {1} in {2}' -f $prefix,$result,$duration)
    }

    Invoke-Event -Prefix $prefix -EventName 'AfterTask' -Property $taskProperties
    Invoke-Event -Prefix $prefix -EventName ('After{0}Task' -f $Name) -Property $taskProperties
    Write-Verbose ($prefix)
    Write-Verbose ''
}