Functions/New-CmEC2Instance.ps1

Function New-CmEC2Instance           {
    <#
.Synopsis
    Creates a Windows EC2 On demand or spot Instance with minimal input.
.DESCRIPTION
    Creates a Windows EC2 On demand or spot Instance and, if a Name and DomainName are specified, creates or updates the Route 53 DNS entry for the instance and applies a Name Tag.
 
.NOTES
    Name: New-CMEC2Instance
    Author: Chad Miles
    DateUpdated: 2017-05-02
    Version: 1.2.0
 
.EXAMPLE
   C:\> New-CMEC2Instance -InstanceType t2.micro -Region us-east-1 -DomainName mydomain.com -Name MyInstance
    
   InstanceID : i-1234567890abcdef
   Region : us-east-1
   Name : MyInstance
   Hostname : MyInstance.Mydomain.com
   InstanceType : t2-micro
   BidPrice :
   OnDemandPrice : 0.017
   Savings : 0 %
   ImageName : WINDOWS_2016_BASE
   ImageID : ami-58a1a73e
   KeyName : MyKeyPair
    
.EXAMPLE
   C:\> New-CMEC2Instance -InstanceType t2.micro -Region us-east-1 -Name MyInstance
    
   InstanceID : i-1234567890abcdef
   Region : us-east-1
   Name : MyInstance
   Hostname : ec2-34-248-2-178.eu-west-1.compute.amazonaws.com
   InstanceType : t2-micro
   ImageName : WINDOWS_2016_BASE
   ImageID : ami-58a1a73e
   KeyName : MyKeyPair
 
.EXAMPLE
   C:\> New-CMEC2Instance -InstanceType t2.micro -Region us-east-1 -Name MyInstance -DomainName mydomain.com -OSVerion 2012R2
    
   InstanceID : i-1234567890abcdef
   Region : us-east-1
   Name : MyInstance
   Hostname : MyInstance.Mydomain.com
   InstanceType : t2-micro
   ImageName : WINDOWS_2012R2_BASE
   ImageID : ami-40003a26
   KeyName : MyKeyPair
 
.EXAMPLE
   C:\> New-CMEC2Instance -InstanceType t2.micro -Region us-east-1 -Name MyInstance -DomainName mydomain.com -SpotRequest
 
   InstanceID : i-1234567890abcdef
   Region : us-east-1
   Name : MyInstance
   Hostname : MyInstance.Mydomain.com
   InstanceType : t2-micro
   ImageName : WINDOWS_2016_BASE
   ImageID : ami-40003a26
   KeyName : MyKeyPair
 
.EXAMPLE
   C:\> New-CMEC2Instance -InstanceType m3.medium -Region us-east-1 -Name MySpotInstance -DomainName mydomain.com -SpotRequest
    
   InstanceID : i-1234567890abcdef
   Region : us-east-1
   Name : MySpotInstance
   Hostname : MySpotInstance.Mydomain.com
   InstanceType : m3.medium
   ImageName : WINDOWS_2016_BASE
   ImageID : ami-58a1a73e
   KeyName : MyKeyPair
 
.EXAMPLE
   C:\> New-CMEC2Instance -InstanceType m3.medium -Region us-east-1 -Name TestInstance -DomainName mydomain.com -RootVolumeSize 50 -SecondaryVolumeSize 100
    
   InstanceID : i-1234567890abcdef
   Region : us-east-1
   Name : TestInstance
   Hostname : TestInstance.Mydomain.com
   InstanceType : m3.medium
   ImageName : WINDOWS_2016_BASE
   ImageID : ami-58a1a73e
   KeyName : MyKeyPair
 
#>

    [CmdletBinding(SupportsShouldProcess=$true,
                   DefaultParameterSetName='SearchBaseIds')]
    Param (
        [ValidateScript({@((Get-AWSRegion).Region)})]
        [string] $Region,
        [Parameter(Mandatory=$true)]
        [ValidateScript({(Get-CmEc2InstanceTypes)})]
        [Alias("Type")] 
        [String] $InstanceType,
        # Applies this name tag to the instance after creation, if -DomainName is specified as well then registers a DNS CNAME for your instance using this name
        [string] $Name,
        # E.g mydomain.com. Must be used with -Name, then a DNS CNAME is registered, provided it is a Route53 hosted zone and you have rights.
        [string] $DomainName,
        # Version of Windows e.g. 2012R2 or 2016. Default is 2016
        [ValidateSet(
            "WindowsServer2004", 
            "WindowsServer1909", 
            "WindowsServer1903", 
            "WindowsServer2019",
            "WindowsServer2016",
            "WindowsServer2012R2",
            "WindowsServer2012",
            "2004",
            "1909",
            "1903",
            "2019",
            "2016",
            "2012R2",
            "2012",
            "Ubuntu16.04",
            "Ubuntu18.04",
            "AmazonLinux",
            "AmazonLinux2",
            "AmazonLinux2NetCore",
            "UbuntuNetCore",
            "EcsAmazonLinux",
            "EcsAmazonLinux2"
        )]
        [string] $OsVersion = "2016",

        [Parameter(ParameterSetName='SearchSqlIds')]
        [ValidateSet("2017","2016","2014","2012")]
        [string] $SqlVersion,

        [Parameter(ParameterSetName='SearchSqlIds')]
        [ValidateSet("Express", "Web","Standard","Enterprise")]
        [string] $SqlEdition = "Standard",
        [switch] $Core,
        # Instance Profile (with IAM Role) to attach to new Instance
        [string] $InstanceProfile,
        # Path to User data file , using Your My Documents folder as a root
        [string] $UserData,
        #Specify an AMI id like ami-2b8c8452
        [Parameter(ValueFromPipeline       =$true,
            ValueFromPipelineByPropertyName=$true,
            ParameterSetName               ='ImageId',
            Mandatory                      =$true)]
        [ValidatePattern("^ami-([\da-f]{8}|[\da-f]{17})$")]
        [string] $ImageId,
        # The name of the Security Group, not the Security Group ID. The Function will get the ID. If none is specified the default Security Group is used.
        [string] $SecurityGroupName,
        # Switch to specify that a Spot instance should be created instead of On Demand. Bid price will be automatically calculated based on current bid price plus a markup percentage of which the default is 1.
        [switch] $SpotInstance,
        # The Name of the EC2 KeyPair to use when creating the instance
        [string] $KeyName,
        # The ID of the VPC you wish to use, if not specified, the default one is used.
        [ValidatePattern("^vpc-([\da-f]{8}|[\da-f]{17})$")]
        [string] $VpcId,
        # Last letter of the Availability Zone like a, b, c, d or e, if none specified, a random one is used
        [string] $AZSuffix,
        # The Subnet in which to launch the Instance(s). Overrides VpcId if and AZsuffix parameters if specified. If not specified then a random subnet is chosen in the default VPC.
        [ValidatePattern("^subnet-([\da-f]{8}|[\da-f]{17})$")]
        [string] $SubNetId,
        [ValidateRange(1,15)]
        [Int]    $NetworkInterfaces = 1,
        [Int]    $Count=1    ,
        # Specifies the Root EBS volume size in GB
        [Int]    $RootVolumeSize,
        # Speciies the Secondary EBS volume size if there is one present in the AMI. If not it will create a new volume and attach it.
        [Int]    $SecondaryVolumeSize,
        # Default is to Keep the secondary volume on instance terminations, but set this switch to kill it
        [switch] $TerminateSecondaryVolume

    )
    <#DynamicParam
    {
        $Names = Get-CmEc2InstanceTypes
        $ParameterName = 'InstanceType'
        $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
        $ParamAttrib = New-Object System.Management.Automation.ParameterAttribute
        $ParamAttrib.Mandatory= 1
        $AttribColl = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
        $AttribColl.Add($ParamAttrib)
        $AttribColl.Add((New-Object System.Management.Automation.ValidateSetAttribute($Names)))
        $RuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttribColl)
        $RuntimeParamDic = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
        $RuntimeParamDic.Add($ParameterName, $RuntimeParam)
        return $RuntimeParamDic
    }#>

    BEGIN 
    {
        $ErrorActionPreference    = "Stop"
        if ($AZSuffix){
            if (-not $Region) {$Region = (Get-DefaultAWSRegion).Region}
            if (-not $Region) {try {$Region = (Get-EC2InstanceMetadata -Category Region).SystemName} catch {}}
            $AvailabilityZone      = $Region+$AZSuffix
        }
        
    }
    Process {
        if ($SqlVersion) {
            if ($InstanceType -like "t*.*" -and $SqlEdition -ne "Express") {Write-Error "Non Express editions of SQL Server are not permited to run on T2/T3 instance types"}
        }
        
        Write-Verbose          "Geting On Demand Instance Pricing"
        Try {$OndemandPrice       = Get-EC2WindowsOndemandPrice -InstanceType $InstanceType -Region $Region}
        Catch {}
        
        if (!$ImageID) {
            Write-Verbose       "Getting Current AMI for Selected OS"
            $ImageParam      = @{OsVersion = $OsVersion}
            If ($Core)       {$ImageParam.Add('Core',      $true)}
            If ($Region)     {$ImageParam.Add('Region',    $Region)}
            If ($SqlVersion) {$ImageParam.Add('SqlVersion',$SqlVersion)}
            If ($SqlEdition) {$ImageParam.Add('SqlEdition',$SqlEdition)}
            $Image           = Get-CMEC2ImageId @ImageParam
            $ImageID         = $Image.ImageId
        } else {
            $ImageParam      = @{ImageId = $ImageId}
            If ($Region)     {$ImageParam.Add("Region",$Region)}
            try {$Image      = Get-EC2Image @ImageParam }
            Catch {}
        }
        if (!$ImageID -or !$Image) { Write-Error "Could not find an image with Search criteria in region $Region"}
        if (!$Keyname) {
            Write-Verbose  "Getting first KeyPair for Region"
            $KeyName  = (Get-EC2KeyPair -Region $Region)[0].KeyName
        }
        if (!$KeyName)    {Write-Error "No EC2 Key Pairs found in region $Region, please create one"}
        If ($UserData)    {$UserData64 = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($UserData))}
        If (!$SubNetId) { 
            If (!$VpcId)   
            {
                Write-Verbose   "Getting default VPC"
                $VpcId  = (Get-EC2Vpc -Region $Region| Where {$_.IsDefault -eq $true}).VpcId
            }
            If (!$VpcId)   { Write-Error "Could not find default VPC in region $Region, Please specify either a VPC or a Subnet Id" }
            Write-Verbose       "Getting Subnet for name $AvailabilityZone"
            $SubNets = Get-EC2Subnet -Region $Region -Filter @{Name='vpc-id';Values=$VpcId}
            If ($AvailabilityZone) {$SubNetId = ( $SubNets | where {$_.AvailabilityZone -eq $AvailabilityZone})[0].SubnetId}
            else {$SubNetId = ($SubNets | where {$_.VpcId -eq $VPCid})[(Get-Random -Maximum $($SubNets.Count-1))].SubnetId}
        } Else {
            $VpcId = (Get-EC2Subnet -Region $Region -SubnetId $SubNetId).VpcId
        }
        If (!$VpcId) {Write-Error "Could not determine VPC, check you have a default VPC in the region and if SubnetId is specified, make sure it is valid"}
    
        If ($SecurityGroupName)  {
            Write-Verbose       "finding Security Group named $SecurityGroupName"
            $SecurityGroupId   = (Get-EC2SecurityGroup -Region $Region | where {$_.GroupName -eq $SecurityGroupName -and $_.VpcId -eq $VpcId})[0].GroupId
            If (!$SecurityGroupId) {Write-Warning "Security Group with $SecurityGroupName cannot be found, using default"}
        } 
        if (!$SecurityGroupId) {
            Write-Verbose       "Getting default Security Group for VPC $VpcId"
            $SecurityGroupId   = (Get-EC2SecurityGroup -Region $Region | where {$_.GroupName -eq "default" -and $_.VpcId -eq $VPCId})[0].GroupId
            If (!$SecurityGroupId) {Write-Error "No Default security group found in $VpcId"}
        }
        If ($RootVolumeSize){
            $BDMs = $Image.BlockDeviceMappings
            ($BDMs | Where {$_.DeviceName -eq $Image.RootDeviceName}).EBS.VolumeSize = $RootVolumeSize
        }
        If ($SecondaryVolumeSize){
            If (!$BDMs) {$BDMs = $Image.BlockDeviceMappings}
            Try {
                ($BDMs | Where-Object {$_.DeviceName -ne $Image.RootDeviceName -and $null -ne $_.Ebs})[0].EBS.VolumeSize = $SecondaryVolumeSize
            } Catch {
                $SecondaryBdm = [Amazon.EC2.Model.BlockDeviceMapping]@{
                    DeviceName = if ($Image.Platform -eq "Windows"){"xvdb"}
                        Else {"/dev/sdb"}
                    Ebs = [Amazon.EC2.Model.EbsBlockDevice]@{
                        VolumeSize = $SecondaryVolumeSize
                        DeleteOnTermination = ($TerminateSecondaryVolume -ne $false)
                    }
                }
                $BDMs += $SecondaryBdm
            }
        }
        $Params    = @{
            Region           = $Region
            MinCount         = $Count
            MaxCount         = $Count
            InstanceType     = $InstanceType
            SecurityGroupId  = $SecurityGroupId
            ImageId          = $ImageId
            SubnetId         = $SubNetId
            KeyName          = $KeyName
        }
            
        if ($Name) {
            $tag1 = @{ Key="Name"; Value=$Name }
            $Tags = @()
            $tagspec1 = New-Object -Type Amazon.EC2.Model.TagSpecification
            $tagspec1.ResourceType = "instance"
            $tagspec1.Tags.Add($tag1)
            $Tags+=$tagspec1
            $tagspec2 = New-Object -Type Amazon.EC2.Model.TagSpecification
            $tagspec2.ResourceType = "volume"
            $tagspec2.Tags.Add($tag1)
            $Tags+=$tagspec2
            $Params.Add("TagSpecification",$Tags)
        }
        If ($SpotInstance) {
            $InstanceMarketOption = @{
                MarketType ="Spot"
            }
            $Params.Add("InstanceMarketOption",$InstanceMarketOption)
        }
            
            If ($UserData64)      {$Params.Add("UserData",$UserData64)}
            If ($InstanceProfile) {$Params.Add("InstanceProfile_Name",$InstanceProfile)}
            If ($BDMs) {$Params.Add("BlockDeviceMapping",$BDMs)}
            
            $InstanceId       = (New-EC2Instance @Params).Instances.InstanceId
                    
        if ($Name -and $Count -eq 1) {
            If ($DomainName) {
                $DNSParams        = @{InstanceId    = $InstanceId}
                if($Region)        {$DNSParams.Add('Region',$Region)}
                $SetDns           = Set-CmEc2DnsName @DNSParams -DomainName $DomainName -InstanceName $Name
                $HostName         = $SetDns.Hostname
            }
        } else {
            $HostName = $RunningInstance.PublicIPAddress
        }
        [PSCustomObject]@{
            InstanceId            = $InstanceId
            Region                = $Region
            Name                  = $Name
            Hostname              = $HostName
            InstanceType          = $InstanceType
            ImageName             = $Image.Name
            ImageId               = $ImageId
            KeyName               = $KeyName
            InstanceProfile       = $InstanceProfile
        }
    }
}