AWSTemplateFormatVersion: 2010-09-09 Description: >- This template deploys a .NET development environment. (qs-1sctmf6e2) Parameters: AllowedCIDR: AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$ ConstraintDescription: The CIDR block parameter must be in the form x.x.x.x/x Description: "Block of CIDR IP addresses that can access the EC2 instance. A value of 0.0.0.0/0 allows access from any IP address." Type: String Default: 0.0.0.0/0 VPCId: Description: (Optional) ID of the existing VPC that you're deploying into. Keep blank if you're creating a new VPC. Type: String #AWS::EC2::VPC::Id doesn't allow 'null' values. Default: "" SubnetId: Description: (Optional) ID of your existing subnet. Keep blank if you're creating a new VPC. Type: String #AWS::EC2::Subnet::Id doesn't allow 'null' values. Default: "" WindowsAMIId: Description: ID of the Windows EC2 AMI. Type: 'AWS::SSM::Parameter::Value' Default: '/aws/service/ami-windows-latest/Windows_Server-2019-English-Full-ContainersLatest' KeyName: Description: Name of the key pair to be used to connect to your EC2 instance by using SSH. Type: AWS::EC2::KeyPair::KeyName AllowedPattern: \S+ MinLength: 1 ConstraintDescription: Please provide the name of an EC2 keypair that has been registered in your account. InstanceType: AllowedValues: - r4.xlarge - r4.2xlarge - r4.4xlarge - r5.large - r5.xlarge - r5.2xlarge - r5.4xlarge - m5.large - m5.2xlarge - m5.4xlarge Default: m5.large Description: Type of EC2 instance to use for the development-tools instance. Type: String QSS3BucketName: AllowedPattern: '^[0-9a-zA-Z]+([0-9a-zA-Z-\.]*[0-9a-zA-Z])*$' ConstraintDescription: The Quick Start bucket name can include numbers, lowercase letters, uppercase letters, periods, and hyphens (-). It cannot start or end with a hyphen (-). Default: aws-quickstart Description: 'Name of the S3 bucket for your copy of the Quick Start assets. Keep the default name unless you are customizing the template. Changing the name updates code references to point to a new Quick Start location. This name can include numbers, lowercase letters, uppercase letters, and hyphens, but do not start or end with a hyphen (-). See https://aws-quickstart.github.io/option1.html.' Type: String QSS3BucketRegion: Default: us-east-1 Description: 'AWS Region where the Quick Start S3 bucket (QSS3BucketName) is hosted. Keep the default Region unless you are customizing the template. Changing this Region updates code references to point to a new Quick Start location. When using your own bucket, specify the Region. See https://aws-quickstart.github.io/option1.html.' Type: String QSS3KeyPrefix: AllowedPattern: '^[0-9a-zA-Z-/]*$' ConstraintDescription: The Quick Start S3 key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slashes (/). Default: quickstart-dotnet-devenvironment-setup/ Description: 'S3 key prefix that is used to simulate a directory for your copy of the Quick Start assets. Keep the default prefix unless you are customizing the template. Changing this prefix updates code references to point to a new Quick Start location. This prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slashes (/). See https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html and https://aws-quickstart.github.io/option1.html.' Type: String Metadata: cfn-lint: config: ignore_checks: - W9006 QuickStartDocumentation: EntrypointName: "Parameters for deploying into a new or existing VPC" AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Network configuration Parameters: - VPCId - SubnetId - AllowedCIDR - Label: default: EC2 configuration Parameters: - KeyName - InstanceType - WindowsAMIId - Label: default: Quick Start parameters Parameters: - QSS3BucketName - QSS3KeyPrefix - QSS3BucketRegion ParameterLabels: KeyName: default: EC2 key pair VPCId: default: VPC ID AllowedCIDR: default: Allowed CIDR block SubnetId: default: Subnet ID WindowsAMIId: default: Windows EC2 AMI ID InstanceType: default: EC2 instance type QSS3BucketName: default: "S3 bucket name" QSS3KeyPrefix: default: "S3 key prefix" QSS3BucketRegion: default: "S3 bucket Region" Conditions: CreateVPC: !Equals [ !Ref VPCId, "" ] Resources: VPC: Condition: CreateVPC Type: AWS::EC2::VPC Properties: CidrBlock: '10.0.0.0/16' EnableDnsHostnames: true Tags: - Key: Name Value: !Sub ${AWS::StackName} VPC InternetGW: Condition: CreateVPC Type: AWS::EC2::InternetGateway # Route tables & routes (public/private) InternetGWAttachment: Condition: CreateVPC Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref InternetGW VpcId: !Ref VPC InternetRouteTable: Condition: CreateVPC Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC InternetRoute: Condition: CreateVPC Type: AWS::EC2::Route DependsOn: InternetGWAttachment Properties: RouteTableId: !Ref InternetRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGW # Subnets public/private PublicSubnet: Condition: CreateVPC Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.0.0/24 MapPublicIpOnLaunch: false VpcId: !Ref VPC PublicRouteAssociation: Condition: CreateVPC Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref InternetRouteTable SubnetId: !Ref PublicSubnet VSInstanceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: ec2.amazonaws.com Sid: '' Path: / ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess' VSInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: [ !Ref VSInstanceRole ] RDPAccess: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow RDP connections from the outside world VpcId: !If [CreateVPC, !Ref VPC, !Ref VPCId] SecurityGroupIngress: - IpProtocol: tcp FromPort: 3389 ToPort: 3389 CidrIp: !Ref AllowedCIDR Description: AllowRDP SecurityGroupEgress: - IpProtocol: tcp FromPort: 0 ToPort: 65535 CidrIp: 0.0.0.0/0 Description: AllowAll VSInstance: Type: AWS::EC2::Instance DependsOn: - VSInstanceWaitHandle - VSCodeWaitHandle - PowershellWaitHandle - RiderWaitHandle - PAWaitHandle - A2CWaitHandle - SCTWaitHandle Properties: InstanceType: !Ref InstanceType ImageId: !Ref WindowsAMIId IamInstanceProfile: !Ref VSInstanceProfile KeyName: !Ref KeyName NetworkInterfaces: - AssociatePublicIpAddress: true DeviceIndex: '0' GroupSet: [ !GetAtt RDPAccess.GroupId ] SubnetId: !If [CreateVPC, !Ref PublicSubnet, !Ref SubnetId] BlockDeviceMappings: - DeviceName: /dev/sda1 Ebs: VolumeSize: 80 VolumeType: gp3 Tags: - Key: Name Value: !Sub ${AWS::StackName}-DevBox UserData: Fn::Base64: !Sub | $VerbosePreference = "Continue" try { $logPath = Join-Path "C:\ProgramData\Amazon\EC2-Windows\Launch\Log" UserDataTranscript.log Start-Transcript -Path $logPath -IncludeInvocationHeader Set-ExecutionPolicy Bypass -Scope Process -Force; Write-Output "WaitHandles:" Write-Output "RiderWaitHandle: ${ RiderWaitHandle }" Write-Output "VSCodeWaitHandle: ${ VSCodeWaitHandle }" Write-Output "PowershellWaitHandle: ${ PowershellWaitHandle }" Write-Output "PAWaitHandle: ${ PAWaitHandle }" Write-Output "A2CWaitHandle: ${ A2CWaitHandle }" Write-Output "SCTWaitHandle: ${ SCTWaitHandle }" Write-Output "VSInstanceWaitHandle: ${ VSInstanceWaitHandle }" Write-Output "Setting default region" if (-not $(Test-Path C:\Users\Administrator\.aws)) { mkdir C:\Users\Administrator\.aws } $configPath = Join-Path C:\Users\Administrator\.aws config Add-Content $configPath "[default]" Add-Content $configPath "region = ${AWS::Region}" Add-Content $configPath "output = json" Write-Output "Installing Chocolatey" iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) Import-Module "$env:ChocolateyInstall\helpers\chocolateyInstaller.psm1" choco feature enable -n allowGlobalConfirmation # Git Write-Output "Installing Git" choco install git # Microsoft Edge Write-Output "Installing Microsoft Edge Browser" choco install microsoft-edge # Corretto JDK choco install correttojdk Write-Output "Installing .NET 3.1 SDK" choco install dotnetcore-3.1-sdk Write-Output "Installing hosting support for ASP.NET 3.1" choco install dotnetcore-3.1-windowshosting Write-Output "Installing .NET 5.0 SDK" choco install dotnet-5.0-sdk Write-Output "Installing hosting support for ASP.NET 5.0" choco install dotnet-5.0-windowshosting Write-Output "Installing .NET 6.0 SDK" choco install dotnet-6.0-sdk Write-Output "Installing hosting support for ASP.NET 6.0" choco install dotnet-6.0-windowshosting Write-Output "Installing .NET 4.7.1/4.7.2/4.8 support." choco install netfx-4.7.1-devpack choco install netfx-4.7.2-devpack choco install netfx-4.8-devpack # JetBrains Rider Write-Output "Installing JetBrains Rider" choco install jetbrains-rider # Signal Rider Installation Completed Invoke-WebRequest -Method PUT -Body $( ConvertTo-Json @{ "Status" = "SUCCESS"; "UniqueId"=[System.Guid]::NewGuid(); "Data"="Rider Installed." } ) -Uri "${ RiderWaitHandle }" # AWS CLI choco install awscli # VS Code Write-Output "Installing Visual Studio Code" choco install vscode # Signal VSCode Installation Completed Invoke-WebRequest -Method PUT -Body $( ConvertTo-Json @{ "Status" = "SUCCESS"; "UniqueId"=[System.Guid]::NewGuid(); "Data"="VSCode Installed." } ) -Uri "${ VSCodeWaitHandle }" # PowerShell Core Write-Output "Installing latest version of PowerShell Core" choco upgrade powershell-core Write-Output "Creating PowerShell 7 shortcut" $TargetFile = "`"${!env:ProgramFiles}\PowerShell\7\pwsh.exe`" -WorkingDirectory ~" $ShortcutFile = "$env:Public\Desktop\PowerShell 7.lnk" $WScriptShell = New-Object -ComObject WScript.Shell $Shortcut = $WScriptShell.CreateShortcut($ShortcutFile) $Shortcut.TargetPath = "${!env:ProgramFiles}\PowerShell\7\pwsh.exe" $Shortcut.Description = "PowerShell 7" $Shortcut.Save() Write-Output "Refreshing environment variables" refreshenv # Install .NET Deployment Tool Start-Process -Wait -FilePath "C:\Program Files\dotnet\dotnet.exe" -ArgumentList "tool","install","--global","aws.deploy.cli" # Helper scripts Write-Output "Downloading helper scripts" if (-not $(Test-Path C:\AWS)) { mkdir C:\AWS } Read-S3Object -Region ${QSS3BucketRegion} -BucketName ${QSS3BucketName} -Key ${QSS3KeyPrefix}docs/images/Background-Dark.bmp -File C:\Temp\Background-Dark.bmp # Install IIS & ASP.NET Support Install-WindowsFeature -name Web-Server -IncludeManagementTools # Install ASP.NET 4.6 Install-WindowsFeature Web-Asp-Net45 # There is no PSDrive for HKU reg.exe add "HKU\.DEFAULT\Control Panel" /v Wallpaper /t REG_SZ /d "C:\Temp\Background-Dark.bmp" Read-S3Object -Region ${QSS3BucketRegion} -BucketName ${QSS3BucketName} -Key ${QSS3KeyPrefix}scripts/UpdateProfileCredentials.ps1 -File C:\AWS\UpdateProfileCredentials.ps1 Read-S3Object -Region ${QSS3BucketRegion} -BucketName ${QSS3BucketName} -Key ${QSS3KeyPrefix}scripts/SetupPwsh.ps1 -File C:\AWS\SetupPwsh.ps1 Write-Output "Installing AWS PowerShell Modules" pwsh -File C:\AWS\SetupPwsh.ps1 Invoke-WebRequest -Method PUT -Body $( ConvertTo-Json @{ "Status" = "SUCCESS"; "UniqueId"=[System.Guid]::NewGuid(); "Data"="Downloaded PowerShell scripts." } ) -Uri "${ PowershellWaitHandle }" # Install AWS Porting Assistant for .NET Invoke-WebRequest -Method GET -Uri https://s3-us-west-2.amazonaws.com/aws.portingassistant.dotnet.download/latest/windows/Porting-Assistant-Dotnet.exe -OutFile C:\AWS\Porting-Assistant-Dotnet.exe Start-Process -Wait -FilePath 'C:\AWS\Porting-Assistant-Dotnet.exe' -ArgumentList "/S" # Signal PA Installation Completed Invoke-WebRequest -Method PUT -Body $( ConvertTo-Json @{ "Status" = "SUCCESS"; "UniqueId"=[System.Guid]::NewGuid(); "Data"="VSCode Installed." } ) -Uri "${ PAWaitHandle }" # Install App2Container Invoke-WebRequest -Method GET -Uri https://app2container-release-us-east-1.s3.us-east-1.amazonaws.com/latest/windows/AWSApp2Container-installer-windows.zip -OutFile C:\AWS\AWSApp2Container-installer-windows.zip Expand-Archive -Path C:\AWS\AWSApp2Container-installer-windows.zip -DestinationPath C:\AWS\App2Container\ C:\AWS\App2Container\install.ps1 -acceptEula $true # Signal A2C Installation Completed Invoke-WebRequest -Method PUT -Body $( ConvertTo-Json @{ "Status" = "SUCCESS"; "UniqueId"=[System.Guid]::NewGuid(); "Data"="VSCode Installed." } ) -Uri "${ A2CWaitHandle }" # Install the Schema Conversion Tool Invoke-WebRequest -Method GET -Uri https://s3.amazonaws.com/publicsctdownload/Windows/aws-schema-conversion-tool-1.0.latest.zip -OutFile C:\AWS\aws-schema-conversion-tool-1.0.latest.zip Expand-Archive -Path C:\AWS\aws-schema-conversion-tool-1.0.latest.zip -DestinationPath C:\AWS\SCT\ Start-Process -Wait -FilePath msiexec.exe -ArgumentList "/i`"$(gci C:\AWS\SCT\*.msi|select -First 1 -ExpandProperty FullName)`"","/q","/lvx","SCT.log" # Signal SCT Installation Completed Invoke-WebRequest -Method PUT -Body $( ConvertTo-Json @{ "Status" = "SUCCESS"; "UniqueId"=[System.Guid]::NewGuid(); "Data"="VSCode Installed." } ) -Uri "${ SCTWaitHandle }" # AWS Credentials Write-Output "Creating task to update credentials from EC2 profile" schtasks.exe /create /tn "Refresh AWS Credentials" /sc ONEVENT /ec "Microsoft-Windows-TerminalServices-LocalSessionManager/Operational" /MO "*[System[Provider[@Name='Microsoft-Windows-TerminalServices-LocalSessionManager'] and EventID=25]]" /tr "pwsh -WindowStyle Hidden -file C:\AWS\UpdateProfileCredentials.ps1" $task = Get-ScheduledTask -TaskPath "\" -TaskName "Refresh AWS Credentials" $triggers = $($( Get-ScheduledTask -TaskPath "\" -TaskName "Refresh AWS Credentials" ).Triggers + @( $(New-ScheduledTaskTrigger -AtLogOn))) Set-ScheduledTask -TaskPath "\" -TaskName "Refresh AWS Credentials" -Trigger $triggers # We need to restart after installing IIS Write-Output "Completed! Restarting computer in 120 seconds..." shutdown /r /t 120 Write-Output "Signaling successful completion to VSInstanceWaitHandle" Invoke-WebRequest -Method PUT -Body $( ConvertTo-Json @{ "Status" = "SUCCESS"; "UniqueId"=[System.Guid]::NewGuid(); "Data"="Instance initialized successfully." } ) -Uri "${ VSInstanceWaitHandle }" } catch { $itemName = $_.Exception.ItemName; $errorMessage = $_.Exception.Message; Write-Output "ERROR: $itemName - $errorMessage`nSignaling failure." Invoke-WebRequest -Method PUT -Body $( ConvertTo-Json @{ "Status" = "FAILURE"; "UniqueId"=[System.Guid]::NewGuid(); "Data"=$errorMessage } ) -Uri "${ VSInstanceWaitHandle }" } VSInstanceWaitHandle: Type: AWS::CloudFormation::WaitConditionHandle PowershellWaitHandle: Type: AWS::CloudFormation::WaitConditionHandle VSCodeWaitHandle: Type: AWS::CloudFormation::WaitConditionHandle RiderWaitHandle: Type: AWS::CloudFormation::WaitConditionHandle PAWaitHandle: Type: AWS::CloudFormation::WaitConditionHandle A2CWaitHandle: Type: AWS::CloudFormation::WaitConditionHandle SCTWaitHandle: Type: AWS::CloudFormation::WaitConditionHandle InstanceWaitCondition: Type: AWS::CloudFormation::WaitCondition Properties: Handle: !Ref VSInstanceWaitHandle Timeout: '7200' Count: 1 PSWaitCondition: Type: AWS::CloudFormation::WaitCondition Properties: Handle: !Ref PowershellWaitHandle Timeout: '3600' Count: 1 VSCodeWaitCondition: Type: AWS::CloudFormation::WaitCondition Properties: Handle: !Ref VSCodeWaitHandle Timeout: '3600' Count: 1 RiderWaitCondition: Type: AWS::CloudFormation::WaitCondition Properties: Handle: !Ref RiderWaitHandle Timeout: '3600' Count: 1 PAWaitCondition: Type: AWS::CloudFormation::WaitCondition Properties: Handle: !Ref PAWaitHandle Timeout: '4500' Count: 1 A2CWaitCondition: Type: AWS::CloudFormation::WaitCondition Properties: Handle: !Ref A2CWaitHandle Timeout: '4500' Count: 1 SCTWaitCondition: Type: AWS::CloudFormation::WaitCondition Properties: Handle: !Ref SCTWaitHandle Timeout: '5400' Count: 1 Outputs: VSInstanceId: Description: VS Instance ID Value: !Ref VSInstance VSInstanceAddress: Description: VS Instance DNS Name Value: !GetAtt VSInstance.PublicDnsName