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.Net 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 'ClassHelpScope'" enum ClassHelpScope { Full Detailed Online Examples } Write-Verbose "Importing class 'DeletionPolicy'" enum DeletionPolicy { Delete Retain Snapshot } Write-Verbose "Importing class 'LoggingLevel'" enum LoggingLevel { OFF ERROR INFO } Write-Verbose "Importing class 'PSCommonParameters'" enum PSCommonParameters { Verbose Debug ErrorAction WarningAction InformationAction ErrorVariable WarningVariable InformationVariable OutVariable OutBuffer PipelineVariable } Write-Verbose "Importing class 'UpdateReplacePolicy'" enum UpdateReplacePolicy { Delete Retain Snapshot } Write-Verbose "Importing class 'VaporShellServiceModule'" enum VaporShellServiceModule { AccessAnalyzer ACMPCA Alexa AmazonMQ Amplify ApiGateway ApiGatewayV2 AppConfig AppFlow AppIntegrations ApplicationAutoScaling ApplicationInsights AppMesh AppRunner AppStream AppSync APS Athena AuditManager AutoScaling AutoScalingPlans Backup Batch Budgets Cassandra CE CertificateManager Chatbot Cloud9 CloudFormation CloudFront CloudTrail CloudWatch CodeArtifact CodeBuild CodeCommit CodeDeploy CodeGuruProfiler CodeGuruReviewer CodePipeline CodeStar CodeStarConnections CodeStarNotifications Cognito Config Connect CUR CustomerProfiles DataBrew DataPipeline DataSync DAX Detective DeviceFarm DevOpsGuru DirectoryService DLM DMS DocDB DynamoDB EC2 ECR ECS EFS EKS ElastiCache ElasticBeanstalk ElasticLoadBalancing ElasticLoadBalancingV2 Elasticsearch EMR EMRContainers Events EventSchemas FinSpace FIS FMS FraudDetector FSx GameLift GlobalAccelerator Glue Greengrass GreengrassV2 GroundStation GuardDuty HealthLake IAM ImageBuilder Inspector IoT IoT1Click IoTAnalytics IoTCoreDeviceAdvisor IoTEvents IoTFleetHub IoTSiteWise IoTThingsGraph IoTWireless IVS Kendra Kinesis KinesisAnalytics KinesisAnalyticsV2 KinesisFirehose KMS LakeFormation Lambda LicenseManager Lightsail Location Logs LookoutEquipment LookoutMetrics LookoutVision Macie ManagedBlockchain MediaConnect MediaConvert MediaLive MediaPackage MediaStore MemoryDB MSK MWAA Neptune NetworkFirewall NetworkManager NimbleStudio OpenSearchService OpsWorks OpsWorksCM Panorama Pinpoint PinpointEmail QLDB QuickSight RAM RDS Redshift Rekognition ResourceGroups RoboMaker Route53 Route53RecoveryControl Route53RecoveryReadiness Route53Resolver S3 S3ObjectLambda S3Outposts SageMaker SAM SDB SecretsManager SecurityHub ServiceCatalog ServiceCatalogAppRegistry ServiceDiscovery SES Signer SNS SQS SSM SSMContacts SSMIncidents SSO StepFunctions Synthetics Timestream Transfer WAF WAFRegional WAFv2 Wisdom WorkSpaces XRay } 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[]] $_topLevelProperties = @() hidden [void] _addAccessors() {} [object] Help() { return $this.Help([ClassHelpScope]::Full) } [object] Help([ClassHelpScope] $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 ( $null -eq $this._topLevelProperties -or $this._topLevelProperties.Count -eq 0 -or $key -in $this._topLevelProperties ) ) ) -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 [PSCommonParameters].GetEnumValues())) { 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 [PSCommonParameters].GetEnumValues())) { 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[]] $_topLevelProperties = @() hidden [void] _addAccessors() {} [object] Help() { return $this.Help([ClassHelpScope]::Full) } [object] Help([ClassHelpScope] $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 ( $null -eq $this._topLevelProperties -or $this._topLevelProperties.Count -eq 0 -or $key -in $this._topLevelProperties ) ) ) -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 [PSCommonParameters].GetEnumValues())) { 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 [PSCommonParameters].GetEnumValues())) { 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 [string] Decode([string] $stringOrFilepath) { return [WebUtility]::UrlDecode($stringOrFilepath) } static [Specialized.OrderedDictionary] TransformToDict([string] $stringOrFilepath) { $dictionary = [ordered]@{} if (Test-Path $stringOrFilepath) { $resPath = Resolve-Path $stringOrFilepath $stringOrFilepath = [File]::ReadAllText($resPath) } if ($stringOrFilepath.Trim() -match '^%\d\w') { Write-Debug "String appears to be URL encoded, decoding" $stringOrFilepath = [VSJson]::Decode($stringOrFilepath) } try { $jsonConvert = @{ InputObject = $stringOrFilepath ErrorAction = 'Stop' } if ($Global:PSVersionTable.PSVersion.Major -ge 7) { $jsonConvert['Depth'] = 20 } $final = ConvertFrom-Json @jsonConvert $final.PSObject.Properties | ForEach-Object { Write-Debug "Adding key '$($_.Name)' with value '$($_.Value)' to VSJson" $dictionary[$_.Name] = $_.Value } } catch { throw [VSError]::InvalidJsonInput($stringOrFilepath) } Write-Debug "Resulting dict: $($dictionary | ConvertTo-Json -Depth 10))" return $dictionary } static [VSJson] Transform([string] $stringOrFilepath) { $vsJson = [VSJson]::new() $stringOrFilepath = [VSJson]::Decode($stringOrFilepath) $dictionary = [VSJson]::TransformToDict($stringOrFilepath) $dictionary.GetEnumerator() | ForEach-Object { $vsJson[$_.Key] = $_.Value } return $vsJson } static [VSJson] Transform([VSJson] $vsJson) { return $vsJson } VSJson() : base() {} VSJson([IDictionary] $dictionary) { $dictionary.GetEnumerator() | ForEach-Object { Write-Debug "Adding key '$($_.Key)' with value '$($_.Value)' to $this" $this[$_.Key] = $_.Value } } VSJson([psobject] $psObject) { $psObject.PSObject.Properties | ForEach-Object { Write-Debug "Adding property '$($_.Name)' with value '$($_.Value)' to $this" $this[$_.Name] = $_.Value } } VSJson([string] $stringOrFilepath) { $stringOrFilepath = [VSJson]::Decode($stringOrFilepath) $dictionary = [VSJson]::TransformToDict($stringOrFilepath) $dictionary.GetEnumerator() | ForEach-Object { Write-Debug "Adding key '$($_.Key)' with value '$($_.Value)' to $this" $this[$_.Key] = $_.Value } } VSJson([string[]] $strings) { $newString = $strings -join [Environment]::NewLine $stringOrFilepath = [VSJson]::Decode($newString) $dictionary = [VSJson]::TransformToDict($stringOrFilepath) $dictionary.GetEnumerator() | ForEach-Object { Write-Debug "Adding key '$($_.Key)' with value '$($_.Value)' to $this" $this[$_.Key] = $_.Value } } } 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 [Specialized.OrderedDictionary] TransformToDict([string] $stringOrFilepath) { $dictionary = [ordered]@{} if (Test-Path $stringOrFilepath) { $resPath = Resolve-Path $stringOrFilepath $stringOrFilepath = [File]::ReadAllText($resPath) } if ($stringOrFilepath.Trim() -match '^%\d\w') { Write-Debug "String appears to be URL encoded, decoding" $stringOrFilepath = [VSYaml]::Decode($stringOrFilepath) } try { $final = if (Get-Command cfn-flip* -ErrorAction SilentlyContinue) { $json = $stringOrFilepath | cfn-flip | Out-String $jsonConvert = @{ InputObject = $json ErrorAction = 'Stop' } if ($Global:PSVersionTable.PSVersion.Major -ge 7) { $jsonConvert['Depth'] = 20 } $final = ConvertFrom-Json @jsonConvert } else { ConvertFrom-Yaml -Yaml $stringOrFilepath -ErrorAction Stop } } catch { throw [VSError]::InvalidYamlInput($stringOrFilepath) } $final.PSObject.Properties | ForEach-Object { Write-Debug "Adding key '$($_.Name)' with value '$($_.Value)' to VSYaml" $dictionary[$_.Name] = $_.Value } Write-Debug "Resulting dict: $($dictionary | ConvertTo-Json -Depth 10))" return $dictionary } static [VSYaml] Transform([string] $stringOrFilepath) { $vsYaml = [VSYaml]::new() $stringOrFilepath = [VSYaml]::Decode($stringOrFilepath) $dictionary = [VSYaml]::TransformToDict($stringOrFilepath) $dictionary.GetEnumerator() | ForEach-Object { $vsYaml[$_.Key] = $_.Value } return $vsYaml } 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 2. Count provided: $($inputData.Count)") } } FnGetAtt() : base() {} FnGetAtt([string] $logicalNameOfResourceAndAttribute) { if ($logicalNameOfResourceAndAttribute -notlike '*.*') { throw [VSError]::InvalidArgument( $logicalNameOfResourceAndAttribute, "If you are passing a single string to `Fn::GetAtt`, it must contain the LogicalId and Attribute joined by a single period. Value passed: $logicalNameOfResourceAndAttribute" ) } $this[$this._topLevelKey] = $logicalNameOfResourceAndAttribute.Split('.',2) } 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 < |