How to Provision a New AWS EC2 Instance with PowerShell
EC2 is a wildly popular AWS service and is bound to be one you either have a lot of experience with or will at some point. One of the most basic tasks you can perform in EC2 is provisioning new instances. Deploying EC2 instances is fairly straightforward if using the management console. The management console walks you through all of the dependencies that an EC2 instance requires.
However, all of those dependencies are up to you to set up and define yourself if you’re setting up some automation script. In that case, you need to become familiar with not only setting up the EC2 instance itself but also all of the other resources that an EC2 instance depends on like a VPC, optional internet gateway, a route table, route, AMI image, route table, and a few more.
By understanding now just how to deploy an EC2 instance but also understanding everything else required will allow you to create a much smarter automation script.
In this article, we’re going to cover how to provision many common components that an EC2 instance needs as well as the EC2 instance itself. You’ll learn how each of these components fit together and take a peek at the PowerShell code necessary to deploy an EC2 instance from scratch with PowerShell.
Before we get too far, though, I’m going to be assuming that you already have an AWS account and have already installed the AWSPowerShell module available by running Install-Module AWSPowerShell in your PowerShell console. I’m also going to assume you’ve already been authenticated with Set-AWSCredential or some other means in your profile to make you ready to begin making changes in your AWS account with various PowerShell commands.
By the end of this article, you should have a running EC2 instance running Windows Server 2016 with a public DNS name that you can connect to via the Internet.
Let’s get started!
The Virtual Private Cloud (VPC)
In AWS-land, we don’t speak of vNets. Instead, we work with Virtual Private Clouds or VPCs. A VPC is similar to an Azure vNet in that it’s a network fabric that allows the virtual machines to connect with the rest of the cloud.
To keep the configuration as similar to Azure as possible, we’re not going to do anything fancy here. We’re simply going to create a VPC with a single subnet at it’s most basic level to replicate the same settings an Azure vNet might have.
To get started, we first need to know the network we’d like to create. I’ll create a network 10.10.0.0/24 just as an example. Once I know the network to create, I can then create it using the New-EC2Vpc command.
PS> $network = '10.0.0.0/16' PS> $vpc = New-EC2Vpc -CidrBlock $network PS> $vpc CidrBlock : 10.0.0.0/24 CidrBlockAssociationSet : {vpc-cidr-assoc-03f1edbc052e8c207} DhcpOptionsId : dopt-3c9c3047 InstanceTenancy : default Ipv6CidrBlockAssociationSet : {} IsDefault : False State : pending Tags : {} VpcId : vpc-03e8c773094d52eb3
Once we’ve got the VPC created, to mirror the Azure vNet, we must manually enable DNS support which Azure did for us automatically. This will point any EC2 instance’s DNS server attached to this VPC to an internal Amazon DNS server. We do this by enabling DNS support. Since Azure also, by default, gives us a public hostname and AWS does not, we must manually assign this as well by enabling DNS hostnames. You can see below that we are doing this with the Edit-EC2VpcAttribute command.
PS> Edit-EC2VpcAttribute -VpcId $vpc.VpcId -EnableDnsSupport $true PS> Edit-EC2VpcAttribute -VpcId $vpc.VpcId -EnableDnsHostnames $true
The Internet Gateway
Next, we must create the Internet gateway. This allows our EC2 instance to route traffic to and from the Internet. Unlike Azure, again, we’ll need to manually create this functionality using the New-EC2InternetGateway command.
PS> $gw = New-EC2InternetGateway PS> $gw
Attachments InternetGatewayId Tags ----------- ----------------- ---- {} igw-05ca5aaa3459119b1 {}
Now that we have the internet gateway created, we then need to attach it to our VPC to allow EC2 instances connected to that VPC to route traffic through it. We do that by using the Add-EC2InternetGateway command.
PS> Add-EC2InternetGateway -InternetGatewayId $gw.InternetGatewayId -VpcId $vpc.VpcId
Routes
Once the gateway is created, we’ll then need to create a route table and a route so that the EC2 instances on our VPC can access the Internet. To do that, we must first create a route table. A route table is required to eventually create the route in. We do this by using the New-EC2RouteTable command.
PS> $rt = New-EC2RouteTable -VpcId $vpc.VpcId
PS> $rt
Associations : {}
PropagatingVgws : {}
Routes : {}
RouteTableId : rtb-09786c17af32005d8
Tags : {}
VpcId : vpc-03e8c773094d52eb3
Listing X-12: Creating a route table
Next, we’ll create a route inside of the route table that points to the gateway we just created. Since I’m creating a “default” route or “default gateway” if you’re familiar with the Windows client term, I’ll be routing all traffic (0.0.0.0/0) through our internet gateway. The command will return True if successful.
PS> New-EC2Route -RouteTableId $rt.RouteTableId -GatewayId $gw.InternetGatewayId -DestinationCidrBlock '0.0.0.0/0' True
Subnet
Next, we must create a subnet inside of our larger VPC and associate it with our route table. To do this, we’ll use the New-EC2Subnet command to create the subnet and once created, we can then use the Register-EC2RouteTable command to register this subnet to the route table we built earlier. But first, we’ll need to define an availability zone for the subnet. If you’re not sure what to use, we can use the Get-EC2AvailabilityZone command to enumerate all of them. For our purposes, we’ll be using the us-east-1d availability zone.
PS> Get-EC2AvailabilityZone
Messages RegionName State ZoneName -------- ---------- ----- -------- {} us-east-1 available us-east-1a {} us-east-1 available us-east-1b {} us-east-1 available us-east-1c {} us-east-1 available us-east-1d {} us-east-1 available us-east-1e {} us-east-1 available us-east-1f Listing X-14: Enumerating EC2 availability zones
Once we know the availability zone to use, we can then create the EC2 subnet and register it with the route table.
PS> $sn = New-EC2Subnet -VpcId $vpc.VpcId -CidrBlock '10.0.1.0/24' -AvailabilityZone 'us-east-1d' PS> Register-EC2RouteTable -RouteTableId $rt.RouteTableId -SubnetId $sn.SubnetId rtbassoc-06a8b5154bc8f2d98
Once we have a subnet created, we’re done with the network stack!
Assigning an AMI to our EC2 instance
Once we’ve got the network stack all built out for our EC2 instance, we can now get to assigning an AMI or Amazon Machine Instance to our VM. To do that, we first need to find an existing AMI that suits our needs. I’d like to create a Windows Server 2016 instance so I’ll first need to figure out the name of the instance. I can enumerate all of the available instances with the Get-EC2ImageByName command. When I do that, I will find an image name called WINDOWS_2016_BASE. This is what we’re going to use.
Once I know the image name, I’ll then use Get-EC2ImageByName again and this time specify the image we’d like to use. This then provides the image object we need to pass to New-EC2Instance to create our EC2 instance.
PS> $ami = Get-EC2ImageByName -Name 'WINDOWS_2016_BASE'
PS> $ami
Architecture : x86_64 BlockDeviceMappings : {/dev/sda1, xvdca, xvdcb, xvdcc...} CreationDate : 2018-08-15T02:27:20.000Z Description : Microsoft Windows Server 2016 with Desktop Experience Locale English AMI provided by Amazon EnaSupport : True Hypervisor : xen ImageId : ami-0b7b74ba8473ec232 ImageLocation : amazon/Windows_Server-2016-English-Full-Base-2018.08.15 ImageOwnerAlias : amazon ImageType : machine KernelId : Name : Windows_Server-2016-English-Full-Base-2018.08.15 OwnerId : 801119661308 Platform : Windows ProductCodes : {} Public : True RamdiskId : RootDeviceName : /dev/sda1 RootDeviceType : ebs SriovNetSupport : simple State : available StateReason : Tags : {} VirtualizationType : hvm
Great! We’ve now got our image captured and ready to go. Next up and most importantly, finally, we can create our EC2 instance using the New-EC2Instance command. To do that, we’re going to need the instance type. Unfortunately, the instance types aren’t available via an AWS API, thus, no PowerShell cmdlet is available, but you can check out all of them on this AWS page. I’m cheap, so I’ll choose the free one which is t2.micro.
PS> $params = @{ >> ImageId = $ami.ImageId >> AssociatePublicIp = $false >> InstanceType = 't2.micro' >> SubnetId = $sn.SubnetId } PS> New-EC2Instance @params GroupNames : {} Groups : {} Instances : {} OwnerId : 013223035658 RequesterId : ReservationId : r-05aa0d9b0fdf2df4f
Once this runs, you will now see a brand new EC2 instance available to you in your AWS Management Console or by running Get-EC2Instance.
Wrap up
We’ve now nailed down the code to create the EC2 instance, but, as-is, it’s cumbersome to use. Because creating an EC2 instance may be a frequent occurrence, it’s a good practice to create a script or function to do all of this for us. You can download an example function called New-EC2 Custom Instance from the TechSnips GitHub repo.
When the function is called, and all dependencies already exist except for the EC2 instance itself, we will see output similar to Listing X-46 when ran with the Verbose parameter. The function is a lot smarter than simply running code to create all of these resources alone. Below you can see an example of it being used.
PS> $parameters = @{ >> VpcCidrBlock = '10.0.0.0/16' >> EnableDnsSupport = $true >> SubnetCidrBlock = '10.0.1.0/24' >> OperatingSystem = 'Windows Server 2016' >> SubnetAvailabilityZone = 'us-east-1d' >> InstanceType = 't2.micro' >> Verbose = $true } PS> New-CustomEC2Instance @parameters VERBOSE: Invoking Amazon Elastic Compute Cloud operation 'DescribeVpcs' in region 'us-east-1' VERBOSE: A VPC with the CIDR block [10.0.0.0/16] has already been created. VERBOSE: Enabling DNS support on VPC ID [vpc-03ba701f5633fcfac]... VERBOSE: Invoking Amazon EC2 operation 'ModifyVpcAttribute' in region 'us-east-1' VERBOSE: Invoking Amazon EC2 operation 'ModifyVpcAttribute' in region 'us-east-1' VERBOSE: Invoking Amazon Elastic Compute Cloud operation 'DescribeInternetGateways' in region 'us-east-1' VERBOSE: An internet gateway is already attached to VPC ID [vpc-03ba701f5633fcfac]. VERBOSE: Invoking Amazon Elastic Compute Cloud operation 'DescribeRouteTables' in region 'us-east-1' VERBOSE: Route table already exists for VPC ID [vpc-03ba701f5633fcfac]. VERBOSE: A default route has already been created for route table ID [rtb-0b4aa3a0e1801311f rtb-0aed41cac6175a94d]. VERBOSE: Invoking Amazon Elastic Compute Cloud operation 'DescribeSubnets' in region 'us-east-1' VERBOSE: A subnet has already been created and registered with VPC ID [vpc-03ba701f5633fcfac]. VERBOSE: Invoking Amazon EC2 operation 'DescribeImages' in region 'us-east-1' VERBOSE: Creating EC2 instance... VERBOSE: Invoking Amazon EC2 operation 'RunInstances' in region 'us-east-1' GroupNames : {} Groups : {} Instances : {} OwnerId : 013223035658 RequesterId : ReservationId : r-0bc2437cfbde8e92a
We now have the tools we need to automate the provisioning of EC2 instances in AWS!