Public/Invoke-buildWorkflow.ps1

function Invoke-buildWorkflow {
    <#
    .SYNOPSIS
        Start a TDS build workflow for the given path.
 
    .DESCRIPTION
        Start a TDS build workflow for a given path.
 
    .PARAMETER buildPath
        The path to the build workflow.
 
    .PARAMETER dmlCSV
        The path to the Definitive Media Library index file.
 
    .PARAMETER credentialStore
        Path to the credential store.
 
    .PARAMETER credentialKeyFile
        The AES key used to decrypt credential objects in the credential store.
 
    .PARAMETER skipValidation
        Skip metadata and media validation (not recommended)
 
    .INPUTS
        None.
 
    .OUTPUTS
        None.
 
    .EXAMPLE
        Invoke-buildWorkflow -buildPath C:\StackBuild\Builds\baseVsphere67\ -dmlCSV C:\DML\dmlContents.csv -credentialStore C:\StackBuild\Credentials\ -credentialKeyFile C:\StackBuild\aesKey.key
 
        Start the build baseVsphere67 using the specified dmlCsv, credential store and aes key.
 
    .LINK
 
    .NOTES
        01 Alistair McNair Initial version.
 
    #>



    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [String]$buildPath,
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String]$dmlCSV,
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String]$credentialStore,
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String]$credentialKeyFile,
        [Parameter(Mandatory=$false,ValueFromPipeline=$false)]
        [switch]$skipMediaValidation
    )

    begin {
        Write-Verbose ("Function start.")
    } # begin

    process {

        Write-Verbose ("Processing workflow at " + $buildPath)

        ## Strip trailing \ from build path, if specified
        $buildPath = $buildPath.Trim("\")

        ## Strip trailing \ from credential store path, if specified
        $credentialStore = $credentialStore.Trim("\")

        ## Derive build name from path
        $buildName = Split-Path -Path $buildPath -Leaf

        ## Validate specified build folder exists
        if (!(Test-Path $buildPath)) {
            Write-Verbose ("Build path not found.")
            throw ("Build path " + $buildPath + " was not found.")
        } # if

        Write-Verbose ("Build path found.")

        ## Initiate logs
        $masterLogPath = ($buildPath + "\" + (Get-Date -Format "yyyy-MM-dd-HH-mm-ss") + "-log")

        try {
            New-Item -ItemType Directory -Path $masterLogPath -Force | Out-Null
            Write-Verbose ("Log directory created at " + $masterLogPath)
        } # try
        catch {
            Write-Debug ("Failed to created log directory.")
            throw ("Failed to create logs path. " + $_.exception.message)
        } # catch


        ## Generate build values table
        Write-Verbose ("Generating build values table.")
        try {
            $buildValues = Get-buildValues -buildPath $buildPath -dmlCSV $dmlCSV -credentialStore $credentialStore -credentialKeyFile $credentialKeyFile -skipMediaValidation:$skipMediaValidation -ErrorAction Stop
            Write-Verbose ("Build values generated.")
        } # try
        catch {
            Write-Debug ("Failed to generate build values.")
            throw ("Failed to generate build values.")
        } # catch


        ## Add log directory to values table to make it available to other functions.
        $buildValues += [pscustomobject]@{"Key" = "logDirectory"; "Value" = $masterLogPath; "DataType" = "String"; "Description" = "Environment master log directory"}

        ## Allocate network addressing
        Write-Verbose ("Allocating dynamic network values.")

        ## Copy networks.json to log directory
        Copy-Item -Path ($buildPath + "\networks.json") -Destination ($masterLogPath + "\networks.json")

        foreach ($buildValue in $buildValues | Where-Object {$_.dataType -like "NETALLOCATION#*"}) {

            ## The network we want to allocate to
            $netName = $buildValue.DataType.split("#")[1]

            ## The network action we want to do, e.g. new IP, get gateway
            $action = $buildValue.DataType.Split("#")[2]

            try {
                $buildValue.Value = New-netMetadata -definitionFile ($masterLogPath + "\networks.json") -action $action -netName $netName -ErrorAction Stop
            } # try
            catch {
                Write-Debug ("Network allocation failed.")
                Throw ("Failed to allocate dynamic network value. " + $_.exception.message)
            } # catch


            $buildValue.dataType = "ipv4"

        } # foreach


        ## Save a copy of this values table to the logs directory for reference
        Write-Verbose ("Exporting values table to " + $masterLogPath + "\translatedValues.csv")
        try {
            $buildValues | Export-Csv -Path ($masterLogPath + "\translatedValues.csv") -NoClobber -NoTypeInformation -ErrorAction Stop
            Write-Verbose ("Saved traslated values table to log directory.")
        } # try
        catch {
            Write-Debug ("Failed to save CSV.")
            throw ("Failed to save translated values CSV to "+ $masterLogPath + "\translatedValues.csv " + $_.exception.message)
        } # catch


        ## Get the available stage JSON files from the metadata folder
        try {
            $jsonFiles = Get-ChildItem -Path ($buildPath + "\metadata") -Filter *.json -ErrorAction Stop | Sort-Object
            Write-Verbose ("Got list of json metadata files.")
        } # try
        catch {
            Write-Debug ("Failed to retreive content from the metadata folder.")
            throw("Failed to retreive content from the metadata folder.")
        } # catch


        ## Check that there is at least 1 stage file
        if ($jsonFiles.Count -eq 0) {
            throw("No stage files in the specified directory.")
        } # if


        ## Inject values into json templates
        try {
            $jsonMaster = $jsonFiles | Set-placeholderValue -buildValues $buildValues -ErrorAction Stop
            Write-Verbose ("Completed injecting values to json templates.")
        } # try
        catch {
            Write-Debug ("Failed to inject values.")
            throw ("Failed to inject values to json templates. " + $_.exception.message)
        } # catch


        ## Save copy of build object to log directory for reference
        try {
            $jsonMaster | ConvertTo-Json -Depth 5 | Out-File -FilePath ($masterLogPath + "\buildJSON.json") -ErrorAction Stop
            Write-Verbose ("Saved build JSON to log directory.")
        } # try
        catch {
            Write-Debug ("Failed to save build JSON.")
            throw ("Failed to save build JSON to log directory. " + $_.exception.message)
        } # catch


        ## Initialise main build log file
        $buildLog = ($masterLogPath + "\buildRunTime.log")

        ## Initialise workflow attributes hash table
        $workflowAttribs = @{}

        Write-Verbose ("Starting build.")

        ## Iterate through master JSON and run functions in sequence
        foreach ($stage in $jsonMaster) {

            ## Build collection of hashtables so we can splat parameters
            $paramHashTables = @()

            foreach ($obj in $stage.Objects) {

                ## Initilaise empty hashtable
                $paramHashTable = @{}

                ## Initialise attribute name flag
                $attribName = $null

                ## Define foreach process block
                $feProcess = {

                    ## Process parameters
                    switch ($_) {

                        {$_.name -like "*credential"} {

                            ## This is a credential item. Build this from the credential store.
                            #$paramHashTable[$_.Name] = Import-Credential -inputFile ($credentialStore + "\" + $_.value) -aesKey $credentialKeyFile
                            $paramHashTable[$_.Name] = Import-Credential -inputFile $_.value -aesKey $credentialKeyFile
                            Write-Verbose ("Injected credential object from " + $_.value)
                            break
                        } # Credential

                        {$_.name -eq "workflowAttrib"} {

                            ## Workflow attribute. This is not a parameter, but an instruction to capture the output of this function to the specified attribute name.
                            Write-Verbose ("Workflow attribute detected. Output from this function will be captured to workflow attribute " + $_.value)
                            $attribName = $_.value
                            break
                        } # Workflow attribute

                        default {

                            ## Standard function parameter. Translate if this is a workflow attribute
                            if ($_.value -like "@@*") {

                                Write-Verbose ("Value for paramter " + $_.name + " will be taken from workflow attribute.")
                                $paramHashTable[$_.Name] = $workFlowAttribs.($_.value.trim("@@"))
                            } # if
                            else {
                                $paramHashTable[$_.Name] = $_.value
                            } # else

                        } # default

                    } # switch

                } # scriptblock

                ## Convert this object to a hashtable
                $obj.psobject.properties | ForEach-Object -Process $feProcess

                ## Add this hash table to the collection
                $paramHashTables += $paramHashTable

            } # foreach


            ## Iterate counter
            $i++

            ## Write progress to the screen
            Write-Progress -Activity ("[" + $buildName + "] Executing stage " + $stage.Function + " [" + $i + " of " + ($jsonMaster | Measure-Object).count + "]") -PercentComplete (($i / ($jsonMaster | Measure-Object).count) * 100)

            ## Generate dynamic scriptblock
            $scriptBlock = [scriptblock]::Create("`$paramHashTables | ForEach-Object {$($stage.Function) @_ -Verbose -ErrorAction Stop *>&1} | Write-Log -logPath `$buildLog -functionName $($stage.Function)")

            Write-Verbose ("Executing script block " + $i + " of " + ($jsonMaster | Measure-Object).count + ": " + $scriptBlock)

            try {
                ## Execute script block
                $cmdReturn = Invoke-Command -ScriptBlock $scriptBlock -ErrorAction Stop
            } # try
            catch {
                Write-Debug ("Command failure.")
                $_ | Write-Log -logPath $buildLog -functionName $stage.function
                throw ("A build failure was detected. Examine " + $buildLog + " for more details.")
            } # catch

            ## If we have a workflowAttrib paramter set, save the output of this function to the specified workflow attribute
            if ($attribName) {

                ## Check there was actual output
                if ($cmdReturn) {
                    Write-Verbose ("Saving function output to workflow attribute " + $attribName)

                    ## Check if this attribute already exists, if so update it
                    if ($workflowAttribs.$attribName) {
                        Write-Verbose ("Workflow attribute already exists, it will be updated.")
                        $workflowAttribs.$attribName = $cmdReturn
                    } # if
                    else {
                        $workFlowAttribs.add($attribName,$cmdReturn)
                    } # else

                } # if
                else {
                    Write-Warning ("The workflowAttrib paramter was specified for this function, but it returned no ouput. This may affect other functions with a dependency on this attribute.")
                } # else

            } # if

            ## Wait 2 seconds between functions
            Start-Sleep 2

        } # foreach

        Write-Verbose ("Build complete.")

    } # process

    end {

        Write-Verbose ("All builds complete.")

    } # end

} # function