VaporShell.Attributes.ps1

using namespace System
using namespace System.Collections
using namespace System.Collections.Generic
using namespace System.IO
using namespace System.Management.Automation
[CmdletBinding()]
Param()

Write-Verbose "Importing class 'VSError'"

class VSError : ErrorRecord {
    hidden static [VSError] InsertError([VSError] $vsErrorRecord) {
        if ($null -eq $global:VSError) {
            $global:VSError = [List[VSError]]::new()
        }
        $global:VSError.Insert(0, $vsErrorRecord)
        return $vsErrorRecord
    }

    static [VSError] InvalidType([object] $inputObject, [type[]] $validTypes) {
        $record = [VSError]::new(
            [ArgumentException]::new("$($inputObject.GetType().FullName) is an invalid type for this property. Valid types include: $(($validTypes.FullName | Sort-Object -Unique) -join ', ')"),
            'InvalidType',
            [ErrorCategory]::InvalidArgument,
            $inputObject
        )
        return [VSError]::InsertError($record)
    }

    static [VSError] InvalidMap([object] $inputObject, [object] $key, [object] $value) {
        $record = [VSError]::new(
            [ArgumentException]::new("The values of the Map property must be IDictionarys themselves. Map key '$($key)' has a value of type '$($value.GetType())' assigned to it."),
            'InvalidMap',
            [ErrorCategory]::InvalidArgument,
            $inputObject
        )
        return [VSError]::InsertError($record)
    }

    static [VSError] InvalidLogicalId([string] $inputObject) {
        $record = [VSError]::new(
            [ArgumentException]::new("The LogicalId must be alphanumeric (a-z, A-Z, 0-9) and unique within the template. Value provided: '$inputObject'"),
            'InvalidLogicalId',
            [ErrorCategory]::InvalidArgument,
            $inputObject
        )
        return [VSError]::InsertError($record)
    }

    static [VSError] InvalidArgument([object] $inputObject, [string] $message) {
        $record = [VSError]::new(
            [ArgumentException]::new($message),
            'InvalidArgument',
            [ErrorCategory]::InvalidArgument,
            $inputObject
        )
        return [VSError]::InsertError($record)
    }

    static [VSError] MissingLogicalId([object] $inputObject, [string] $section) {
        $record = [VSError]::new(
            [ArgumentException]::new("This $($inputObject.GetType()) is missing a LogicalId! Unable to add to $section"),
            'MissingLogicalId',
            [ErrorCategory]::InvalidArgument,
            $inputObject
        )
        return [VSError]::InsertError($record)
    }

    static [VSError] DuplicateLogicalId([object] $inputObject, [string] $section) {
        $record = [VSError]::new(
            [ArgumentException]::new("The Template already contains a $section with a LogicalId of '$($inputObject.LogicalId)'. LogicalIds must be unique within the Template."),
            'DuplicateLogicalId',
            [ErrorCategory]::InvalidArgument,
            $inputObject
        )
        return [VSError]::InsertError($record)
    }

    static [VSError] InvalidJsonInput([string] $jsonStringOrFilePath) {
        $record = [VSError]::new(
            [ArgumentException]::new("Unable to convert input data from JSON to PSObject! Please use a JSON string OR provide a valid path to a JSON file!"),
            'InvalidJsonInput',
            [ErrorCategory]::InvalidArgument,
            $jsonStringOrFilePath
        )
        return [VSError]::InsertError($record)
    }

    static [VSError] InvalidYamlInput([string] $yamlStringOrFilePath) {
        $record = [VSError]::new(
            [ArgumentException]::new("Unable to convert input data from YAML to PSObject! Please use a YAML string OR provide a valid path to a YAML file!"),
            'InvalidYamlInput',
            [ErrorCategory]::InvalidArgument,
            $yamlStringOrFilePath
        )
        return [VSError]::InsertError($record)
    }

    VSError() {}

    VSError([ErrorRecord] $errorRecord) {
        foreach ($prop in $errorRecord.PSObject.Properties.Name) {
            if ($null -ne $errorRecord.$prop) {
                $this.$prop = $errorRecord.$prop
            }
        }
    }

    VSError([Exception] $exception, [string] $errorId, [ErrorCategory] $errorCategory, [object] $targetObject) : base($exception, $errorId, $errorCategory, $targetObject) {}
}

Write-Verbose "Importing class 'TransformDeletionPolicy'"

class TransformDeletionPolicy : ArgumentTransformationAttribute {
    hidden [EngineIntrinsics] $engineIntrinsics

    hidden [object] TransformData([object] $inputData) {
        if ($inputData.ToString() -in @('Delete','Retain','Snapshot')) {
            return (Get-Culture).TextInfo.ToTitleCase(($inputData.ToString()).ToLower())
        }
        return $inputData
    }

    [object] Transform([EngineIntrinsics] $engineIntrinsics, [object] $inputData) {
        $this.engineIntrinsics = $engineIntrinsics
        return $this.TransformData($inputData)
    }
}

Write-Verbose "Importing class 'TransformTag'"

class TransformTag : ArgumentTransformationAttribute {
    hidden [EngineIntrinsics] $engineIntrinsics

    hidden [hashtable] ConvertToTag([object] $key, [object] $value) {
        $tag = @{
            Key = $key.ToString()
            Value = $value
        }
        return $tag
    }

    hidden [IEnumerable[IDictionary]] TransformHashtable([IDictionary] $inputData) {
        $final = [List[IDictionary]]::new()
        if (($inputData['key'] -or $inputData.Key) -and ($inputData['value'] -or $inputData.Value)) {
            $final.Add($inputData)
        } else {
            $inputData.GetEnumerator() | ForEach-Object {
                $final.Add(@{
                    Key = $_.Key
                    Value = $_.Value
                })
            }
        }
        return $final
    }

    hidden [IEnumerable[IDictionary]] TransformPSObject([PSObject] $inputData) {
        $final = [List[IDictionary]]::new()
        if ($inputData.Properties.Name -contains "Key" -and $inputData.Properties.Name -contains "Value") {
            $final.Add(@{
                Key = $inputData.Key
                Value = $inputData.Value
            })
        }
        else {
            foreach ($property in $inputData.Properties) {
                $final.Add(@{
                    Key = $_.Name
                    Value = $_.Value
                })
            }
        }
        return $final
    }

    hidden [IEnumerable[IDictionary]] TransformSingle([object] $inputData) {
        $final = [List[IDictionary]]::new()
        if ($inputData -is [IDictionary]) {
            foreach ($tag in $this.TransformHashtable($inputData -as [IDictionary])) {
                $final.Add($tag)
            }
        }
        elseif ($inputData -is [PSObject]) {
            foreach ($tag in $this.TransformPSObject($inputData)) {
                $final.Add($tag)
            }
        }
        return $final
    }

    hidden [IEnumerable[IDictionary]] TransformData([object] $inputData) {
        $final = [List[IDictionary]]::new()
        if ($inputData -is [Array]) {
            foreach ($item in ($inputData -as [Array])) {
                foreach ($tag in $this.TransformSingle($item)) {
                    $final.Add($tag)
                }
            }
        }
        else {
            foreach ($tag in $this.TransformSingle($inputData)) {
                $final.Add($tag)
            }
        }
        return $final
    }

    [object] Transform([EngineIntrinsics] $engineIntrinsics, [object] $inputData) {
        $this.engineIntrinsics = $engineIntrinsics
        return $this.TransformData($inputData)
    }
}

Write-Verbose "Importing class 'TransformUserData'"

class TransformUserData : ArgumentTransformationAttribute {
    hidden [EngineIntrinsics] $engineIntrinsics

    hidden [object] TransformData([object] $inputData) {
        Write-Debug "Transforming UserData"
        if (
            $inputData.GetType().FullName -match ('^(UserData|Fn(Base64|Join)|Con(Ref|And|Equals|If|Not|Or))$') -or
            $inputData.GetType() -in @([IDictionary],[psobject])
        ) {
            return $inputData
        }
        else {
            $list = if ($inputData -is [array]) {
                $inputData
            }
            else {
                @($inputData)
            }
            $final = @()
            $tag = $null
            foreach ($item in $list) {
                if ($item -is [string] -and (Test-Path $item)) {
                    $obj = Get-Item $item
                    $tag = switch -RegEx ($obj.Extension) {
                        '^\.ps1$' {
                            "powershell"
                        }
                        '^\.(bat|cmd)$' {
                            "script"
                        }
                        default {
                            $null
                        }
                    }
                    [File]::ReadAllLines($obj.FullName) | ForEach-Object {
                        $final += $_
                    }
                }
                else {
                    $final += $item
                }
            }
            if ($null -ne $tag -and ($final -join [Environment]::NewLine) -notmatch "<$tag>") {
                $final.Insert(0,"<$($tag)>") | Out-Null
                $final += "</$($tag)>"
            }
            return @{
                'Fn::Base64' = @{
                    'Fn::Join' = @(
                        [Environment]::NewLine,
                        @($final)
                    )
                }
            }
        }
    }

    [object] Transform([EngineIntrinsics] $engineIntrinsics, [object] $inputData) {
        $this.engineIntrinsics = $engineIntrinsics
        return $this.TransformData($inputData)
    }
}

Write-Verbose "Importing class 'ValidateLogicalId'"
class ValidateLogicalId : ValidateArgumentsAttribute {
    hidden [EngineIntrinsics] $engineIntrinsics

    [Void] ValidateLogicalIdString(
        [object] $logicalId
    ) {
        if ($logicalId.ToString() -notmatch '^(\w+|AWS::CloudFormation::.*|Fn::Transform)$') {
            throw [VSError]::InvalidLogicalId($logicalId)
        }
    }

    [Void] Validate([object] $arguments, [EngineIntrinsics] $engineIntrinsics) {
        $this.engineIntrinsics = $engineIntrinsics
        $this.ValidateLogicalIdString($arguments)
    }
}

Write-Verbose "Importing class 'ValidateType'"
class ValidateType : ValidateEnumeratedArgumentsAttribute {
    hidden [Type[]]
    $_validTypes

    ValidateType([Type] $validType) {
        $this._validTypes = @($validType)
    }
    ValidateType([Type[]] $validTypes) {
        $this._validTypes = $validTypes
    }

    [Void] ValidateElement(
        [Object]$element
    ) {
        if ($element.GetType().FullName -in $this._validTypes.FullName) {
            Write-Debug "[$element] Validated current Type as acceptable -- matched against type: $($element.GetType().FullName)"
        }
        elseif ($element.GetType().BaseType.FullName -in $this._validTypes.FullName) {
            Write-Debug "[$element] Validated BaseType as acceptable -- matched against type: $($element.GetType().BaseType.FullName)"
        }
        elseif ($validMatch = $this._validTypes | Where-Object {$element -is $_}) {
            Write-Debug "[$element] Validated MatchedType(s) as acceptable -- matched against type: $($validMatch.FullName)"
        }
        else {
            throw [VSError]::InvalidType($element, $this._validTypes)
        }
    }
}