Shared/DynamicParams.ps1

# . { # example of how to access the common parameter $server
# param(
# [string] $server
# )

# Execute-Workflow {
# Write-Host "Aggregation File: $aggregationFile"
# Write-Host "Server: $server"
# Aggregate -AggregationFile $aggregationFile
# }

# } @PSBoundParameters

function AddParam {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        $type,
        [Parameter(Mandatory = $true, Position = 1)]
        [string] $name
        # [System.Attribute[]] $attributes
    )

    throw "Should only be called from DefineDynamicParams"
}

function New-DynamicParamsDict {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('param')]
        [System.Management.Automation.RuntimeDefinedParameter[]] $params
    )

    begin {
        $paramsDict = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
    }

    process {
        $params | Add-DynamicParam -paramsDict $paramsDict
    }

    end {
        return $paramsDict
    }
}

function Invoke-WithBoundParams {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string] $commandName,
        [Parameter(Mandatory = $true)]
        $BoundParameters
    )

    dynamicparam {
        $command = Get-Command -Name $commandName

        if (!$command) {
            throw "Command not found: $commandName"
        }

        $command | Import-DynamicParamsFromCommand | New-DynamicParamsDict
    }

    process {
        $command = Get-Command -Name $commandName

        # execute command with only the bound parameters corresponding to the dynamic parameters (filtered)
        $commandParams = $command.Parameters.Values
        $filteredBoundParams = @{}

        foreach ($param in $commandParams) {
            if ($BoundParameters.ContainsKey($param.Name)) {
                $filteredBoundParams[$param.Name] = $BoundParameters[$param.Name]
            }

            if ($PsBoundParameters.ContainsKey($param.Name)) {
                $filteredBoundParams[$param.Name] = $PsBoundParameters[$param.Name]
            }
        }

        Write-Host $filteredBoundParams

        & $commandName @filteredBoundParams
    }
}


function Import-DynamicParameters {
    return {
        foreach ($currentScriptParamName in $PSCmdlet.MyInvocation.MyCommand.Parameters.Keys) {
            $currentScriptParam = $PSCmdlet.MyInvocation.MyCommand.Parameters[$currentScriptParamName]
            if (!$currentScriptParam.IsDynamic) {
                continue
            }

            if ($PSBoundParameters.ContainsKey($currentScriptParamName)) {
                $currentScriptParamValue = $PSBoundParameters[$currentScriptParamName]
                Set-Variable -Name $currentScriptParamName -Value $currentScriptParamValue
            }

            # $currentScriptParam.Attributes | ConvertTo-Json -Depth 2 | Out-String

            # otherwise set default
            if ($currentScriptParam.DefaultValue) {
                Write-Host "Default: "
                $defaultValue = $currentScriptParam.Attributes.DefaultValue

                if ($null -eq $defaultValue) {
                    continue
                }

                $defaultValue | Out-String
                Set-Variable -Name $currentScriptParamName -Value $defaultValue
            }
        }
    }.ToString()
}


function Select-ParametersForCommand {
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string] $command,

        [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true)]
        [hashtable] $BoundParameters
    )

    process {
        $commandInfo = Get-Command -Name $command

        if (!$command) {
            throw "Command not found: $command"

        }

        $commandParams = $commandInfo.Parameters.Values
        $filteredBoundParams = @{}



        foreach ($param in $commandParams) {
            if ($BoundParameters.ContainsKey($param.Name)) {
                $filteredBoundParams[$param.Name] = $BoundParameters[$param.Name]
            }

            if ($PsBoundParameters.ContainsKey($param.Name)) {
                $filteredBoundParams[$param.Name] = $PsBoundParameters[$param.Name]
            }
        }

        return $filteredBoundParams
    }
}

function WithDefaults-For {
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string] $Command,

        [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true)]
        [hashtable] $BoundParameters
    )

    dynamicparam {
        $commandInfo = Get-Command -Name $command

        if (!$commandInfo) {
            throw "Command not found: $command"
        }
        $dict = $commandInfo | Import-DynamicParamsFromCommand -MakeNonMandatory | New-DynamicParamsDict

        return $dict
    }

    begin {
        $defaultCommandParams = $PSBoundParameters | Where-Object { $_.Key -ne "Command" -and $_.Key -ne "BoundParameters" }
        $boundCommandParams = $BoundParameters | Select-ParametersForCommand $Command

        Write-Host "Bound"
        ConvertTo-Json $boundCommandParams

        $params = $defaultCommandParams | Override-HashtableWith $boundCommandParams

        return $params
    }
}

function Execute-Workflow {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [scriptblock] $block,
        [string] $server = "dev",
        [string] $epicId,
        [Parameter(Mandatory = $true)]
        [string] $asOfDate,

        [string] $scenario,
        [string] $operationType = "UNSET_OPERATION_TYPE", # e.g. "PRICE:RESULT", "PNL:RESULT", "ETL:BOOTSTRAP" etc.
        [string] $operationDisplayName, # e.g. "Price", "PNL", "ETL" etc.

        [string] $triggeredBy = "System",
        [switch] $dontMonitor,
        [switch] $dryRun,
        [switch] $noPause
    )

    $isVerbose = $PSBoundParameters['Verbose'] -eq $true

    Import-Module ([IO.Path]::Combine($PSScriptRoot, "../../../scripts/flo/run", "utils.psm1")) -Force -Global -DisableNameChecking -Verbose:$false
    Import-Module ([IO.Path]::Combine($PSScriptRoot, "../../../scripts/flo/run", "base-templates.psm1")) -Force -Global -DisableNameChecking -Verbose:$false
    Import-Module ([IO.Path]::Combine($PSScriptRoot, "../../../scripts/flo/run", "xpl-commons.psm1")) -Force -Global -DisableNameChecking -Verbose:$false
    # Import-Module ([IO.Path]::Combine($PSScriptRoot, "../../../tags.psm1")) -Force -Global -DisableNameChecking -Verbose:$false


    # print all the parameters
    Write-Host "-------------------------------------"
    Write-Host "Executing workflow with parameters:"
    Write-Host "-------------------------------------"
    foreach ($key in ($PSBoundParameters.Keys | Where-Object { $_ -ne "block" })) {
        Write-Host "$($key): $($PSBoundParameters[$key])"
    }
    Write-Host "-------------------------------------"

    Start-Workflow

    # execute the definition block of the workflow
    # IMPORTANT: the variables from the calling function scope will not be available in the block automatically
    # you need to pass them explicitly in the script block:
    # Execute-Workflow { param($server, $epicId) Write-Host "My server is: $server" }

    # below we create a new script block to capture only epicId and server variables

    # Opposed to & $block, Invoke-Command -ScriptBlock $block will capture all the variables from the calling function scope
    Invoke-Command -NoNewScope -ScriptBlock $block

    # & {
    # $closure = $block.GetNewClosure()
    # & $closure
    # } @PSBoundParameters

    Write-Host "-------------------------------------"

    $wfFilePath = [System.IO.Path]::GetTempFileName() + ".json"

    $wfMetadata = @{
        DisplayName   = $operationDisplayName ?? $operationType
        Scenario      = $scenario ?? "$operationType @ $asOfDate"
        AsOfDate      = $asOfDate
        OperationType = $operationType
        TriggeredBy   = $triggeredBy
        EpicId        = $epicId
    }

    Save-Workflow $wfFilePath -Metadata $wfMetadata -Context $wfContext
    Run-Workflow $wfFilePath -DryRun:$dryRun -Server $server -Verbose:$isVerbose -DontMonitor:$dontMonitor
    Write-Host "===> EpicId: $epicId" -ForegroundColor Green

    if (-not $noPause) {
        Pause
    }
}


function Params-For {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string] $command
    )

    dynamicparam {
        (Get-Command $command) | Import-DynamicParamsFromCommand | New-DynamicParamsDict
    }

    process {
        return $PSBoundParameters
    }
}

function PartialParams-For {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string] $command
    )

    dynamicparam {
        (Get-Command $command) | Import-DynamicParamsFromCommand -MakeNonMandatory | New-DynamicParamsDict
    }

    process {
        return $PSBoundParameters | Select-ParametersForCommand $command
    }
}

function Override-HashtableWith {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [hashtable] $override,

        [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true)]
        [hashtable] $base
    )

    begin {
        $result = @{}
    }

    process {
        foreach ($key in $base.Keys) {
            $result[$key] = $base[$key]
        }

        foreach ($key in $override.Keys) {
            $result[$key] = $override[$key]
        }
    }

    end {
        return $result
    }
}

function Add-DynamicParam {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [System.Management.Automation.RuntimeDefinedParameterDictionary] $paramsDict,

        [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('param')]
        [System.Management.Automation.RuntimeDefinedParameter[]] $params
    )

    begin {
        # intentionally empty
    }

    process {
        foreach ($param in $params) {
            $paramsDict.Add($param.Name, $param)
        }
    }

    end {
    }
}

function New-DynamicParam {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        $type,
        [Parameter(Mandatory = $true, Position = 1)]
        [string] $name,
        [Parameter(Position = 2)]
        $attributes,
        [switch] $Mandatory
    )

    if ($type.GetType() -eq [string]) {
        if ($type.StartsWith("[") -and $type.EndsWith("]")) {
            $type = $type.Substring(1, $type.Length - 2)
        }

        $actualType = Invoke-Expression "[$type]"
    } else {
        $actualType = $type
    }

    $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]

    $hasParamAttr = $false
    if ($attributes) {
        foreach ($attr in $attributes) {
            if ($attr -is [System.Management.Automation.ParameterAttribute]) {
                $hasParamAttr = $true

                $paramAttr = [System.Management.Automation.ParameterAttribute]$attr

                $paramAttr.Mandatory = $Mandatory
                # $paramAttr.ValueFromPipeline = $false
                # $paramAttr.ValueFromPipelineByPropertyName = $false
            }

            if ($attr -is [System.Attribute]) {
                $attributeCollection.Add($attr)
            } else {
                throw "Invalid attribute: $attr"
            }

        }
    }

    if (!$hasParamAttr) {
        $paramAttr = New-Object System.Management.Automation.ParameterAttribute
        $paramAttr.Mandatory = $Mandatory
        $attributeCollection.Add($paramAttr)
    }


    # $ageAttribute.Position = 3
    # $ageAttribute.Mandatory = $true
    # $ageAttribute.HelpMessage = "This product is only available for customers 21 years of age and older. Please enter your age:"

    #create an attributecollection object for the attribute we just created.

    $runtimeDefinedParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($name, $actualType, $attributeCollection)
    return $runtimeDefinedParameter

}

function Import-DynamicParams {
    [CmdletBinding(DefaultParameterSetName = "Block")]
    param (
        [Parameter(ParameterSetName = "Block", Mandatory = $true)]
        [scriptblock] $block,

        [Parameter(ParameterSetName = "Command", Mandatory = $true)]
        [System.Management.Automation.CommandInfo] $command
    )

    begin {
        $params = @()
    }

    process {
        switch ($PSCmdlet.ParameterSetName) {
            "Block" { $params = $params + @(Import-DynamicParamsFromBlock -block $block) }
            "Command" { $params = $params + @(Import-DynamicParamsFromCommand -command $command) }
            Default { throw "Invalid parameter set name: $($PSCmdlet.ParameterSetName)" }
        }
    }

    end {
        return $params
    }

}

function Import-DynamicParamsFromBlock {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [scriptblock] $block
    )

    begin {
        $params = @()
    }

    process {
        $functionName = "FUNC_" + [System.Guid]::NewGuid().ToString().Replace("-", "")

        Invoke-Expression "function $functionName { $block }"

        $command = Get-Command $functionName

        $params = $params + @(Import-DynamicParamsFromCommand $command)
    }

    end {
        return $params
    }
}

$POWERSHELL_COMMON_PARAMS = @(
    "Verbose",
    "Debug",
    "ErrorAction",
    "WarningAction",
    "InformationAction",
    "ErrorVariable",
    "WarningVariable",
    "InformationVariable",
    "OutVariable",
    "OutBuffer",
    "PipelineVariable",
    "ProgressAction"
)

function Test-CommonPowerShellParameter {
    param (
        [Parameter(Mandatory = $true)]
        [System.Management.Automation.ParameterMetadata] $param
    )



    return $POWERSHELL_COMMON_PARAMS -contains $param.Name
}

function Import-DynamicParamsFromCommand {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [System.Management.Automation.CommandInfo] $command,
        [switch] $makeNonMandatory
    )

    begin {
        $params = @()
    }

    process {
        $commandParams = $command.Parameters

        foreach ($param in $commandParams.Values) {
            $paramName = $param.Name
            $paramType = $param.ParameterType

            if (Test-CommonPowerShellParameter $param) {
                continue
            }

            $mandatory = $param.Attributes `
            | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } `
            | ForEach-Object { $_.Mandatory }

            if ($makeNonMandatory) {
                $mandatory = $false
            }

            $params += New-DynamicParam $paramType $paramName $param.Attributes -Mandatory:$mandatory
        }
    }

    end {
        return $params
    }
}

function Import-DynamicParams {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        $BoundParameters
    )

    $scriptBlockText = foreach ($key in $BoundParameters.Keys) {
        "Write-Host `$PSBoundParameters['$key']"
        "Set-Variable -Name `"$key`" -Value (`$PSBoundParameters['$key'])"
    }

    return [scriptblock]::Create($scriptBlockText -join "`n")
}