VaporShell.Classes.ps1
using namespace System using namespace System.Collections using namespace System.Collections.Generic using namespace System.Collections.Specialized using namespace System.IO using namespace System.Management.Automation using namespace System.Xml [CmdletBinding()] Param() Write-Verbose "Importing class 'AutoScalingProcess'" enum AutoScalingProcess { Launch Terminate HealthCheck ReplaceUnhealthy AZRebalance AlarmNotification ScheduledActions AddToLoadBalancer } Write-Verbose "Importing class 'DeletionPolicy'" enum DeletionPolicy { Delete Retain Snapshot } Write-Verbose "Importing class 'LoggingLevel'" enum LoggingLevel { OFF ERROR INFO } Write-Verbose "Importing class 'UpdateReplacePolicy'" enum UpdateReplacePolicy { Delete Retain Snapshot } Write-Verbose "Importing class 'VSHashtable'" class VSHashtable : OrderedDictionary { # Anything inheriting from this class will only show the hashtable contents. # Object properties will be stripped when cast to JSON/YAML. # Useful for adding corresponding public properties for intellisense. static hidden [string] $_vsFunctionName = '' static hidden [string] $_awsDocumentation = '' hidden [string[]] $_commonParams = @('Verbose','Debug','ErrorAction','WarningAction','InformationAction','ErrorVariable','WarningVariable','InformationVariable','OutVariable','OutBuffer','PipelineVariable') hidden [void] _addAccessors() {} [object] Help() { return $this.Help($null) } [object] Help([string] $scope) { if ([string]::IsNullOrEmpty($this._vsFunctionName)) { return "Help content has not been created for this class. Please open an issue on the GitHub repository to request help for this class." } $params = @{Name = $this._vsFunctionName} switch -Regex ($scope) { '^F(u|ull){0,1}' { $params["Full"] = $true } '^D(e|etail){0,1}' { $params["Detailed"] = $true } '^E(x|xample){0,1}' { $params["Examples"] = $true } '^O(n|nline){0,1}$' { $params["Online"] = $true } } return (Get-Help @params) } [string] Docs() { if ([string]::IsNullOrEmpty($this._awsDocumentation)) { return "AWS Documentation link not found for this class!" } Start-Process $this._awsDocumentation return "Opening documentation link: $($this._awsDocumentation)" } [OrderedDictionary] ToOrderedDictionary() { return $this.ToOrderedDictionary($false) } [OrderedDictionary] ToOrderedDictionary([bool] $addAllProperties) { $clean = [ordered]@{} $this.GetEnumerator() | ForEach-Object { $key = if ($_.Name) { $_.Name } else { $_.Key } $value = $_.Value if ( $addAllProperties -or ( $key -notmatch '^(_|LogicalId$)' -and ( $value -is [enum] -or $key -match '::' -or $null -ne $value ) -and ( $value -isnot [string] -or -not [string]::IsNullOrEmpty($value) ) ) ) { $clean[$key] = if ($key -match '$(UpdateReplacePolicy|DeletionPolicy)$' -and $value.ToString() -match '^(Delete|Retain|Snapshot)$') { (Get-Culture).TextInfo.ToTitleCase($value.ToString().ToLower()) } elseif ($value -is [enum]) { $value.ToString() } elseif ($value -is [IDictionary] -and $value -isnot [VSHashtable]) { $value } elseif ($value | Get-Member -Name ToOrderedDictionary* -MemberType Method -ErrorAction SilentlyContinue) { try { $value.ToOrderedDictionary($addAllProperties) } catch { $value } } else { $value } Write-Debug "Key matched: $key" Write-Debug "Value matched: $($clean[$key])" } else { Write-Debug "Key excluded: $key" } } return $clean } [string] ToJson() { return $this.ToJson($false) } [string] ToJson([bool] $compress) { $clean = if ($this['LogicalId']) { @{$this['LogicalId'] = $this.ToOrderedDictionary()} } else { $this.ToOrderedDictionary() } return $clean | ConvertTo-Json -Depth 50 -Compress:$compress } [string] ToYaml() { return $this.ToYaml($false) } [string] ToYaml([bool] $usePowerShellYaml) { $flipped = if (-not $usePowerShellYaml -and $null -ne (Get-Command cfn-flip* -ErrorAction SilentlyContinue)) { ($this.ToJson() | cfn-flip) -join [System.Environment]::NewLine } else { $clean = if ($this['LogicalId']) { @{$this['LogicalId'] = $this.ToOrderedDictionary()} } else { $this.ToOrderedDictionary() } ($clean | ConvertTo-Yaml) -join [System.Environment]::NewLine } return $flipped } VSHashtable() { $this._addAccessors() } VSHashtable([IDictionary] $props) { $this._addAccessors() Write-Debug "[$($this.GetType())] Contructing from input IDictionary" $props.GetEnumerator() | ForEach-Object { Write-Debug "[$($this.GetType())] [$($_.Key)] Checking for property" if ($this.GetType().FullName -eq 'VSHashtable' -or ($this.PSObject.Properties.Name -contains $_.Key -and $_.Key -notin $this._commonParams)) { Write-Debug "[$($this.GetType())] [$($_.Key)] Property found, adding value: $($_.Value)" $val = if ($_.Value -is [enum]) { $_.Value.ToString() } else { $_.Value } $this[$_.Key] = $val } } } VSHashtable([psobject] $props) { $this._addAccessors() Write-Debug "[$($this.GetType())] Contructing from input PSObject" $props.PSObject.Properties | ForEach-Object { Write-Debug "[$($this.GetType())] [$($_.Name)] Checking for property" if ($this.GetType().FullName -eq 'VSHashtable' -or ($this.PSObject.Properties.Name -contains $_.Name -and $_.Name -notin $this._commonParams)) { Write-Debug "[$($this.GetType())] [$($_.Name)] Property found, adding value: $($_.Value)" $val = if ($_.Value -is [enum]) { $_.Value.ToString() } else { $_.Value } $this[$_.Name] = $val } } } } Write-Verbose "Importing class 'VSObject'" class VSObject : object { # Anything inheriting from this class will only show the hashtable contents. # Object properties will be stripped when cast to JSON/YAML. # Useful for adding corresponding public properties for intellisense. static hidden [string] $_vsFunctionName = '' static hidden [string] $_awsDocumentation = '' hidden [string[]] $_commonParams = @('Verbose','Debug','ErrorAction','WarningAction','InformationAction','ErrorVariable','WarningVariable','InformationVariable','OutVariable','OutBuffer','PipelineVariable') hidden [void] _addAccessors() {} [object] Help() { return $this.Help($null) } [object] Help([string] $scope) { if ([string]::IsNullOrEmpty($this._vsFunctionName)) { return "Help content has not been created for this class. Please open an issue on the GitHub repository to request help for this class." } $params = @{Name = $this._vsFunctionName} switch -Regex ($scope) { '^F(u|ull){0,1}' { $params["Full"] = $true } '^D(e|etail){0,1}' { $params["Detailed"] = $true } '^E(x|xample){0,1}' { $params["Examples"] = $true } '^O(n|nline){0,1}$' { $params["Online"] = $true } } return (Get-Help @params) } [string] Docs() { if ([string]::IsNullOrEmpty($this._awsDocumentation)) { return "AWS Documentation link not found for this class!" } Start-Process $this._awsDocumentation return "Opening documentation link: $($this._awsDocumentation)" } [System.Collections.Specialized.OrderedDictionary] ToOrderedDictionary() { return $this.ToOrderedDictionary($false) } [System.Collections.Specialized.OrderedDictionary] ToOrderedDictionary([bool] $addAllProperties) { $clean = [ordered]@{} $this.PSObject.Properties | ForEach-Object { $key = $_.Name $value = $_.Value if ( $addAllProperties -or ( $key -notmatch '^(_|LogicalId$)' -and ( $value -is [enum] -or $key -match '::' -or $null -ne $value ) -and ( $value -isnot [string] -or -not [string]::IsNullOrEmpty($value) ) ) ) { $clean[$key] = if ($key -match '$(UpdateReplacePolicy|DeletionPolicy)$' -and $value.ToString() -match '^(Delete|Retain|Snapshot)$') { (Get-Culture).TextInfo.ToTitleCase($value.ToString().ToLower()) } elseif ($value -is [enum]) { $value.ToString() } elseif ($value -is [System.Collections.IDictionary] -and $value -isnot [VSHashtable]) { $value } elseif ($value | Get-Member -Name ToOrderedDictionary* -MemberType Method -ErrorAction SilentlyContinue) { try { $value.ToOrderedDictionary($addAllProperties) } catch { $value } } else { $value } Write-Debug "Key matched: $key" Write-Debug "Value matched: $($clean[$key])" } else { Write-Debug "Key excluded: $key" } } return $clean } [string] ToJson() { return $this.ToJson($false) } [string] ToJson([bool] $compress) { $clean = if ($this.PSObject.Properties.Name -contains 'LogicalId') { @{$this.LogicalId = $this.ToOrderedDictionary()} } else { $this.ToOrderedDictionary() } return $clean | ConvertTo-Json -Depth 50 -Compress:$compress } [string] ToYaml() { return $this.ToYaml($false) } [string] ToYaml([bool] $usePowerShellYaml) { $flipped = if (-not $usePowerShellYaml -and $null -ne (Get-Command cfn-flip* -ErrorAction SilentlyContinue)) { ($this.ToJson() | cfn-flip) -join [System.Environment]::NewLine } else { $clean = if ($this.PSObject.Properties.Name -contains 'LogicalId') { @{$this.LogicalId = $this.ToOrderedDictionary()} } else { $this.ToOrderedDictionary() } ($clean | ConvertTo-Yaml) -join [System.Environment]::NewLine } return $flipped } VSObject() { $this._addAccessors() } VSObject([IDictionary] $props) { $this._addAccessors() Write-Debug "[$($this.GetType())] Contructing from input IDictionary" $props.GetEnumerator() | ForEach-Object { Write-Debug "[$($this.GetType())] [$($_.Key)] Checking for property" if ($_.Key -eq 'Fn::Transform' -or ($this.PSObject.Properties.Name -contains $_.Key -and $_.Key -notin $this._commonParams)) { Write-Debug "[$($this.GetType())] [$($_.Key)] Property found, adding value: $($_.Value)" $val = if ($_.Value -is [enum]) { $_.Value.ToString() } else { $_.Value } $this."$($_.Key)" = $val } } } VSObject([psobject] $props) { $this._addAccessors() Write-Debug "Contructing $($this.GetType()) from input PSObject" $props.PSObject.Properties | ForEach-Object { Write-Debug "[$($this.GetType())] [$($_.Name)] Checking for property" if ($_.Name -eq 'Fn::Transform' -or ($this.PSObject.Properties.Name -contains $_.Name -and $_.Name -notin $this._commonParams)) { Write-Debug "[$($this.GetType())] [$($_.Name)] Property found, adding value: $($_.Value)" $val = if ($_.Value -is [enum]) { $_.Value.ToString() } else { $_.Value } $this."$($_.Name)" = $val } } } } Write-Verbose "Importing class 'ConditionFunction'" class ConditionFunction : VSHashtable { hidden [string] $_topLevelKey = '_SHOULD_BE_OVERRIDDEN' hidden [type[]] $_validTypes = @([string], [int], [bool], [IDictionary], [psobject], [FnFindInMap], [FnRef], [ConditionFunction]) [string] ToString() { return "$($this._topLevelKey -replace '\W' -replace '^Fn','Con')($($this[$this._topLevelKey]))" } hidden [void] _validateInput([object[]] $inputData) {} ConditionFunction() : base() {} ConditionFunction([object[]] $value) { $this._addAccessors() $this._validateInput($value) $this[$this._topLevelKey] = @() foreach ($item in $value) { $isValid = foreach ($type in $this._validTypes) { if ($item -is $type) { $true break } } if (-not $isValid) { throw [VSError]::InvalidType($item, $this._validTypes) } Write-Debug "Adding $($this.GetType().Name) from input type $($item.GetType())" $this[$this._topLevelKey] += $item } } } Write-Verbose "Importing class 'IntrinsicFunction'" class IntrinsicFunction : VSHashtable { hidden [string] $_topLevelKey = '_SHOULD_BE_OVERRIDDEN' hidden [type[]] $_validTypes = @([string], [int], [IDictionary], [psobject], [IntrinsicFunction], [ConditionFunction]) [string] ToString() { return "$($this._topLevelKey -replace '\W')($($this[$this._topLevelKey]))" } hidden [void] _validateInput([object] $inputData) {} IntrinsicFunction() : base() {} IntrinsicFunction([object] $value) { $this._addAccessors() $this._validateInput($value) $isValid = foreach ($type in $this._validTypes) { if ($value -is $type) { $true break } } if (-not $isValid) { throw [VSError]::InvalidType($value, $this._validTypes) } if ($value -is [IDictionary] -and $value -isnot [IntrinsicFunction] -and $value -isnot [ConditionFunction]) { if ($value.Contains($this._topLevelKey)) { Write-Debug "Contructing $($this.GetType().Name) from input IDictionary" $this[$this._topLevelKey] = $value[$this._topLevelKey] } else { throw [VSError]::InvalidArgument($value, "The input object is missing the Key '$($this._topLevelKey)'. Unable to construct a $($this.GetType()) object from the input data.") } } elseif ($value -is [psobject]) { if ($value.PSObject.Properties.Name -contains $this._topLevelKey) { Write-Debug "Contructing $($this.GetType().Name) from input PSObject" $this[$this._topLevelKey] = $value."$($this._topLevelKey)" } else { throw [VSError]::InvalidArgument($value, "The input object is missing the Property '$($this._topLevelKey)'. Unable to construct a $($this.GetType()) object from the input data.") } } else { $this[$this._topLevelKey] = $value } } } Write-Verbose "Importing class 'VSLogicalObject'" class VSLogicalObject : VSObject { [ValidateLogicalId()] [string] $LogicalId [string] ToString() { return $this.LogicalId } VSLogicalObject() : base() {} VSLogicalObject([IDictionary] $props) : base($props) {} VSLogicalObject([psobject] $props) : base($props) {} } Write-Verbose "Importing class 'VSJson'" <# class VSJson : VSHashtable { static [VSJson] Transform([string] $jsonStringOrFilePath) { $newData = if (Test-Path $jsonStringOrFilePath) { $resolvedPath = (Resolve-Path $jsonStringOrFilePath).Path [File]::ReadAllText($resolvedPath) } else { $jsonStringOrFilePath } try { $final = (ConvertFrom-Json -InputObject $newData -ErrorAction Stop) } catch { throw [VSError]::InvalidJsonInput($jsonStringOrFilePath) } $dict = [ordered]@{} $final.PSObject.Properties | ForEach-Object { $dict[$_.Name] = $_.Value } return [VSJson]::new($dict) } VSJson() : base() {} VSJson([IDictionary] $dictionary) { $dictionary.GetEnumerator() | ForEach-Object { $this[$_.Key] = $_.Value } } VSJson([psobject] $psObject) { $psObject.PSObject.Properties | ForEach-Object { $this[$_.Name] = $_.Value } } VSJson([string] $jsonStringOrFilePath) { $this = $this::Transform($jsonStringOrFilePath) } VSJson([string[]] $jsonStrings) { $newString = $jsonStrings -join [Environment]::NewLine $this = $this::Transform($newString) } } <# #> class VSJson : VSHashtable { static [VSJson] Transform([string] $stringOrFilepath) { $decoded = if ($stringOrFilepath.Trim() -match '^%\d\w') { Write-Debug "String appears to be URL encoded, decoding with System.Web.HttpUtility::UrlDecode" Add-Type -AssemblyName System.Web [System.Web.HttpUtility]::UrlDecode($stringOrFilepath) } else { $stringOrFilepath } $newData = if (Test-Path $decoded) { $resolvedPath = (Resolve-Path $decoded).Path [File]::ReadAllText($resolvedPath) } else { $decoded } Write-Debug "New data: $newData" try { $final = (ConvertFrom-Json -InputObject $newData -ErrorAction Stop) } catch { throw [VSError]::InvalidJsonInput($decoded) } $dict = [ordered]@{} $final.PSObject.Properties | ForEach-Object { $dict[$_.Name] = $_.Value } Write-Debug "Resulting dict as parsed JSON: $($dict | ConvertTo-Json -Depth 20)" return [VSJson]$dict } static [VSJson] Transform([VSJson] $vsJson) { return $vsJson } VSJson() : base() {} VSJson([IDictionary] $dictionary) { if ($dictionary -is [VSJson]) { $this = $dictionary } else { $this = [ordered]@{} $dictionary.GetEnumerator() | ForEach-Object { Write-Debug "Adding key '$($_.Key)' with value '$($_.Value)' to $this" $this[$_.Key] = $_.Value } } } VSJson([psobject] $psObject) { $this = [ordered]@{} $psObject.PSObject.Properties | ForEach-Object { Write-Debug "Adding property '$($_.Name)' with value '$($_.Value)' to $this" $this[$_.Name] = $_.Value } } VSJson([string] $stringOrFilepath) { $this = $this::Transform($stringOrFilepath) } VSJson([string[]] $strings) { $newString = $strings -join [Environment]::NewLine $this = $this::Transform($newString) } } #> Write-Verbose "Importing class 'VSTimestamp'" class VSTimestamp : VSObject { hidden [string] $_timestamp static [string] Help() { return "Help content has not been created for this class. Please open an issue on the GitHub repository to request help for this class." } static [string] Transform([datetime] $dateTime) { return $dateTime.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss') } static [string] Transform([string] $dateString) { return (Get-Date $dateString).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss') } [string] ToString() { return $this._timestamp } VSTimestamp() {} VSTimestamp([datetime] $dateTime) { $this._timestamp = $this::Transform($dateTime) } VSTimestamp([string] $dateString) { $this._timestamp = $this::Transform($dateString) } } Write-Verbose "Importing class 'VSYaml'" <# class VSYaml : VSJson { static [VSYaml] Transform([string] $yamlStringOrFilePath) { $newData = if (Test-Path $yamlStringOrFilePath) { $resolvedPath = (Resolve-Path $yamlStringOrFilePath).Path [File]::ReadAllText($resolvedPath) } else { $yamlStringOrFilePath } try { $final = if (Get-Command cfn-flip* -ErrorAction SilentlyContinue) { $json = $newData | cfn-flip | Out-String ConvertFrom-Json -InputObject $json -ErrorAction Stop } else { ConvertFrom-Yaml -Yaml $newData -ErrorAction Stop } } catch { throw [VSError]::InvalidYamlInput($yamlStringOrFilePath) } $dict = [ordered]@{} $final.PSObject.Properties | ForEach-Object { $dict[$_.Name] = $_.Value } return [VSYaml]::new($dict) } VSYaml() : base() {} VSYaml([IDictionary] $dictionary) { $dictionary.GetEnumerator() | ForEach-Object { $this[$_.Key] = $_.Value } } VSYaml([psobject] $psObject) { $psObject.PSObject.Properties | ForEach-Object { $this[$_.Name] = $_.Value } } VSYaml([string] $yamlStringOrFilePath) { $this = $this::Transform($yamlStringOrFilePath) } VSYaml([string[]] $yamlStrings) { $newString = $yamlStrings -join [Environment]::NewLine $this = $this::Transform($newString) } } <# #> class VSYaml : VSJson { static [VSYaml] Transform([string] $stringOrFilepath) { $newData = if (Test-Path $stringOrFilepath) { $resolvedPath = (Resolve-Path $stringOrFilepath).Path [File]::ReadAllText($resolvedPath) } elseif ($stringOrFilepath -match '^(?:[^%]|%[0-9A-Fa-f]{2})+$') { Write-Debug "YAML string appears to be URL encoded, decoding with System.Web.HttpUtility::UrlDecode" Add-Type -AssemblyName System.Web [System.Web.HttpUtility]::UrlDecode($stringOrFilepath) } else { $stringOrFilepath } Write-Debug "New data: $newData" try { $final = if (Get-Command cfn-flip* -ErrorAction SilentlyContinue) { $json = $newData | cfn-flip | Out-String ConvertFrom-Json -InputObject $json -ErrorAction Stop } else { ConvertFrom-Yaml -Yaml $newData -ErrorAction Stop } } catch { throw [VSError]::InvalidYamlInput($stringOrFilepath) } $dict = [ordered]@{} $final.PSObject.Properties | ForEach-Object { $dict[$_.Name] = $_.Value } Write-Debug "Resulting dict as parsed JSON: $($dict | ConvertTo-Json -Depth 20)" return [VSYaml]$dict } static [VSYaml] Transform([VSYaml] $vsYaml) { return $vsYaml } VSYaml() : base() {} VSYaml([IDictionary] $dictionary) : base($dictionary) {} VSYaml([psobject] $psObject) : base($psObject) {} VSYaml([string] $stringOrFilepath) : base($stringOrFilepath) {} VSYaml([string[]] $strings) : base($strings) {} } #> Write-Verbose "Importing class 'VSTag'" class VSTag : VSHashtable { hidden [string] $_vsFunctionName = 'Add-VSTag' hidden [string] $_awsDocumentation = 'http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html' [string] $Key [object] $Value static [VSTag[]] TransformTag([object] $inputData) { $final = [List[VSTag]]::new() $list = if ($inputData -is [array]) { $inputData } else { @($inputData) } foreach ($item in $list) { if ($item -is [VSTag]) { $final.Add($item) } elseif ($item -is [IDictionary]) { if ($item['Key'] -and $item['Value']) { $final.Add( [VSTag]@{ Key = $item['Key'] Value = $item['Value'] } ) } else { $item.GetEnumerator() | ForEach-Object { $final.Add( [VSTag]@{ Key = $_.Key Value = $_.Value } ) } } } elseif ($item -is [psobject]) { if ($item.PSObject.Properties.Name -contains 'Key' -and $item.PSObject.Properties.Name -contains 'Value') { $final.Add( [VSTag]@{ Key = $item.Key Value = $item.Value } ) } else { $item.PSObject.Properties | ForEach-Object { $final.Add( [VSTag]@{ Key = $_.Name Value = $_.Value } ) } } } } return $final } VSTag() {} VSTag([IDictionary] $inputData) { $this.Key = $inputData.Key $this.Value = $inputData.Value } VSTag([psobject] $inputData) { $this.Key = $inputData.Key $this.Value = $inputData.Value } VSTag([object] $key, [object] $value) { $this.Key = $key.ToString() $this.Value = $value } } Write-Verbose "Importing class 'FnBase64'" class FnBase64 : IntrinsicFunction { hidden [string] $_vsFunctionName = 'Add-FnBase64' hidden [string] $_awsDocumentation = 'http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-base64.html' hidden [string] $_topLevelKey = 'Fn::Base64' FnBase64() : base() {} FnBase64([object] $value) : base($value) {} } Write-Verbose "Importing class 'FnCidr'" class FnCidr : IntrinsicFunction { hidden [string] $_vsFunctionName = 'Add-FnCidr' hidden [string] $_awsDocumentation = 'http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-cidr.html' hidden [string] $_topLevelKey = 'Fn::Cidr' hidden [void] _validateInput([object] $inputData) { if ($inputData.Count -ne 3) { throw [VSError]::InvalidArgument($inputData, "Total input item count when constructing a <$($this.GetType())> object needs to be 3. Count provided: $($inputData.Count)") } } FnCidr() : base() {} FnCidr([object] $value) : base($value) {} FnCidr( [object] $ipBlock, [object] $count, [object] $cidrBits ) { foreach ($item in @($ipBlock,$count,$cidrBits)) { $isValid = foreach ($type in $this._validTypes) { if ($item -is $type) { $true break } } if (-not $isValid) { throw [VSError]::InvalidType($item, $this._validTypes) } } $this[$this._topLevelKey] = @($ipBlock, $count, $cidrBits) } } Write-Verbose "Importing class 'FnFindInMap'" class FnFindInMap : IntrinsicFunction { hidden [string] $_vsFunctionName = 'Add-FnFindInMap' hidden [string] $_awsDocumentation = 'http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-findinmap.html' hidden [string] $_topLevelKey = 'Fn::FindInMap' hidden [void] _validateInput([object] $inputData) { if ($inputData.Count -ne 3) { throw [VSError]::InvalidArgument($inputData, "Total input item count when constructing a <$($this.GetType())> object needs to be 3. Count provided: $($inputData.Count)") } elseif ($inputData[0] -isnot [string]) { throw [VSError]::InvalidArgument($inputData, "The first item provided when constructing a <$($this.GetType())> object needs to be a string. Type provided: $($inputData[0].GetType())") } } FnFindInMap() : base() {} FnFindInMap([object] $value) : base($value) {} FnFindInMap( [string] $mapName, [object] $topLevelKey, [object] $secondLevelKey ) { foreach ($item in @($topLevelKey,$secondLevelKey)) { $isValid = foreach ($type in $this._validTypes) { if ($item -is $type) { $true break } } if (-not $isValid) { throw [VSError]::InvalidType($item, $this._validTypes) } } $this[$this._topLevelKey] = @($mapName, $topLevelKey, $secondLevelKey) } } Write-Verbose "Importing class 'FnGetAtt'" class FnGetAtt : IntrinsicFunction { hidden [string] $_vsFunctionName = 'Add-FnGetAtt' hidden [string] $_awsDocumentation = 'http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html' hidden [string] $_topLevelKey = 'Fn::GetAtt' hidden [void] _validateInput([object] $inputData) { if ($inputData.Count -ne 2) { throw [VSError]::InvalidArgument($inputData, "Total input item count when constructing a <$($this.GetType())> object needs to be 3. Count provided: $($inputData.Count)") } } FnGetAtt() : base() {} FnGetAtt([object] $value) : base($value) {} FnGetAtt([string] $logicalNameOfResource, [string] $attributeName) { $this[$this._topLevelKey] = @($logicalNameOfResource,$attributeName) } } Write-Verbose "Importing class 'FnGetAZs'" class FnGetAZs : IntrinsicFunction { hidden [string] $_vsFunctionName = 'Add-FnGetAZs' hidden [string] $_awsDocumentation = 'http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getavailabilityzones.html' hidden [string] $_topLevelKey = 'Fn::GetAZs' FnGetAZs() { $this[$this._topLevelKey] = '' } FnGetAZs([object] $value) : base($value) {} } Write-Verbose "Importing class 'FnImportValue'" class FnImportValue : IntrinsicFunction { hidden [string] $_vsFunctionName = 'Add-FnImportValue' hidden [string] $_awsDocumentation = 'http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html' hidden [string] $_topLevelKey = 'Fn::ImportValue' FnImportValue() : base() {} FnImportValue([object] $value) : base($value) {} } Write-Verbose "Importing class 'FnJoin'" class FnJoin : IntrinsicFunction { hidden [string] $_vsFunctionName = 'Add-FnJoin' hidden [string] $_awsDocumentation = 'http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-join.html' hidden [string] $_topLevelKey = 'Fn::Join' hidden [void] _validateInput([object] $inputData) { if ($inputData.Count -ne 2) { throw [VSError]::InvalidArgument($inputData, "Total input item count when constructing a <$($this.GetType())> object needs to be 3. Count provided: $($inputData.Count)") } elseif ($inputData[0] -isnot [string]) { throw [VSError]::InvalidArgument($inputData, "The first item provided when constructing a <$($this.GetType())> object needs to be a string. Type provided: $($inputData[0].GetType())") } } FnJoin() : base() {} FnJoin([object] $value) : base($value) {} FnJoin( [string] $delimiter, [object[]] $listOfValues ) { $validTypes = @([string], [int], [IntrinsicFunction], [ConditionFunction]) foreach ($value in $listOfValues) { $isValid = foreach ($type in $validTypes) { if ($value -is $type) { $true break } } if (-not $isValid) { throw [VSError]::InvalidType($value, $validTypes) } } $this[$this._topLevelKey] = @($delimiter, @($listOfValues)) } } Write-Verbose "Importing class 'FnRef'" class FnRef : IntrinsicFunction { hidden [string] $_vsFunctionName = 'Add-FnRef' hidden [string] $_awsDocumentation = 'http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html' static [FnRef] $AccountId = [FnRef]::new('AWS::AccountId') static [FnRef] $Include = [FnRef]::new('AWS::Include') static [FnRef] $NotificationARNs = [FnRef]::new('AWS::NotificationARNs') static [FnRef] $NoValue = [FnRef]::new('AWS::NoValue') static [FnRef] $Partition = [FnRef]::new('AWS::Partition') static [FnRef] $Region = [FnRef]::new('AWS::Region') static [FnRef] $StackId = [FnRef]::new('AWS::StackId') static [FnRef] $StackName = [FnRef]::new('AWS::StackName') static [FnRef] $URLSuffix = [FnRef]::new('AWS::URLSuffix') [string] $Ref [string] ToString() { return "Ref($($this['Ref']))" } hidden [void] _addAccessors() { $this | Add-Member -Force -MemberType ScriptProperty -Name Ref -Value { $this['Ref'] } -SecondValue { param([string] $ref) $this['Ref'] = $ref } } FnRef() {} FnRef([string] $ref) { $this['Ref'] = $ref } FnRef([VSLogicalObject] $vsLogicalObject) { $this['Ref'] = $vsLogicalObject.ToString() } } Write-Verbose "Importing class 'FnSelect'" class FnSelect : IntrinsicFunction { hidden [string] $_vsFunctionName = 'Add-FnSelect' hidden [string] $_awsDocumentation = 'http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-select.html' hidden [string] $_topLevelKey = 'Fn::Select' hidden [void] _validateInput([object] $inputData) { if ($inputData.Count -ne 2) { throw [VSError]::InvalidArgument($inputData, "Total input item count when constructing a <$($this.GetType())> object needs to be 3. Count provided: $($inputData.Count)") } } FnSelect() : base() {} FnSelect([object] $value) : base($value) {} FnSelect( [object] $index, [object[]] $listOfObjects ) { $isValid = foreach ($type in $this._validTypes) { if ($index -is $type) { $true break } } if (-not $isValid) { throw [VSError]::InvalidType($index, $this._validTypes) } foreach ($value in $listOfObjects) { $isValid = foreach ($type in $this._validTypes) { if ($value -is $type) { $true break } } if (-not $isValid) { throw [VSError]::InvalidType($value, $this._validTypes) } } $this[$this._topLevelKey] = @($index, @($listOfObjects)) } } Write-Verbose "Importing class 'FnSplit'" class FnSplit : IntrinsicFunction { hidden [string] $_vsFunctionName = 'Add-FnSplit' hidden [string] $_awsDocumentation = 'http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-split.html' hidden [string] $_topLevelKey = 'Fn::Split' hidden [void] _validateInput([object] $inputData) { if ($inputData.Count -ne 2) { throw [VSError]::InvalidArgument($inputData, "Total input item count when constructing a <$($this.GetType())> object needs to be 3. Count provided: $($inputData.Count)") } elseif ($inputData[0] -isnot [string]) { throw [VSError]::InvalidArgument($inputData, "The first item provided when constructing a <$($this.GetType())> object needs to be a string. Type provided: $($inputData[0].GetType())") } } FnSplit() : base() {} FnSplit([object] $value) : base($value) {} FnSplit( [string] $delimiter, [object] $sourceString ) { $isValid = foreach ($type in $this._validTypes) { if ($sourceString -is $type) { $true break } } if (-not $isValid) { throw [VSError]::InvalidType($sourceString, $this._validTypes) } $this[$this._topLevelKey] = @($delimiter,$sourceString) } } Write-Verbose "Importing class 'FnSub'" class FnSub : IntrinsicFunction { hidden [string] $_vsFunctionName = 'Add-FnSub' hidden [string] $_awsDocumentation = 'http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html' hidden [string] $_topLevelKey = 'Fn::Sub' FnSub() {} FnSub([string] $string) { $this['Fn::Sub'] = $String } FnSub( [string] $string, [IDictionary] $mapping ) { $this['Fn::Sub'] = @($String,$Mapping) } } Write-Verbose "Importing class 'FnTransform'" class FnTransform : VSHashtable { hidden [string] $_vsFunctionName = 'Add-FnTransform' hidden [string] $_awsDocumentation = 'http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-transform.html' [string] $LogicalId = 'Fn::Transform' [string] $Name [IDictionary] $Parameters [string] ToString() { return "FnTransform($($this['Fn::Transform']))" } hidden [void] _addAccessors() { $this.LogicalId = 'Fn::Transform' $this['Name'] = '' $this | Add-Member -Force -MemberType ScriptProperty -Name 'LogicalId' -Value { 'Fn::Transform' } -SecondValue { param([object] $value) $this.LogicalId = 'Fn::Transform' } $this | Add-Member -Force -MemberType ScriptProperty -Name 'Parameters' -Value { $this['Parameters'] } -SecondValue { param([object] $value) if ($value -is [IDictionary]) { $this['Parameters'] = $value } elseif ($value -is [psobject]) { $ord = [ordered]@{} $value.PSObject.Properties | ForEach-Object { $ord[$_.Name] = $_.Value } $this['Parameters'] = $ord } else { throw [VSError]::InvalidArgument($value,"Input value for the Parameters property on an FnTransform object must be either an IDictionary or a PSObject.") } } } FnTransform() : base() {} FnTransform([IDictionary] $props) : base($props) {} FnTransform([psobject] $props) : base($props) {} } Write-Verbose "Importing class 'Include'" class Include : FnTransform { hidden [string] $_vsFunctionName = 'Add-Include' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/create-reusable-transform-function-snippets-and-add-to-your-template-with-aws-include-transform.html' [string] ToString() { return "Include($($this.Parameters['Location']))" } hidden [void] SetLocation([object] $inputData) { $this['LogicalId'] = 'Fn::Transform' $this['Name'] = 'AWS::Include' if ($null -eq $this['Parameters']) { $this['Parameters'] = [ordered]@{Location = ''} } $props = if ($inputData -is [string]) { [pscustomobject]@{ Location = $inputData } } elseif ($inputData -is [IDictionary]) { [pscustomobject]$inputData } elseif ($inputData -is [psobject]) { $inputData } else { $errorRecord = [VSError]::new( [ArgumentException]::new("Invalid input! Please either pass an IDictionary or PSObject containing a Parameters or Location property or just the S3 location as a string value."), 'InvalidInput', [ErrorCategory]::InvalidArgument, $inputData ) throw [VSError]::InsertError($errorRecord) } if ($props.PSObject.Properties.Name -contains 'Parameters') { if ($props.Parameters.Location -notmatch '^s3:\/\/.*') { $errorRecord = [VSError]::new( [ArgumentException]::new("$($props.Parameters.Location) is not a valid s3 path! Location must match pattern '^s3:\/\/.*'"), 'InvalidLocation', [ErrorCategory]::InvalidArgument, $props ) throw [VSError]::InsertError($errorRecord) } else { $this['Parameters']['Location'] = $props.Parameters.Location } } elseif ($props.PSObject.Properties.Name -contains 'Location') { if ($props.Location -match '^s3:\/\/.*') { $this['Parameters']['Location'] = $props.Location } else { $errorRecord = [VSError]::new( [ArgumentException]::new("$($props.Location) is not a valid s3 path! Location must match pattern '^s3:\/\/.*'"), 'InvalidLocation', [ErrorCategory]::InvalidArgument, $props ) throw [VSError]::InsertError($errorRecord) } } else { $errorRecord = [VSError]::new( [ArgumentException]::new("Invalid input! Please either pass an IDictionary or PSObject containing a Parameters or Location property or just the S3 location as a string value."), 'InvalidInput', [ErrorCategory]::InvalidArgument, $props ) throw [VSError]::InsertError($errorRecord) } } Include() {} Include([IDictionary] $props) { $this.SetLocation($props) } Include([psobject] $props) { $this.SetLocation($props) } Include([string] $location) { $this.SetLocation($location) } } Write-Verbose "Importing class 'VSTemplate'" class VSTemplate : VSObject { hidden [string] $_vsFunctionName = 'Initialize-VaporShell' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-anatomy.html' hidden [string]$_description = $null hidden [string] $_awsTemplateFormatVersion = $null hidden [OrderedDictionary] $_mappings = $null hidden [object[]] $_mappingsOriginal = @() hidden [OrderedDictionary] $_parameters = $null hidden [object[]] $_parametersOriginal = @() hidden [OrderedDictionary] $_resources = $null hidden [object[]] $_resourcesOriginal = @() hidden [OrderedDictionary] $_outputs = $null hidden [object[]] $_outputsOriginal = @() hidden [OrderedDictionary] $_metadata = $null hidden [object[]] $_metadataOriginal = @() hidden [object[]] $_transform = @() hidden [OrderedDictionary] $_conditions = $null hidden [object[]] $_conditionsOriginal = @() [string] $AWSTemplateFormatVersion = $null [string] $Description = $null [FnTransform[]] $Transform = $null [VSParameter[]] $Parameters = $null [VSCondition[]] $Conditions = $null [VSMetadata[]] $Metadata = $null [VSMapping[]] $Mappings = $null [VSResource[]] $Resources = $null [VSOutput[]] $Outputs = $null static [string] Help() { $help = "This is the Template help." return $help } [string] ToString() { return $this.ToJson() } [string] Export([bool] $passThru, [string] $format) { if ($null -eq $this.Resources) { throw [VSError]::InvalidArgument($this,"Unable to find any resources on this Vaporshell template. Resources are required in CloudFormation templates at the minimum.") } $out = switch -RegEx ($format.ToLower()) { '^(y|yml|yaml)$' { $this.ToYaml() } '^(j|jsn|json)$' { $this.ToJson() } default { $this.ToJson() } } return $out } [void] Export ([string] $path) { $format = switch -RegEx ($Path) { '\.(yml|yaml)$' { 'YAML' } default { 'JSON' } } $this.Export($path, $format, $false) } [void] Export ([string] $path, [bool] $force) { $format = switch -RegEx ($Path) { '\.(yml|yaml)$' { 'YAML' } default { 'JSON' } } $this.Export($path, $format, $force) } [void] Export([string] $path, [string] $format, [bool] $force) { if ($null -eq $this.Resources) { throw [VSError]::InvalidArgument($this,"Unable to find any resources on this Vaporshell template. Resources are required in CloudFormation templates at the minimum.") } $forcePref = @{} if ($force) { $forcePref.add("Force",$True) } switch -RegEx ($format.ToLower()) { '^(yml|yaml)$' { $this.ToYaml() | Set-Content $path @forcePref } '^(template|json|cfn|cf)$' { $this.ToJson() | Set-Content $path @forcePref } } } [void] Remove([string] $logicalId, [string] $section) { $validSections = @('Parameters', 'Conditions', 'Metadata', 'Mappings', 'Resources', 'Outputs','Globals') if ($this.PSObject.Properties.Name -notcontains $section) { $message = "The section $section was not found on $($this.GetType()). Valid sections: $($validSections -join ', ')" throw [VSError]::InvalidArgument($logicalId, $message) } $_section = '_' + $section.Substring(0, 1).ToLower() + $section.Substring(1) $_sectionOriginal = $_section + 'Original' if (($this.$_section).Contains($logicalId)) { ($this.$_section).Remove($logicalId) | Out-Null $this.$_sectionOriginal = $this.$_sectionOriginal | Where-Object { $_.LogicalId -ne $logicalId } } } [void] RemoveParameter([string] $logicalId) { $this.Remove($logicalId, 'Parameters') } [void] RemoveCondition([string] $logicalId) { $this.Remove($logicalId, 'Conditions') } [void] RemoveMetadata([string] $logicalId) { $this.Remove($logicalId, 'Metadata') } [void] RemoveMapping([string] $logicalId) { $this.Remove($logicalId, 'Mappings') } [void] RemoveResource([string] $logicalId) { $this.Remove($logicalId, 'Resources') } [void] RemoveOutput([string] $logicalId) { $this.Remove($logicalId, 'Outputs') } [void] AddTransform([object] $transform) { if ($transform -is [string]) { if ($transform -match 'Serverless' -and $this._transform -notcontains 'AWS::Serverless-2016-10-31') { $this._transform += 'AWS::Serverless-2016-10-31' } else { $this._transform += $transform } } elseif ($transform -is [FnTransform]) { $this._transform += $transform.ToOrderedDictionary() } elseif ($cast = $transform -as [FnTransform]) { $this._transform += $cast.ToOrderedDictionary() } else { throw [VSError]::InvalidType($transform, @([string], [FnTransform])) } } [void] AddTransform([object[]] $transforms) { $transforms | ForEach-Object { $this.AddTransform($_) } } [void] AddSAMTransform() { $this.AddTransform('AWS::Serverless-2016-10-31') } [void] AddCondition([object] $item) { if ($item.GetType() -in @([string],[int],[bool],[double],[long])) { throw [VSError]::InvalidType($item,@([VSObject],[VSHashtable],[psobject],[IDictionary])) } elseif ($null -eq $this._conditions) { $this._conditions = [ordered]@{} } if ($null -eq $item.LogicalId) { throw [VSError]::MissingLogicalId($item, 'Condition') } elseif ($item -is [VSCondition] -and $this._conditions.Contains($item.LogicalId)) { throw [VSError]::DuplicateLogicalId($item, 'Condition') } elseif ($item -is [VSCondition]) { $this._conditions[$item.LogicalId] = $item.Condition } elseif ($item -is [FnTransform]) { $cleaned = [ordered]@{ Name = $item.Name Parameters = $item.Parameters } $this._conditions[$item.LogicalId] = $cleaned } elseif ($cast = $item -as [FnTransform]) { $cleaned = [ordered]@{ Name = $cast.Name Parameters = $cast.Parameters } $this._conditions[$cast.LogicalId] = $cleaned } else { throw [VSError]::InvalidType($item, @([VSCondition], [FnTransform])) } $this._conditionsOriginal += $item } [void] AddCondition([object[]] $items) { $items | ForEach-Object { $this.AddCondition($_) } } [void] AddMapping([object] $item) { if ($item.GetType() -in @([string],[int],[bool],[double],[long])) { throw [VSError]::InvalidType($item,@([VSObject],[VSHashtable],[psobject],[IDictionary])) } elseif ($null -eq $this._mappings) { $this._mappings = [ordered]@{} } if ($null -eq $item.LogicalId) { throw [VSError]::MissingLogicalId($item, 'Mapping') } elseif ($item -is [VSMapping] -and $this._mappings.Contains($item.LogicalId)) { throw [VSError]::DuplicateLogicalId($item, 'Mapping') } elseif ($item -is [VSMapping]) { $this._mappings[$item.LogicalId] = $item.Map } elseif ($item -is [FnTransform]) { if ($this._mappings.Contains($item.LogicalId)) { $this._mappings[$item.LogicalId] += $item.ToOrderedDictionary() } else { $this._mappings[$item.LogicalId] = $item.ToOrderedDictionary() } } elseif ($cast = $item -as [FnTransform]) { if ($this._mappings.Contains($item.LogicalId)) { $this._mappings[$item.LogicalId] += $cast.ToOrderedDictionary() } else { $this._mappings[$item.LogicalId] = $cast.ToOrderedDictionary() } } else { throw [VSError]::InvalidType($item, @([VSMapping], [FnTransform])) } $this._mappingsOriginal += $item } [void] AddMapping([object[]] $items) { $items | ForEach-Object { $this.AddMapping($_) } } [void] AddOutput([object] $item) { if ($item.GetType() -in @([string],[int],[bool],[double],[long])) { throw [VSError]::InvalidType($item,@([VSObject],[VSHashtable],[psobject],[IDictionary])) } elseif ($null -eq $this._outputs) { $this._outputs = [ordered]@{} } if ($null -eq $item.LogicalId) { throw [VSError]::MissingLogicalId($item, 'Output') } elseif ($item -is [VSOutput] -and $this._outputs.Contains($item.LogicalId)) { throw [VSError]::DuplicateLogicalId($item, 'Output') } elseif ($item -is [VSOutput]) { $cleaned = [ordered]@{} $safeList = [VSOutput]::new().PSObject.Properties.Name $item.ToOrderedDictionary().GetEnumerator() | ForEach-Object { if ($_.Key -in $safeList) { $cleaned[$_.Key] = $_.Value } } $this._outputs[$item.LogicalId] = $cleaned } elseif ($item -is [FnTransform]) { if ($this._outputs.Contains($item.LogicalId)) { $this._outputs[$item.LogicalId] += $item.ToOrderedDictionary() } else { $this._outputs[$item.LogicalId] = $item.ToOrderedDictionary() } } elseif ($cast = $item -as [FnTransform]) { if ($this._outputs.Contains($item.LogicalId)) { $this._outputs[$item.LogicalId] += $cast.ToOrderedDictionary() } else { $this._outputs[$item.LogicalId] = $cast.ToOrderedDictionary() } } else { throw [VSError]::InvalidType($item, @([VSOutput], [FnTransform])) } $this._outputsOriginal += $item } [void] AddOutput([object[]] $items) { $items | ForEach-Object { $this.AddOutput($_) } } [void] AddParameter([object] $item) { if ($item.GetType() -in @([string],[int],[bool],[double],[long])) { throw [VSError]::InvalidType($item,@([VSObject],[VSHashtable],[psobject],[IDictionary])) } elseif ($null -eq $this._parameters) { $this._parameters = [ordered]@{} } if ($null -eq $item.LogicalId) { throw [VSError]::MissingLogicalId($item, 'Parameter') } elseif ($this._parameters.Contains($item.LogicalId)) { throw [VSError]::DuplicateLogicalId($item, 'Parameter') } else { $cleaned = [ordered]@{} $safeList = [VSParameter]::new().PSObject.Properties.Name $item.ToOrderedDictionary().GetEnumerator() | ForEach-Object { if ($_.Key -in $safeList) { $cleaned[$_.Key] = $_.Value } } $this._parameters[$item.LogicalId] = $cleaned $this._parametersOriginal += $item } } [void] AddParameter([object[]] $items) { $items | ForEach-Object { $this.AddParameter($_) } } [void] AddMetadata([object] $item) { if ($item.GetType() -in @([string],[int],[bool],[double],[long])) { throw [VSError]::InvalidType($item,@([VSObject],[VSHashtable],[psobject],[IDictionary])) } elseif ($null -eq $this._metadata) { $this._metadata = [ordered]@{} } if ($null -eq $item.LogicalId) { throw [VSError]::MissingLogicalId($item, 'Metadata') } elseif ($item -is [VSMetadata] -and $this._metadata.Contains($item.LogicalId)) { throw [VSError]::DuplicateLogicalId($item, 'Metadata') } elseif ($item -is [VSMetadata]) { $this._metadata[$item.LogicalId] = $item.Metadata } elseif ($item -is [FnTransform]) { $cleaned = [ordered]@{ Name = $item.Name Parameters = $item.Parameters } $this._metadata[$item.LogicalId] = $cleaned } elseif ($cast = $item -as [FnTransform]) { $cleaned = [ordered]@{ Name = $cast.Name Parameters = $cast.Parameters } $this._metadata[$cast.LogicalId] = $cleaned } else { throw [VSError]::InvalidType($item, @([VSMetadata], [FnTransform])) } $this._metadataOriginal += $item } [void] AddMetadata([object[]] $items) { $items | ForEach-Object { $this.AddParameter($_) } } [void] AddResource([object] $item) { if ($item.GetType() -in @([string],[int],[bool],[double],[long])) { throw [VSError]::InvalidType($item,@([VSObject],[VSHashtable],[psobject],[IDictionary])) } elseif ($null -eq $this._resources) { $this._resources = [ordered]@{} } if ($null -eq $item.LogicalId) { throw [VSError]::MissingLogicalId($item, 'Resource') } elseif ($item -is [VSResource] -and $this._resources.Contains($item.LogicalId)) { throw [VSError]::DuplicateLogicalId($item, 'Resource') } elseif ($item -is [VSResource]) { $cleaned = [ordered]@{} $safeList = [VSResource]::new().PSObject.Properties.Name $item.ToOrderedDictionary().GetEnumerator() | ForEach-Object { if ($item.LogicalId -eq 'Fn::Transform' -or $_.Key -in $safeList) { $cleaned[$_.Key] = $_.Value } } $this._resources[$item.LogicalId] = $cleaned if ($item.Type -match 'Serverless') { $this.AddTransform('Serverless') } } elseif ($item -is [FnTransform]) { if ($this._resources.Contains($item.LogicalId)) { $this._resources[$item.LogicalId] += $item.ToOrderedDictionary() } else { $this._resources[$item.LogicalId] = $item.ToOrderedDictionary() } } elseif ($cast = $item -as [FnTransform]) { if ($this._resources.Contains($item.LogicalId)) { $this._resources[$item.LogicalId] += $cast.ToOrderedDictionary() } else { $this._resources[$item.LogicalId] = $cast.ToOrderedDictionary() } } else { throw [VSError]::InvalidType($item, @([VSResource], [FnTransform])) } $this._resourcesOriginal += $item } [void] AddResource([object[]] $items) { $items | ForEach-Object { $this.AddResource($_) } } hidden [void] _addExtraAccessors() {} hidden [void] _addAccessors() { $this | Add-Member -Force -MemberType 'ScriptProperty' -Name 'AWSTemplateFormatVersion' -Value { $this._awsTemplateFormatVersion } -SecondValue { param([object] $value) if ($value -is [string]) { $this._awsTemplateFormatVersion = $value } elseif ($value -is [datetime]) { $this._awsTemplateFormatVersion = $value.ToString('yyyy-MM-dd') } } $this | Add-Member -Force -MemberType 'ScriptProperty' -Name 'Description' -Value { $this._description } -SecondValue { param([string] $value) $this._description = $value } $this | Add-Member -Force -MemberType 'ScriptProperty' -Name 'Transform' -Value { $this._transform } -SecondValue { param([object] $value) $this.AddTransform($value) } $this | Add-Member -Force -MemberType 'ScriptProperty' -Name 'Parameters' -Value { if ($MyInvocation.Line -match '\.Parameters') { $this._parametersOriginal } else { $this._parameters } } -SecondValue { param([object[]] $value) if ($null -eq $this._parameters) { $this._parameters = [ordered]@{} } $this.AddParameter($value) } $this | Add-Member -Force -MemberType 'ScriptProperty' -Name 'Conditions' -Value { if ($MyInvocation.Line -match '\.Conditions') { $this._conditionsOriginal } else { $this._conditions } } -SecondValue { param([object[]] $value) if ($null -eq $this._conditions) { $this._conditions = [ordered]@{} } $this.AddCondition($value) } $this | Add-Member -Force -MemberType 'ScriptProperty' -Name 'Metadata' -Value { if ($MyInvocation.Line -match '\.Metadata') { $this._metadataOriginal } else { $this._metadata } } -SecondValue { param([object[]] $value) if ($null -eq $this._metadata) { $this._metadata = [ordered]@{} } $this.AddMetadata($value) } $this | Add-Member -Force -MemberType 'ScriptProperty' -Name 'Mappings' -Value { if ($MyInvocation.Line -match '\.Mappings') { $this._mappingsOriginal } else { $this._mappings } } -SecondValue { param([object[]] $value) if ($null -eq $this._mappings) { $this._mappings = [ordered]@{} } $this.AddMapping($value) } $this | Add-Member -Force -MemberType 'ScriptProperty' -Name 'Resources' -Value { if ($MyInvocation.Line -match '\.Resources') { $this._resourcesOriginal } else { $this._resources } } -SecondValue { param([object[]] $value) if ($null -eq $this._resources) { $this._resources = [ordered]@{} } $this.AddResource($value) } $this | Add-Member -Force -MemberType 'ScriptProperty' -Name 'Outputs' -Value { if ($MyInvocation.Line -match '\.Outputs') { $this._outputsOriginal } else { $this._outputs } } -SecondValue { param([object[]] $value) if ($null -eq $this._outputs) { $this._outputs = [ordered]@{} } $this.AddOutput($value) } $this._addExtraAccessors() } VSTemplate() : base() {} VSTemplate([IDictionary] $props) : base($props) {} VSTemplate([psobject] $props) : base($props) {} VSTemplate([string] $pathOrBodyOrUrl) { Write-Debug "Building template from $pathOrBodyOrUrl" $templateBody = if (Test-Path $pathOrBodyOrUrl) { [System.IO.File]::ReadAllText((Resolve-Path $pathOrBodyOrUrl)) } elseif ($pathOrBodyOrUrl -as [Uri]) { (Invoke-WebRequest -Uri $pathOrBodyOrUrl).Content } else { $pathOrBodyOrUrl } $baseObj = if ($templateBody -match "Resources:") { if (Get-Command cfn-flip -ErrorAction SilentlyContinue) { ConvertFrom-Json -InputObject (($templateBody | cfn-flip) -join [Environment]::NewLine) } else { $ht = ConvertFrom-Yaml -Yaml $templateBody -Ordered [PSCustomObject]$ht } } else { ConvertFrom-Json -InputObject $templateBody } #<# foreach ($section in $baseObj.PSObject.Properties) { Write-Debug "Importing section: $($section.Name)" switch -Regex ($section.Name) { '(Outputs|Parameters|Resources|Metadata|Mappings|Conditions|Globals)' { $this."$($section.Name)" = @() $sectionContents = $baseObj."$($section.Name)" $list = if ($sectionContents -is [IDictionary]) { ([PSCustomObject]$sectionContents).PSObject.Properties } else { $sectionContents.PSObject.Properties } foreach ($item in $list) { Write-Debug "[$($section.Name)] Parsing item: $($item.Name)" $newItem = $item.Value if ($null -eq $newItem.LogicalId) { $newItem | Add-Member -Force -MemberType NoteProperty -Name LogicalId -Value $item.Name -PassThru } switch ($section.Name) { Outputs { if ($item.Name -eq 'Fn::Transform') { Write-Debug "[$($section.Name)] [$($item.Name)] Adding as FnTransform" $this.AddOutput(($newItem -as [FnTransform])) } else { Write-Debug "[$($section.Name)] [$($item.Name)] Adding as VSOutput" $this.AddOutput(($newItem -as [VSOutput])) } } Parameters { if ($item.Name -eq 'Fn::Transform') { Write-Debug "[$($section.Name)] [$($item.Name)] Adding as FnTransform" $this.AddParameter(($newItem -as [FnTransform])) } else { Write-Debug "[$($section.Name)] [$($item.Name)] Adding as VSParameter" $this.AddParameter(($newItem -as [VSParameter])) } } Mappings { if ($item.Name -eq 'Fn::Transform') { Write-Debug "[$($section.Name)] [$($item.Name)] Adding as FnTransform" $this.AddMapping(($newItem -as [FnTransform])) } else { Write-Debug "[$($section.Name)] [$($item.Name)] Adding as VSMapping" $this.AddMapping(([VSMapping]@{LogicalId = $item.Name; Map = $item.Value})) } } Metadata { if ($item.Name -eq 'Fn::Transform') { Write-Debug "[$($section.Name)] [$($item.Name)] Adding as FnTransform" $this.AddMetadata(($newItem -as [FnTransform])) } else { Write-Debug "[$($section.Name)] [$($item.Name)] Adding as VSMetadata" $this.AddMetadata(([VSMetadata]@{LogicalId = $item.Name; Metadata = $item.Value})) } } Conditions { if ($item.Name -eq 'Fn::Transform') { Write-Debug "[$($section.Name)] [$($item.Name)] Adding as FnTransform" $this.AddCondition(($newItem -as [FnTransform])) } else { Write-Debug "[$($section.Name)] [$($item.Name)] Adding as VSCondition" $this.AddCondition(([VSCondition]@{LogicalId = $item.Name; Condition = $item.Value})) } } Resources { if ($item.Name -eq 'Fn::Transform') { Write-Debug "[$($section.Name)] [$($item.Name)] Adding as FnTransform" $this.AddResource(($newItem -as [FnTransform])) } else { $className = $newItem.Type -replace '^AWS::' -replace '\W' if ($className -match 'Serverless') { $className = $className -replace 'Serverless','SAM' } Write-Debug "[$($section.Name)] [$($item.Name)] Checking for type: $className" $resource = if ($t = $className -as [type]) { Write-Debug "[$($section.Name)] [$($item.Name)] Adding as $className" $newItem -as $t } else { Write-Debug "[$($section.Name)] [$($item.Name)] Adding as VSResource" $newItem -as [VSResource] } $this.AddResource(($resource)) } } } Write-Debug "[$($section.Name)] [$($item.Name)] Added item to VSTemplate" } } AWSTemplateFormatVersion { $this.AWSTemplateFormatVersion = '2010-09-09' } default { $this."$($section.Name)" = $baseObj."$($section.Name)" } } Write-Debug "[$($section.Name)] Added section to VSTemplate" } #> } } Write-Verbose "Importing class 'ConRef'" class ConRef : ConditionFunction { hidden [string] $_vsFunctionName = 'Add-FnRef' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-conditions.html#intrinsic-function-reference-conditions-or' hidden [string] $_topLevelKey = 'Condition' [string] $Condition hidden [void] _addAccessors() { $this | Add-Member -Force -MemberType ScriptProperty -Name Condition -Value { $this[$this._topLevelKey] } -SecondValue { param([string] $conditionName) $this[$this._topLevelKey] = $conditionName } } ConRef() {} ConRef([string] $condition) { $this[$this._topLevelKey] = $condition } ConRef([VSCondition] $vsCondition) { $this[$this._topLevelKey] = $vsCondition.ToString() } } Write-Verbose "Importing class 'ConAnd'" class ConAnd : ConditionFunction { hidden [string] $_vsFunctionName = 'Add-ConAnd' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-conditions.html#intrinsic-function-reference-conditions-and' hidden [string] $_topLevelKey = 'Fn::And' [ValidateCount(2,10)] [object[]] $Conditions hidden [void] _addAccessors() { $this | Add-Member -Force -MemberType ScriptProperty -Name Conditions -Value { $this[$this._topLevelKey] } -SecondValue { param([ValidateType(([ConditionFunction]))] [object[]] $value) $this[$this._topLevelKey] = $value } } hidden [void] _validateInput([object[]] $inputData) { if ($inputData.Count -lt 2 -or $inputData.Count -gt 10) { throw [VSError]::InvalidArgument($inputData, "$($inputData.Count) condition(s) provided! The minimum number of conditions that you can include is 2, and the maximum is 10.") } } ConAnd() : base() {} ConAnd([object[]] $conditions) : base($conditions) {} } Write-Verbose "Importing class 'ConEquals'" class ConEquals : ConditionFunction { hidden [string] $_vsFunctionName = 'Add-ConEquals' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-conditions.html#intrinsic-function-reference-conditions-equals' hidden [string] $_topLevelKey = 'Fn::Equals' [object] $ValueOne [object] $ValueTwo hidden [void] _validateInput([object[]] $inputData) { if ($inputData.Count -ne 2) { throw [VSError]::InvalidArgument($inputData, "Total input item count when constructing a <$($this.GetType())> object needs to be 2. Count provided: $($inputData.Count)") } } hidden [void] _addAccessors() { $this | Add-Member -Force -MemberType ScriptProperty -Name ValueOne -Value { $this[$this._topLevelKey] | Select-Object -First 1 } -SecondValue { param([object] $value) $clean = if ($value | Get-Member -Name ToOrderedDictionary* -MemberType Method -ErrorAction SilentlyContinue) { $value.ToOrderedDictionary() } else { $value } if ($null -eq $this[$this._topLevelKey]) { $this[$this._topLevelKey] = @($clean) } else { if ($this[$this._topLevelKey].Count -ge 1) { $this[$this._topLevelKey][0] = $clean } else { $this[$this._topLevelKey] += $clean } } } $this | Add-Member -Force -MemberType ScriptProperty -Name ValueTwo -Value { $this[$this._topLevelKey] | Select-Object -First 1 } -SecondValue { param([object] $value) $clean = if ($value | Get-Member -Name ToOrderedDictionary* -MemberType Method -ErrorAction SilentlyContinue) { $value.ToOrderedDictionary() } else { $value } if ($null -eq $this[$this._topLevelKey]) { $this[$this._topLevelKey] = @($null,$clean) } else { if ($this[$this._topLevelKey].Count -gt 1) { $this[$this._topLevelKey][1] = $clean } else { $this[$this._topLevelKey] += $clean } } } } ConEquals() : base() {} ConEquals([object[]] $conditions) : base($conditions) {} ConEquals( [object] $value1, [object] $value2 ) { $this[$this._topLevelKey] = @($value1,$value2) } } Write-Verbose "Importing class 'ConIf'" class ConIf : ConditionFunction { hidden [string] $_vsFunctionName = 'Add-ConIf' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-conditions.html#intrinsic-function-reference-conditions-if' hidden [string] $_topLevelKey = 'Fn::If' hidden [type[]] $_validTypes = @( [string], [int], [bool], [IDictionary], [psobject], [VSObject], [VSHashtable] ) hidden [void] _validateInput([object[]] $inputData) { if ($inputData.Count -ne 3) { throw [VSError]::InvalidArgument($inputData, "Total input item count when constructing a <$($this.GetType())> object needs to be 3. Count provided: $($inputData.Count)") } elseif ($inputData[0] -isnot [string]) { throw [VSError]::InvalidArgument($inputData, "The first item provided when constructing a <$($this.GetType())> object needs to be a string. Type provided: $($inputData[0].GetType())") } } ConIf() : base() {} ConIf([object[]] $conditions) : base($conditions) {} ConIf( [string] $conditionName, [object] $valueIfTrue, [object] $valueIfFalse ) { $final = @($conditionName) foreach ($item in @($valueIfTrue,$valueIfFalse)) { if ($item | Get-Member -Name ToOrderedDictionary* -MemberType Method -ErrorAction SilentlyContinue) { $final += $item.ToOrderedDictionary() } else { $final += $item } } $this[$this._topLevelKey] = $final } } Write-Verbose "Importing class 'ConNot'" class ConNot : ConditionFunction { hidden [string] $_vsFunctionName = 'Add-ConNot' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-conditions.html#intrinsic-function-reference-conditions-not' hidden [string] $_topLevelKey = 'Fn::Not' [object[]] $Conditions hidden [void] _addAccessors() { $this | Add-Member -Force -MemberType ScriptProperty -Name Conditions -Value { $this[$this._topLevelKey] } -SecondValue { param([ValidateType(([ConditionFunction]))] [object[]] $value) $this[$this._topLevelKey] = $value } } ConNot() {} ConNot([object[]] $conditions) : base($conditions) {} } Write-Verbose "Importing class 'ConOr'" class ConOr : ConditionFunction { hidden [string] $_vsFunctionName = 'Add-ConOr' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-conditions.html#intrinsic-function-reference-conditions-or' hidden [string] $_topLevelKey = 'Fn::Or' [object[]] $Conditions hidden [void] _addAccessors() { $this | Add-Member -Force -MemberType ScriptProperty -Name Conditions -Value { $this[$this._topLevelKey] } -SecondValue { param([ValidateType(([ConditionFunction]))] [object[]] $value) $this[$this._topLevelKey] = $value } } ConOr() {} ConOr([object[]] $conditions) : base($conditions) {} } Write-Verbose "Importing class 'UserData'" class UserData : FnBase64 { hidden [string] $_vsFunctionName = 'Add-UserData' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/deploying.applications.html' static [object] Transform([UserData] $userData) { Write-Debug "Transforming UserData from [UserData]" return $userData['Fn::Base64'] } static [object] Transform([FnJoin] $fnJoin) { Write-Debug "Transforming UserData from [FnJoin]" return $fnJoin } static [object] Transform([FnBase64] $fnBase64) { Write-Debug "Transforming UserData from [FnBase64]" return $fnBase64['Fn::Base64'] } static [object] Transform([string] $userDataStringOrFilepath) { Write-Debug "Transforming UserData from [string]" return [UserData]::Transform($false, $false, @{}, $userDataStringOrFilepath) } static [object] Transform([string[]] $userDataStringOrFilepath) { Write-Debug "Transforming UserData from [string[]]" return [UserData]::Transform($false, $false, @{}, ($userDataStringOrFilepath -join [Environment]::NewLine)) } static [object] Transform([string] $userDataStringOrFilepath, [bool] $persist) { Write-Debug "Transforming UserData from [string] with Persist=$persist" return [UserData]::Transform($persist, $false, @{}, $userDataStringOrFilepath) } static [object] Transform([bool] $useJoin, [string] $userDataStringOrFilepath) { Write-Debug "Transforming UserData from [string] with UseJoin=$useJoin" return [UserData]::Transform($false, $useJoin, @{}, $userDataStringOrFilepath) } static [object] Transform([bool] $persist, [bool] $useJoin, [IDictionary] $replaceDict, [string] $userDataStringOrFilepath) { $final = @() $startTag = $null $endTag = $null $tag = $null if (Test-Path $userDataStringOrFilepath) { Write-Debug "Extracting script from file path: $userDataStringOrFilepath" $item = Get-Item $userDataStringOrFilepath $tag = switch -RegEx ($item.Extension) { '^\.ps1$' { "powershell" } '^\.(bat|cmd)$' { "script" } default { $null } } $fileContents = [File]::ReadAllLines($item.FullName) if ($tag -and ($fileContents -join [Environment]::NewLine) -notlike "<$($tag)>*") { if ($fileContents[0] -notlike "<$($tag)>`n*") { Write-Debug "Adding missing script tags: <$tag>" $final += "<$($tag)>" } } $final += $fileContents if ($tag -and ($fileContents -join [Environment]::NewLine) -notlike "*</$($tag)>*") { $final += "</$($tag)>" } if ($persist -and ($fileContents -join [Environment]::NewLine) -notlike "*<persist>true</persist>*") { Write-Debug "Adding missing script tags: <persist>" $final += "`n<persist>true</persist>" } } else { $final += $userDataStringOrFilepath } $replaceDict.GetEnumerator() | ForEach-Object { Write-Verbose "Replacing '$($_.Key)' with '$($_.Value)'" $final = $final.Replace($_.Key,$_.Value) } if ($null -ne $tag -and ($final -join [Environment]::NewLine) -notmatch "\<$tag\>") { $final.Insert(0,"<$($tag)>") | Out-Null $final += "</$($tag)>" } if ($useJoin) { return [FnJoin]::new([Environment]::NewLine,$final) } else { return ($final -join [Environment]::NewLine) } } UserData() : base() {} UserData([IDictionary] $props) : base($props) {} UserData([psobject] $props) : base($props) {} UserData([FnJoin] $fnJoin) { Write-Debug "Creating UserData from [FnJoin]" $this['Fn::Base64'] = $fnJoin } UserData([FnBase64] $fnBase64) { Write-Debug "Creating UserData from [FnBase64]" $this['Fn::Base64'] = $fnBase64['Fn::Base64'] } UserData([string] $userDataStringOrFilepath) { Write-Debug "Creating UserData from [string]" $this['Fn::Base64'] = [UserData]::Transform($userDataStringOrFilepath) } UserData([bool] $useJoin, [string] $userDataStringOrFilepath) { Write-Debug "Creating UserData from [string] with UseJoin=$useJoin" $this['Fn::Base64'] = [UserData]::Transform($useJoin,$userDataStringOrFilepath) } <# UserData([object] $object) { Write-Debug "Creating UserData from [object]" $this['Fn::Base64'] = [FnJoin]::new([Environment]::NewLine,$object) } #> UserData([object[]] $objects) { Write-Debug "Creating UserData from [object[]]" $final = @() $tag = $null foreach ($o in $objects) { if ($o -is [string] -and (Test-Path $o)) { Write-Debug "Extracting script from file path: $o" $item = Get-Item $o $tag = switch -RegEx ($item.Extension) { '^\.ps1$' { "powershell" } '^\.(bat|cmd)$' { "script" } default { $null } } [File]::ReadAllLines($item.FullName) | ForEach-Object { $final += $_ } } else { $final += $o } } if ($null -ne $tag -and ($final -join [Environment]::NewLine) -notmatch "\<$tag\>") { $final.Insert(0,"<$($tag)>") | Out-Null $final += "</$($tag)>" } $this['Fn::Base64'] = [FnJoin]::new([Environment]::NewLine,$final) } UserData([bool] $useJoin, [object[]] $objects) { Write-Debug "Creating UserData from [object[]] with UseJoin=$useJoin" if ($useJoin -or $null -ne ($objects | Where-Object {$_ -isnot [string]})) { $this['Fn::Base64'] = [FnJoin]::new([Environment]::NewLine,$objects) } else { Write-Debug "All objects are strings and UseJoin=$useJoin" $this['Fn::Base64'] = $objects -join [Environment]::NewLine } } UserData([UserData] $userData) { Write-Debug "Creating UserData from [UserData]" $this['Fn::Base64'] = $userData['Fn::Base64'] } } Write-Verbose "Importing class 'AutoScalingCreationPolicy'" class AutoScalingCreationPolicy : VSObject { hidden [string] $_vsFunctionName = 'Add-CreationPolicy' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-creationpolicy.html#cfn-attributes-creationpolicy-autoscalingcreationpolicy' hidden [object] $_minSuccessfulInstancesPercent [ValidateRange(0,100)] [int] $MinSuccessfulInstancesPercent hidden [void] _addAccessors() { $this | Add-Member -Force -MemberType ScriptProperty -Name MinSuccessfulInstancesPercent -Value { $this._minSuccessfulInstancesPercent } -SecondValue { param( [object] $minSuccessfulInstancesPercent ) if ( $minSuccessfulInstancesPercent -as [int] -and ( ($minSuccessfulInstancesPercent -as [int]) -gt 100 -or ($minSuccessfulInstancesPercent -as [int]) -lt 0 ) ) { $errorRecord = [VSError]::new( [ArgumentException]::new("MinSuccessfulInstancesPercent must be an integer between 0-100!"), 'InvalidMinSuccessfulInstancesPercent', [ErrorCategory]::InvalidArgument, $minSuccessfulInstancesPercent ) throw [VSError]::InsertError($errorRecord) } if ($cast = $minSuccessfulInstancesPercent -as [int]) { $this._minSuccessfulInstancesPercent = $cast } elseif ($value -is [IntrinsicFunction] -or $value -is [ConditionFunction]) { $this._minSuccessfulInstancesPercent = $minSuccessfulInstancesPercent } else { throw [VSError]::InvalidArgument($minSuccessfulInstancesPercent,"$($this.GetType()) - Invalid value for property MinSuccessfulInstancesPercent") } } } AutoScalingCreationPolicy() : base() {} AutoScalingCreationPolicy([IDictionary] $props) : base($props) {} AutoScalingCreationPolicy([psobject] $props) : base($props) {} } Write-Verbose "Importing class 'AutoScalingReplacingUpdate'" class AutoScalingReplacingUpdate : VSObject { hidden [string] $_vsFunctionName = 'Add-UpdatePolicy' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html#cfn-attributes-updatepolicy-replacingupdate' [nullable[bool]] $WillReplace = $null AutoScalingReplacingUpdate() : base() {} AutoScalingReplacingUpdate([bool] $willReplace) { $this.WillReplace = $willReplace } AutoScalingReplacingUpdate([IDictionary] $props) : base($props) {} AutoScalingReplacingUpdate([psobject] $props) : base($props) {} } Write-Verbose "Importing class 'AutoScalingRollingUpdate'" class AutoScalingRollingUpdate : VSObject { hidden [string] $_vsFunctionName = 'Add-UpdatePolicy' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html#cfn-attributes-updatepolicy-replacingupdate' hidden [object] $_maxBatchSize hidden [object] $_minInstancesInService hidden [object] $_minSuccessfulInstancesPercent hidden [object] $_pauseTime hidden [string[]] $_suspendProcesses = $null [int] $MaxBatchSize [int] $MinInstancesInService [int] $MinSuccessfulInstancesPercent [string] $PauseTime [AutoScalingProcess[]] $SuspendProcesses [nullable[bool]] $WaitOnResourceSignals = $null hidden [void] _addAccessors() { $this | Add-Member -Force -MemberType ScriptProperty -Name MaxBatchSize -Value { $this._maxBatchSize } -SecondValue { param([object] $value) if ($null -ne ($value -as [int])) { $this._maxBatchSize = $value -as [int] } elseif ($value -is [IntrinsicFunction] -or $value -is [ConditionFunction]) { $this._maxBatchSize = $value } else { throw [VSError]::InvalidArgument($value,"$($this.GetType()) - Invalid value for property MaxBatchSize") } } $this | Add-Member -Force -MemberType ScriptProperty -Name MinInstancesInService -Value { $this._minInstancesInService } -SecondValue { param([object] $value) if ($null -ne ($value -as [int])) { $this._minInstancesInService = $value -as [int] } elseif ($value -is [IntrinsicFunction] -or $value -is [ConditionFunction]) { $this._minInstancesInService = $value } else { throw [VSError]::InvalidArgument($value,"$($this.GetType()) - Invalid value for property MinInstancesInService") } } $this | Add-Member -Force -MemberType ScriptProperty -Name MinSuccessfulInstancesPercent -Value { $this._minSuccessfulInstancesPercent } -SecondValue { param([object] $value) if ($null -ne ($value -as [int])) { $this._minSuccessfulInstancesPercent = $value -as [int] } elseif ($value -is [IntrinsicFunction] -or $value -is [ConditionFunction]) { $this._minSuccessfulInstancesPercent = $value } else { throw [VSError]::InvalidArgument($value,"$($this.GetType()) - Invalid value for property MinSuccessfulInstancesPercent") } } $this | Add-Member -Force -MemberType ScriptProperty -Name PauseTime -Value { $this._pauseTime } -SecondValue { param( [ValidateType(([string], [TimeSpan], [IntrinsicFunction], [ConditionFunction]))] [object] $value ) if ($ts = $value -as [TimeSpan]) { $pt = 'P' if ($ts.Days) { $pt += ("{0}D" -f $ts.Days) } if ($ts.Hours + $ts.Minutes + $ts.Seconds) { $pt += 'T' if ($ts.Hours) { $pt += ("{0}H" -f $ts.Hours) } if ($ts.Minutes) { $pt += ("{0}M" -f $ts.Minutes) } if ($ts.Seconds) { $pt += ("{0}S" -f $ts.Seconds) } } $this._pauseTime = $pt } elseif ($value -is [string] -and $value -notmatch '^P((?<Years>[\d\.,]+)Y)?((?<Months>[\d\.,]+)M)?((?<Weeks>[\d\.,]+)W)?((?<Days>[\d\.,]+)D)?(?<Time>T((?<Hours>[\d\.,]+)H)?((?<Minutes>[\d\.,]+)M)?((?<Seconds>[\d\.,]+)S)?)?$') { $errorRecord = [VSError]::new( [ArgumentException]::new("Value '$value' is not a valid ISO8601 duration string!"), 'InvalidPauseTime', [ErrorCategory]::InvalidArgument, $value ) throw [VSError]::InsertError($errorRecord) } else { $this._pauseTime = $value } } $this | Add-Member -Force -MemberType ScriptProperty -Name SuspendProcesses -Value { $this._suspendProcesses } -SecondValue { param( [AutoScalingProcess[]] $value ) if ($null -eq $this._suspendProcesses) { $this._suspendProcesses = @() } foreach ($proc in $value) { $this._suspendProcesses += $proc.ToString() } } } AutoScalingRollingUpdate() : base() {} AutoScalingRollingUpdate([IDictionary] $props) : base($props) {} AutoScalingRollingUpdate([psobject] $props) : base($props) {} } Write-Verbose "Importing class 'AutoScalingScheduledAction'" class AutoScalingScheduledAction : VSObject { hidden [string] $_vsFunctionName = 'Add-UpdatePolicy' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html#cfn-attributes-updatepolicy-scheduledactions' [nullable[bool]] $IgnoreUnmodifiedGroupSizeProperties = $null AutoScalingScheduledAction() : base() {} AutoScalingScheduledAction([bool] $ignoreUnmodifiedGroupSizeProperties) { $this.IgnoreUnmodifiedGroupSizeProperties = $ignoreUnmodifiedGroupSizeProperties } AutoScalingScheduledAction([IDictionary] $props) : base($props) {} AutoScalingScheduledAction([psobject] $props) : base($props) {} } Write-Verbose "Importing class 'CodeDeployLambdaAliasUpdate'" class CodeDeployLambdaAliasUpdate : VSObject { hidden [string] $_vsFunctionName = 'Add-UpdatePolicy' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html#cfn-attributes-updatepolicy-codedeploylambdaaliasupdate' hidden [object] $_afterAllowTrafficHook hidden [object] $_applicationName hidden [object] $_beforeAllowTrafficHook hidden [object] $_deploymentGroupName [string] $AfterAllowTrafficHook [string] $ApplicationName [string] $BeforeAllowTrafficHook [string] $DeploymentGroupName hidden [void] _addAccessors() { $this | Add-Member -Force -MemberType ScriptProperty -Name AfterAllowTrafficHook -Value { $this._afterAllowTrafficHook } -SecondValue { param( [ValidateType(([string], [IntrinsicFunction], [ConditionFunction]))] [object] $value ) $this._afterAllowTrafficHook = $value } $this | Add-Member -Force -MemberType ScriptProperty -Name ApplicationName -Value { $this._applicationName } -SecondValue { param( [ValidateType(([string], [IntrinsicFunction], [ConditionFunction]))] [object] $value ) $this._applicationName = $value } $this | Add-Member -Force -MemberType ScriptProperty -Name BeforeAllowTrafficHook -Value { $this._beforeAllowTrafficHook } -SecondValue { param( [ValidateType(([string], [IntrinsicFunction], [ConditionFunction]))] [object] $value ) $this._beforeAllowTrafficHook = $value } $this | Add-Member -Force -MemberType ScriptProperty -Name DeploymentGroupName -Value { $this._deploymentGroupName } -SecondValue { param( [ValidateType(([string], [IntrinsicFunction], [ConditionFunction]))] [object] $value ) $this._deploymentGroupName = $value } } CodeDeployLambdaAliasUpdate() : base() {} CodeDeployLambdaAliasUpdate([IDictionary] $props) : base($props) {} CodeDeployLambdaAliasUpdate([psobject] $props) : base($props) {} } Write-Verbose "Importing class 'ResourceSignal'" class ResourceSignal : VSObject { hidden [string] $_vsFunctionName = 'Add-CreationPolicy' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-creationpolicy.html#cfn-attributes-creationpolicy-resourcesignal' hidden [object] $_count hidden [object] $_timeout [int] $Count [string] $Timeout hidden [void] _addAccessors() { $this | Add-Member -Force -MemberType ScriptProperty -Name Count -Value { $this._count } -SecondValue { param([object] $count) if ($cast = $count -as [int]) { $this._count = $cast } elseif ($count -is [IntrinsicFunction] -or $count -is [ConditionFunction]) { $this._count = $count } else { throw [VSError]::InvalidArgument($count,"Count must be an integer or a Condition or Intrinsic function.") } } $this | Add-Member -Force -MemberType ScriptProperty -Name Timeout -Value { $this._timeout } -SecondValue { param([object] $timeout) if ($timeout -is [string]) { try { # Check if it's a valid ISO8601 duration string $null = [XmlConvert]::ToTimeSpan($timeOut) $this._timeout = $timeout } catch { throw [VSError]::InvalidArgument($timeout,"Timeout must be a valid ISO8601 duration string or an Intrinsic or Condition Function object!") } } elseif ($count -is [IntrinsicFunction] -or $count -is [ConditionFunction]) { $this._timeout = $timeout } else { throw [VSError]::InvalidArgument($timeout,"Timeout must be a valid ISO8601 duration string or an Intrinsic or Condition Function object!") } } } ResourceSignal() : base() {} ResourceSignal([IDictionary] $props) : base($props) {} ResourceSignal([psobject] $props) : base($props) {} } Write-Verbose "Importing class 'CreationPolicy'" class CreationPolicy : VSObject { hidden [string] $_vsFunctionName = 'Add-CreationPolicy' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-creationpolicy.html' [AutoScalingCreationPolicy] $AutoScalingCreationPolicy [ResourceSignal] $ResourceSignal CreationPolicy() : base() {} CreationPolicy([IDictionary] $props) : base($props) {} CreationPolicy([psobject] $props) : base($props) {} } Write-Verbose "Importing class 'UpdatePolicy'" class UpdatePolicy : VSObject { hidden [string] $_vsFunctionName = 'Add-UpdatePolicy' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html' [AutoScalingReplacingUpdate] $AutoScalingReplacingUpdate [AutoScalingRollingUpdate] $AutoScalingRollingUpdate [AutoScalingScheduledAction] $AutoScalingScheduledAction [CodeDeployLambdaAliasUpdate] $CodeDeployLambdaAliasUpdate [nullable[bool]] $EnableVersionUpgrade = $null [nullable[bool]] $UseOnlineResharding = $null UpdatePolicy() : base() {} UpdatePolicy([IDictionary] $props) : base($props) {} UpdatePolicy([psobject] $props) : base($props) {} } Write-Verbose "Importing class 'Export'" class Export : VSObject { hidden [string] $_vsFunctionName = 'New-VaporOutput' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html' hidden [object] $_name [string] $Name [string] ToString() { return $this._name.ToString() } hidden [void] _addAccessors() { $this | Add-Member -Force -MemberType 'ScriptProperty' -Name 'Name' -Value { $this._name } -SecondValue { param([ValidateType(([string], [IntrinsicFunction]))] [object] $value) $this._name = $value } } Export([object] $name) { $this._addAccessors() $this.Name = $name } Export([string] $name) { $this._addAccessors() $this.Name = $name } Export() : base() {} Export([IDictionary] $props) : base($props) {} Export([psobject] $props) : base($props) {} } Write-Verbose "Importing class 'VSCondition'" class VSCondition : VSHashtable { hidden [string] $_vsFunctionName = 'New-VaporCondition' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html' [ValidateLogicalId()] [string] $LogicalId [ConditionFunction] $Condition [string] ToString() { return $this.LogicalId } hidden [void] _addAccessors() { $this | Add-Member -Force -MemberType ScriptProperty -Name 'Condition' -Value { $this.ToOrderedDictionary() } -SecondValue { param([object] $value) if ($value -isnot [IDictionary] -and $value -isnot [psobject]) { throw [VSError]::InvalidType($value,@([IDictionary],[psobject])) } if ($value -is [IDictionary]) { $value.GetEnumerator() | ForEach-Object { $this[$_.Key] = $_.Value } } elseif ($value -is [psobject]) { $value.PSObject.Properties | ForEach-Object { $this[$_.Name] = $_.Value } } } } VSCondition() : base() {} VSCondition([IDictionary] $props) { $mainKey = $props.Keys | Select-Object -First 1 $mainValue = $props.Values | Select-Object -First 1 if ( $null -ne $props.LogicalId -and $null -ne $props.Condition -and ( $props.Condition -is [ConditionFunction] -or $null -ne $props.Condition.'Fn::And' -or $null -ne $props.Condition.'Fn::Equals' -or $null -ne $props.Condition.'Fn::If' -or $null -ne $props.Condition.'Fn::Not' -or $null -ne $props.Condition.'Fn::Or' ) ) { Write-Debug "Creating VSCondition from correctly formed IDictionary" $this.LogicalId = $props.LogicalId $this.Condition = $props.Condition } elseif ( $props.Keys.Count -eq 1 -and ( $mainValue -is [ConditionFunction] -or $null -ne $mainValue.'Fn::And' -or $null -ne $mainValue.'Fn::Equals' -or $null -ne $mainValue.'Fn::If' -or $null -ne $mainValue.'Fn::Not' -or $null -ne $mainValue.'Fn::Or' ) ) { Write-Debug "Creating VSCondition from raw IDictionary" $this.LogicalId = $mainKey $this.Condition = $mainValue } else { throw [VSError]::InvalidArgument($props,"Input IDictionary did not match expected contents, unable to construct a VSCondition object.") } } VSCondition([psobject] $props) { $mainKey = $props.PSObject.Properties.Name | Select-Object -First 1 $mainValue = $props.PSObject.Properties.Value | Select-Object -First 1 if ( $null -ne $props.LogicalId -and $null -ne $props.Condition -and ( $props.Condition -is [ConditionFunction] -or $null -ne $props.Condition.'Fn::And' -or $null -ne $props.Condition.'Fn::Equals' -or $null -ne $props.Condition.'Fn::If' -or $null -ne $props.Condition.'Fn::Not' -or $null -ne $props.Condition.'Fn::Or' ) ) { Write-Debug "Creating VSCondition from correctly formed PSObject" $this.LogicalId = $props.LogicalId $this.Condition = $props.Condition } elseif ( $props.PSObject.Properties.Name.Count -eq 1 -and ( $mainValue -is [ConditionFunction] -or $null -ne $mainValue.'Fn::And' -or $null -ne $mainValue.'Fn::Equals' -or $null -ne $mainValue.'Fn::If' -or $null -ne $mainValue.'Fn::Not' -or $null -ne $mainValue.'Fn::Or' ) ) { Write-Debug "Creating VSCondition from raw PSObject" $this.LogicalId = $mainKey $this.Condition = $mainValue } else { throw [VSError]::InvalidArgument($props,"Input PSObject did not match expected contents, unable to construct a VSCondition object.") } } } Write-Verbose "Importing class 'VSMapping'" class VSMapping : VSLogicalObject { hidden [string] $_vsFunctionName = 'New-VaporMapping' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/mappings-section-structure.html' hidden [object] $_map [IDictionary] $Map hidden [void] _addAccessors() { $this | Add-Member -Force -MemberType ScriptProperty -Name 'Map' -Value { $this._map } -SecondValue { param([object] $map) $ht = [ordered]@{} if ($map -is [psobject]) { $map.PSObject.Properties | ForEach-Object { if ($_.Name -notmatch 'LogicalId' -and $_.Value -isnot [IDictionary] -and $_.Value -isnot [psobject]) { throw [VSError]::InvalidMap($map, $_.Name, $_.Value) } if ($_.Name -eq 'LogicalId') { $this.LogicalId = $_.Value } else { $ht[$_.Name] = $_.Value } } } elseif ($map -is [IDictionary]) { $map.GetEnumerator() | ForEach-Object { if ($_.Key -notmatch 'LogicalId' -and $_.Value -isnot [IDictionary] -and $_.Value -isnot [psobject]) { throw [VSError]::InvalidMap($map, $_.Key, $_.Value) } if ($_.Key -eq 'LogicalId') { $this.LogicalId = $_.Value } else { $ht[$_.Key] = $_.Value } } } else { throw [VSError]::InvalidArgument($map,"The Map value must be an IDictionary or PSObject!") } $this._map = $ht } } VSMapping() : base() {} VSMapping([IDictionary] $props) : base($props) {} VSMapping([psobject] $props) : base($props) {} } Write-Verbose "Importing class 'VSMetadata'" class VSMetadata : VSHashtable { hidden [string] $_vsFunctionName = 'New-VaporMetadata' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/metadata-section-structure.html' [ValidateLogicalId()] [string] $LogicalId [object] $Metadata [string] ToString() { return $this.LogicalId } hidden [void] _addAccessors() { $this | Add-Member -Force -MemberType ScriptProperty -Name 'Metadata' -Value { $this.ToOrderedDictionary() } -SecondValue { param([object] $value) if ($value -isnot [IDictionary] -and $value -isnot [psobject]) { throw [VSError]::InvalidType($value,@([IDictionary],[psobject])) } if ($value -is [IDictionary]) { $value.GetEnumerator() | ForEach-Object { $this[$_.Key] = $_.Value } } elseif ($value -is [psobject]) { $value.PSObject.Properties | ForEach-Object { $this[$_.Name] = $_.Value } } } } VSMetadata() : base() {} VSMetadata([IDictionary] $props) { $mainKey = $props.Keys | Select-Object -First 1 $mainValue = $props.Values | Select-Object -First 1 if ( $null -ne $props.LogicalId -and $null -ne $props.Metadata ) { Write-Debug "Creating VSMetadata from correctly formed IDictionary" $this.LogicalId = $props.LogicalId $this.Metadata = $props.Metadata } elseif ( $props.Keys.Count -eq 1 ) { Write-Debug "Creating VSMetadata from raw IDictionary" $this.LogicalId = $mainKey $this.Metadata = $mainValue } else { throw [VSError]::InvalidArgument($props,"Input IDictionary did not match expected contents, unable to construct a VSMetadata object.") } } VSMetadata([psobject] $props) { $mainKey = $props.PSObject.Properties.Name | Select-Object -First 1 $mainValue = $props.PSObject.Properties.Value | Select-Object -First 1 if ( $null -ne $props.LogicalId -and $null -ne $props.Metadata ) { Write-Debug "Creating VSMetadata from correctly formed PSObject" $this.LogicalId = $props.LogicalId $this.Metadata = $props.Metadata } elseif ( $props.PSObject.Properties.Name.Count -eq 1 ) { Write-Debug "Creating VSMetadata from raw PSObject" $this.LogicalId = $mainKey $this.Metadata = $mainValue } else { throw [VSError]::InvalidArgument($props,"Input PSObject did not match expected contents, unable to construct a VSMetadata object.") } } } Write-Verbose "Importing class 'VSOutput'" class VSOutput : VSLogicalObject { hidden [string] $_vsFunctionName = 'New-VaporOutput' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html' hidden [object] $_value hidden [object] $_condition [string] $Condition [string] $Description [string] $Value [Export] $Export hidden [void] _addAccessors() { $this | Add-Member -Force -MemberType 'ScriptProperty' -Name 'Value' -Value { $this._value } -SecondValue { param([ValidateType(([string], [int], [bool], [hashtable], [psobject], [IntrinsicFunction], [ConditionFunction]))] [object] $value) $this._value = $value } $this | Add-Member -Force -MemberType 'ScriptProperty' -Name 'Condition' -Value { $this._condition } -SecondValue { param([ValidateType(([string], [hashtable], [psobject], [ConRef]))] [object] $value) $this._condition = if ($value -is [ConRef]) { $value.Condition } else { $value } } } VSOutput() : base() {} VSOutput([IDictionary] $props) : base($props) {} VSOutput([psobject] $props) : base($props) {} } Write-Verbose "Importing class 'VSParameter'" class VSParameter : VSLogicalObject { hidden [string] $_vsFunctionName = 'New-VaporParameter' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html' [string] $AllowedPattern [string[]] $AllowedValues [string] $ConstraintDescription [string] $Default [ValidateLength(0,4000)] [string] $Description [nullable[int]] $MaxLength = $null [nullable[int]] $MaxValue = $null [nullable[int]] $MinLength = $null [nullable[int]] $MinValue = $null [nullable[bool]] $NoEcho = $null [ValidateSet("String","Number","List<Number>","CommaDelimitedList","AWS::EC2::AvailabilityZone::Name","AWS::EC2::Image::Id","AWS::EC2::Instance::Id","AWS::EC2::KeyPair::KeyName","AWS::EC2::SecurityGroup::GroupName","AWS::EC2::SecurityGroup::Id","AWS::EC2::Subnet::Id","AWS::EC2::Volume::Id","AWS::EC2::VPC::Id","AWS::Route53::HostedZone::Id","List<AWS::EC2::AvailabilityZone::Name>","List<AWS::EC2::Image::Id>","List<AWS::EC2::Instance::Id>","List<AWS::EC2::SecurityGroup::GroupName>","List<AWS::EC2::SecurityGroup::Id>","List<AWS::EC2::Subnet::Id>","List<AWS::EC2::Volume::Id>","List<AWS::EC2::VPC::Id>","List<AWS::Route53::HostedZone::Id>")] [string] $Type VSParameter() : base() {} VSParameter([IDictionary] $props) : base($props) {} VSParameter([psobject] $props) : base($props) {} } Write-Verbose "Importing class 'VSResourceProperty'" class VSResourceProperty : VSObject { hidden [string] $_vsFunctionName = 'New-VaporResource' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html' VSResourceProperty() : base() {} VSResourceProperty([IDictionary] $props) : base($props) {} VSResourceProperty([psobject] $props) : base($props) {} } Write-Verbose "Importing class 'VSResource'" class VSResource : VSLogicalObject { hidden [string] $_vsFunctionName = 'New-VaporResource' hidden [string] $_awsDocumentation = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html' hidden [string[]] $_attributes = @() hidden [object] $_deletionPolicy hidden [object] $_updateReplacePolicy [string] $Condition [CreationPolicy] $CreationPolicy [string[]] $DependsOn [TransformDeletionPolicy()] [DeletionPolicy] $DeletionPolicy [VSJson] $Metadata [VSHashtable] $Properties = [VSHashtable]::new() [string] $Type [UpdatePolicy] $UpdatePolicy [TransformDeletionPolicy()] [UpdateReplacePolicy] $UpdateReplacePolicy static [object] FormatDeletionPolicy([object] $policy) { if ($policy -is [string]) { return (Get-Culture).TextInfo.ToTitleCase($policy.ToString().ToLower()) } else { return $policy } } [FnRef] Ref() { return [FnRef]::new($this.LogicalId) } [FnGetAtt] GetAtt() { if ($this._attributes.Count -ne 1) { $message = if ($this._attributes.Count -eq 0) { "There are 0 attributes available for this resource! Please use <FnRef> instead." } else { "There are $($this._attributes.Count) attributes available for this resource! Please specify the attribute you would like to use for <FnGetAtt> instead. Valid attributes for a $($this.GetType().FullName) resource: $($this._attributes -join ', ')" } $errorRecord = [VSError]::new( [ArgumentException]::new($message), 'InvalidAttribute', [ErrorCategory]::InvalidArgument, $this._attributes ) throw [VSError]::InsertError($errorRecord) } return [FnGetAtt]::new($this.LogicalId, ($this._attributes | Select-Object -First 1)) } [FnGetAtt] GetAtt([string] $attributeName) { if ($attributeName -notin $this._attributes) { $message = if ($this._attributes.Count -eq 0) { "There are 0 attributes available for this resource! Please use <FnRef> instead." } else { "$attributeName is not a valid attribute for this resource to return via <FnGetAtt>. Valid attributes for a $($this.GetType().FullName) resource: $($this._attributes -join ', ')" } $errorRecord = [VSError]::new( [ArgumentException]::new($message), 'InvalidAttribute', [ErrorCategory]::InvalidArgument, $attributeName ) throw [VSError]::InsertError($errorRecord) } return [FnGetAtt]::new($this.LogicalId, $attributeName) } hidden [void] _addBaseAccessors() { $this | Add-Member -Force -MemberType ScriptProperty -Name DeletionPolicy -Value { $this::FormatDeletionPolicy($this._deletionPolicy) } -SecondValue { param( [ValidateType(([string], [IntrinsicFunction], [DeletionPolicy]))] [object] $deletionPolicy ) $this._deletionPolicy = $deletionPolicy } $this | Add-Member -Force -MemberType ScriptProperty -Name UpdateReplacePolicy -Value { $this::FormatDeletionPolicy($this._updateReplacePolicy) } -SecondValue { param( [ValidateType(([string], [IntrinsicFunction], [UpdateReplacePolicy]))] [object] $updateReplacePolicy ) $this._updateReplacePolicy = $updateReplacePolicy } } VSResource() : base() {} VSResource([IDictionary] $props) : base($props) {} VSResource([psobject] $props) : base($props) {} } |