CMAWS.psm1

#Requires -version 3.0
#Requires -Module AwsPowerShell
Function Convert-TextToSpeech        {
<#
.Synopsis
    Converts text files into audio files.
.DESCRIPTION
    Parses the contents of multiple text files and outputs each file to an audio file by using Amazon's Polly service (https://aws.amazon.com/polly/)
 
.NOTES
    Name: Convert-TextToSpeech
    Author: Chad Miles
    DateUpdated: 2017-05-04
    Version: 1.2.0
 
.EXAMPLE
   Convert-TextToSpeech input.txt -Voice Joanna
 
   This will output input.mp3 using Amy voice
.EXAMPLE
   Convert-TextToSpeech *.txt -Voice Amy -OutputFormat ogg
    
   This will convert all the .txt files to .ogg files in Joanna voice.
#>

    [CmdletBinding()]
    Param (
        #The Input file(s), wilcards and just pointing to a folder itself work fine, just know that it will suck in what you ever you give it.
        [Parameter(Mandatory=$true)]
        [string[]]  $InputFiles, 
        #Format of the output, mp3, ogg or pcm
        [string]    $OutputFormat = "mp3",
        #The voice used, default is Amy (English - UK), for all voices please run Get-POLVoice | select -ExpandProperty Id
        [string]    $Voice = "Amy"
    )
    $ErrorActionPreference = "Stop"
    Write-Verbose "Validating Voice"
    $VoiceIds   = (Get-POLVoice).Id.Value
    if ($VoiceIds -notcontains $Voice) {
        Write-Error "$Voice is not a valid voice, Valid Voices are $VoiceIds"
    }
    Write-Verbose "Processing Files"
    $PreText        = '<speak>'
    $PostText       = '<break time="350ms"/></speak>'
    $PollyLimit     = 1000
    $MaxTextLength  = $PollyLimit - ($PreText.Length + $PostText.Length)
    $InputFileNames = (dir $InputFiles).FullName
    Foreach ($InputFile in $InputFileNames) {
        $Text           = Get-Content $InputFile
        $LongLines      = $false
        Foreach ($Line in $Text){
            $FileName       = $InputFile.Name
            $LineLength     = $PreText.Length + $Line.Length + $PostText.Length
            If ($LineLength -ge $PollyLimit){
                $LongLines  = $true
                }   
        }
        If ($LongLines) {
            Write-Warning "$InputFile was skipped as it has lines that are longer than $MaxTextLength characters, which is the longest we can submit to Polly excluding SSML tags."
        } else {
            $DirectoryName  = (dir $InputFile).Directory
            $BaseName       = (dir $InputFile).BaseName
            $OutputFile     = "$DirectoryName\$BaseName.$OutputFormat"
            $OutputStream   = New-Object System.IO.FileStream $OutputFile, Create
            Foreach ($Line in $Text)
            {
                (Get-POLSpeech -Text $PreText$Line$PostText -TextType ssml -VoiceId $Voice -OutputFormat $OutputFormat).AudioStream.CopyTo($OutputStream)
            }
            (Get-POLSpeech -Text '<speak><break time="2s"/></speak>' -TextType ssml -VoiceId $Voice -OutputFormat $OutputFormat).AudioStream.CopyTo($OutputStream)
            $OutputStream.Close()
            $OutputProperties = @{
                InputFile     = $InputFile
                Outputfile    = $OutPutFile
            }
            $OutputObject   = New-Object -TypeName PSObject -Property $OutputProperties
            Write-Output      $OutputObject
        }
    }
}
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 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
   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 -DomainName mydomain.com -OSVerion 2012R2
    
   InstanceID : i-1234567890abcdef
   Region : us-east-1
   Name : MyInstance
   Hostname : MyInstance.Mydomain.com
   InstanceType : t2-micro
   BidPrice :
   OnDemandPrice : 0.017
   Savings : 0 %
   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
   WARNING: Spot Instances not available for T1 and T2 instance types, switching to on demand.
 
   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-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
   BidPrice : 0.0741
   OnDemandPrice : 0.13
   Savings : 55 %
   ImageName : WINDOWS_2016_BASE
   ImageID : ami-58a1a73e
   KeyName : MyKeyPair
 
#>

    [CmdletBinding(SupportsShouldProcess=$true)]
    Param (
        [Parameter(Mandatory=$true)]
        [string] $InstanceType,
        [Parameter(Mandatory=$true)]
        [string] $Region,
        # Applies this name tage to the instance after creation, if -DomainName is specified as well then registers a DNS CNAME for your instance using this name
        [string] $Name,
        # If -Name is also specified then a DNS CNAME is registered, provided you have rights to do so.
        [string] $DomainName,
        # WINDOWS or LINUX, Default is Windows
        [string] $OS         = "Windows",
        # Version of Windows e.g. 2012R2 or 2016. Default is 2012R2
        [string] $OSVersion  = "2016",
        # BASE or SQL_EDITION_YEAR, Must be uppercase
        [string] $AmiEdition = "BASE",
        # 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,
        # What Percrentage to add to the lowest Spot Price to ensure instance's longevity
        [int]    $MarkUp     = 1,
        [Parameter(ValueFromPipeline       =$true,
            ValueFromPipelineByPropertyName=$true)]
        [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.
        [string] $VpcId,
        # Last letter of the Availability Zone like a, b, c, d or e, if none specified, a is used
        [string] $AZSuffix    = "a"    
    )
    [int]$Count               = 1 
    $ErrorActionPreference    = "Stop"
    $AllRegions               = (Get-AWSRegion).Region
    If ($AllRegions -notcontains $Region) {
        Write-Error "$Region is not a valid AWS Region, Valid regions are $AllRegions"
    }
    If ($InstanceType -like "t*" -and $SpotInstance) {
        Write-Warning "Spot Instances not available for T1 and T2 instance types, switching to on demand."
        $SpotInstance = $false
    }
    $AvailabilityZone      = $Region+$AZSuffix
    $OS                    = $OS.ToUpper()
    $AmiEdition            = $AmiEdition.ToUpper()
    $OSVersion             = $OSVersion.ToUpper()
    $Region                = $Region.ToLower()
    $InstanceType          = $InstanceType.ToLower()
    Write-Verbose          "Geting On Demand Instance Pricing"
    $OndemandPrice         = Get-EC2WindowsOndemandPrice -InstanceType $InstanceType -Region $Region
    If ($SpotInstance) {
        Write-Verbose      "Getting Current Spot Price of Instance Type and adding mark up."
        $OSLower           = $OS.Substring(0,1).ToUpper()+$OS.Substring(1).ToLower()
        $CurrentSpotPrice  = ((Get-EC2SpotPriceHistory -Region $Region -StartTime (Get-Date) -InstanceType $InstanceType -ProductDescription $OSLower).Price | Measure-Object -Minimum).Minimum
        $BidPrice          = $CurrentSpotPrice + ($CurrentSpotPrice * ($MarkUp/100))
    
        If ($OndemandPrice -gt $BidPrice) {
            $Savings       = [math]::Round((($OndemandPrice - $BidPrice) / $OnDemandPrice)*100)
            Write-Verbose     "Requesting $Count $InstanceType instance(s) at $ $BidPrice/hour ($Savings% Savings)" 
            
        } else { 
            Write-Error    "OnDemand Pricing ($ $OndemandPrice) is better than Spot Price ($ $BidPrice)"}
    }
    $ImageName     = $OS+"_"+$OSVersion+"_"+$AMIEdition   
    if (!$ImageID) {
        Write-Verbose       "Getting Current AMI for Selected OS"
        $ImageID           = (Get-EC2ImageByName -Region $Region -Name $ImageName).ImageId
    }
    if (!$ImageID) {         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 (!$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 one"}

    If ($SecurityGroupName) {
        Write-Verbose       "Getting Security Group for name $SecurityGroupName"
        $SecurityGroupId   = (Get-EC2SecurityGroup -Region $Region | where {$_.GroupName -eq $SecurityGroupName -and $_.VpcId -eq $VpcId})[0].GroupId
        If (!$SecurityGroupName) {Write-Error "Security Group with $SecurityGroupName cannot be found"}
    } else {
        Write-Verbose       "Getting Security Group for VPC $VpcId"
        $SecurityGroupId   = (Get-EC2SecurityGroup -Region $Region | where {$_.GroupName -eq "default" -and $_.VpcId -eq $VPCId})[0].GroupId
    }
    If (!$SecurityGroupId) {Write-Error "Could not find a Security Group with the name $SecurityGroupName in region $Region"}
    If ($SpotInstance) {
        $Params            = @{
            Region             = $Region
            InstanceCount      = $Count
            SpotPrice          = $BidPrice
            Type               = "one-time"
            LaunchSpecification_ImageId       = $ImageID
            LaunchSpecification_InstanceType  = $InstanceType
            LaunchSpecification_KeyName       = $KeyName
            LaunchSpecification_UserData      = $UserData64
        }
        If ($InstanceProfile){
            $Params.Add("IamInstanceProfile_Name", $InstanceProfile)} 
            
        $SpotRequest       = Request-EC2SpotInstance @Params
        Write-Host          "Waiting for Spot Fufillment"
        $InstanceID        = $false
        While (!$InstanceID){
            Start-Sleep   1
            $InstanceID    = (Get-EC2SpotInstanceRequest $SpotRequest.SpotInstanceRequestId).Instanceid
        }
    } else {
        Write-Verbose       "Getting Subnet for name $AvailabilityZone"
        $SubNetId          = (Get-EC2Subnet -Region $Region | where {$_.AvailabilityZone -eq $AvailabilityZone -and $_.VpcId -eq $VPCid})[0].SubnetId
        $Params            = @{
            Region               = $Region
            MinCount             = $Count
            MaxCount             = $Count
            InstanceType         = $InstanceType
            SecurityGroupId      = $SecurityGroupId
            ImageId              = $ImageID
            SubnetId             = $SubNetId
            KeyName              = $KeyName
            UserData             = $UserData64
        }
        If ($InstanceProfile){
            $Params.Add("InstanceProfile_Name",$InstanceProfile)}
        $NewInstance       = New-EC2Instance @Params
        $InstanceID        = ($NewInstance.Instances).InstanceID
        $Savings           = 0
    }   
    $InstanceInfo          = Get-EC2Instance -InstanceId $InstanceID -Region $Region
    $PublicDNSName         = $InstanceInfo.RunningInstance.PublicDNSName
        
    if ($Name) {
        $Tag          = New-Object Amazon.EC2.Model.Tag
        $Tag.Key      = "Name"
        $Tag.Value    = "$Name"
        Write-Verbose "Applying Name Tag to instance"
        New-EC2Tag -Resource $InstanceID -Tag $Tag -Region $Region
    }

    if ($DomainName -and $Name) {
        $HostName = $Name+"."+$DomainName
        Write-Verbose "Creating DNS entry $HostName"
        Set-R53Record -Domain $DomainName -Type CNAME -Name $Name -Value $PublicDNSName -TTL 30 | Out-Null
        
    } else { $HostName = $PublicDNSName}
    $OutputProperties = @{
        InstanceID            = $InstanceID
        Region                = $Region
        Name                  = $Name
        Hostname              = $HostName
        InstanceType          = $InstanceType
        BidPrice              = $BidPrice
        OnDemandPrice         = $OnDemandPrice
        Savings               = "$Savings %"
        ImageName             = $ImageName
        ImageID               = $ImageID
        KeyName               = $KeyName
        InstanceProfile       = $InstanceProfile
    }
    $OutputObject   = New-Object -TypeName PSObject -Property $OutputProperties
    Write-Output      $OutputObject
}
Function New-CMPassword              {
    <#
    .Synopsis
       Generates random passwords
    .DESCRIPTION
       Generates Random passwords and outputs as text and to Clipboard or just to clipboard
    .EXAMPLE
       PS> New-CMPassword -Easy
       Kobi2858
    .EXAMPLE
       PS> New-CMPassword
       N42v9AjCWF1J
    .EXAMPLE
       PS> New-CMPassword -ClipOnly
       PS>
       Password is only put in clipboard and not displayed on screen.
    .EXAMPLE
       PS> $pass = New-CMPassword
       PS> Set-ADAccountPassword DOMAIN\USERNAME -NewPassword (ConvertTo-SecureString ($pass) -AsPlainText -Force)
       PS> Write-Output "Account password set to $pass"
 
       This will set an AD account's password to a randomly generated password and copy that password to the clipboard
    #>

    [CmdletBinding()]
    [Alias('np')]
    [OutputType([String])]
    Param
    (
        #Creates easy readable and rememberable password like Office 365's password generator
        [Switch]   $Easy, 
        #Enables the use of special characters, like !^&*(%$#)-=_+
        [Switch]   $Specials,
        #Doesn't output the password as text but rather only copies it to Clipboard
        [Switch]   $ClipOnly,
        #Specifies the length of the password. This parameter is ignored when you use the -Easy switch
        [Parameter(Position=0)]
        [int]      $Length = 12,
        #Specifies the ammount of passwords to generate, only the last one will be left in the clipboard
        [int]      $Count = 1
    )
    for ($c = 0 ; $c -lt $Count ; $c++){
        if ($easy -Eq $true) 
        {
            $digits = 48..57
            $UpperConsonants = 66..68 + 70..72 + 74..78 + 80..84 + 86..90
            $LowerConsonants = 98..100 + 102..104 + 106..110 + 112..116 + 118..122
            $LowerVowels = 97, 101, 105, 111, 117

            $first = [char](get-random -count 1 -InputObject $UpperConsonants)
            $second = [char](get-random -count 1 -InputObject $LowerVowels)
            $third = [char](get-random -count 1 -InputObject $LowerConsonants)
            $fourth = [char](get-random -count 1 -InputObject $LowerVowels)
            $numbers = $null
            for ($i=0 ; $i -lt 4; $i++)
            {
                $numbers += [char](get-random -count 1 -InputObject $digits)
            }
            $password = ($first + $second + $third + $fourth + $numbers)
        }
        Else
        {
            $digits = 48..57
            $letters = 65..90 + 97..122
            $specialchar = 33..47 + 58..64 + 91..96 + 123..126
            $password = $null
            for ($i = 0 ; $i -lt $Length ; $i++)
            {
                If ($Specials -eq $true) {$password += [char](get-random -count 1 -InputObject ($digits + $letters + $specialchar))}
                Else {$password += [char](get-random -count 1 -InputObject ($digits + $letters))}
            }
        }
        $password | Set-Clipboard
        If(!$ClipOnly) {$password}
    }
}
Function New-CMEasyPassword          {
    <#
    .Synopsis
       See New-CMPassword Help
 
       Alais for executing New-CMPassword -Easy -Count 1
    #>

    Param(
        [int]      $Count = 1,
        [Switch]   $ClipOnly
    )
    if ($ClipOnly -eq $true) {
        New-CMPassword -Easy -Count $Count -ClipOnly
    } else {
        New-CMPassword -Easy -Count $Count
    }
}
Function Get-EC2WindowsOndemandPrice {
    Param(
        [Parameter(Mandatory=$true)]
        [string] $InstanceType,
        [Parameter(Mandatory=$true)]
        [string] $Region
    )
    $ErrorActionPreference      = "Stop"
    $AllRegions    = (Get-AWSRegion).Region
    If ($AllRegions -notcontains $Region) {Write-Error "$Region is not a valid AWS Region, Valid regions are $AllRegions"}
    $OnDemandPricing    = Invoke-RestMethod -uri http://a0.awsstatic.com/pricing/1/ec2/mswin-od.min.js
    $CurrentYear        = (Get-Date).Year
    $IntroText          = '/*
* This file is intended for use only on aws.amazon.com. We do not guarantee its availability or accuracy.
*
* Copyright '
+$CurrentYear+' Amazon.com, Inc. or its affiliates. All rights reserved.
*/
callback('
 | Out-String
    $OnDemandPricing    = $OnDemandPricing.TrimStart($IntroText)
    $OnDemandPricing    = $OnDemandPricing.TrimEnd(');')
    $PricingObject      = ($OnDemandPricing | ConvertFrom-Json).config.regions
    $RegionPrices       = $PricingObject | where {$_.region -eq $Region}
    $AllInstances       = $RegionPrices.instancetypes.sizes
    $InstanceEntry      = $AllInstances| where {$_.size -eq $InstanceType}
    $Price              = $InstanceEntry.valuecolumns.prices.usd
    Write-Output $Price
}
Function Get-CMEC2Instances          {
    [Cmdletbinding()]
    Param (
        [string[]]$Region
    )
    $ErrorActionPreference = "Stop"
    $AllRegions    = (Get-AWSRegion).Region
    If (!$Region){
        $Region = $AllRegions
        Write-Warning "Getting instances for all regions, May take some time"
    } 
    Foreach ($Reg in $Region) {
        If ($AllRegions -notcontains $Reg) {Write-Error "$Region is not a valid AWS Region, Valid regions are $AllRegions"}
        $Instances = (Get-EC2Instance -Region $Reg).RunningInstance 
        Foreach ($Instance in $Instances) {  
            $Properties    = @{
                Name            = $Instance.Tags | Where-Object {$_.Key -eq "Name"} | Select -ExpandProperty Value
                State           = $Instance.State.Name
                InstanceType    = $Instance.InstanceType
                InstanceID      = $Instance.InstanceID
                Region          = $Reg
                LaunchTime      = $Instance.LaunchTime
                PublicIpAddress = $Instance.PublicIpAddress
            }
            $InstanceObject = New-Object PSObject -Property $Properties
            Write-Output $InstanceObject
        }
    }
    Write-Output $InstancesList
}
Function Set-R53Record               {
<#
.SYNOPSIS
    Made for easier interaction with Amazon Route 53 DNS service.
.DESCRIPTION
    Run the script in CREATE/UPDATE mode in order to add or modify DNS records in Amazon Route 53.
    Requires 4 parameters - Domain name and type, name and the value of DNS record.
.NOTES
    File name : Set-R53Record.ps1
    Author : Sinisa Mikasinovic - six@mypowershell.space
    Date : 02-Jan-17
    Script created as part of a learning tutorial at mypowershell.space.
    http://mypowershell.space/index.php/2017/01/02/amazon-route-53-records/
    All expected functionality may not be there, make sure you give it a test run first.
    Feel free to update/modify. I'd be interested in seeing it improved.
    This script example is provided "AS IS", without warranties or conditions of any kind, either expressed or implied.
    By using this script, you agree that only you are responsible for any resulting damages, losses, liabilities, costs or expenses.
.LINK
    http://mypowershell.space
.EXAMPLE
    Set-R53Record -Domain mypowershell.space -Type A -Name www -Value 1.2.3.4 -TTL 300
    Create an A record to point www.mypowershell.space to IP 1.2.3.4. TTL set to 5 minutes.
.EXAMPLE
    Set-R53Record -Domain mypowershell.space -Type A -Name mail -Value 1.2.3.4 -TTL 3600 -Comment "mail entry"
    Create an A record to point mail.mypowershell.space to IP 1.2.3.4. TTL set to 60 minutes and has an optional comment.
.EXAMPLE
    Set-R53Record -Domain mypowershell.space -Type TXT -Name _amazonses -Value "G3LNeKkT8eYmQLeyAp" -Comment "confirm domain ownership"
    Create a TXT record to set _amazonses.mypowershell.space to "G3LNeKkT8eYmQLeyAp" and confirm domain ownership. Will use default TTL (300) and no comment.
.PARAMETER Domain
    Defines a domain which DNS zone is to be edited:
    1. mypowershell.space
    2. amazon.com
    3. google.com.
    4. facebook.com.
.PARAMETER Type
    Defines a type of a DNS record: A, TXT, MX, CNAME, NS...
    Most likely won't support all. If you mod the script and add functionality, let me know!
.PARAMETER Name
    Defines a name of a DNS record: www, mail, intranet, dev...
.PARAMETER Value
    Defines a value of DNS record:
    1. 192.168.0.1
    2. "ZTJGIJ4OIJS9J3560S"
    Bear in mind which record type is numerical and which textual!
.PARAMETER TTL
    Defines a TTL of DNS record. I shouldn't really need to explain this :-)
    Not mandatory, defaults to 300.
.PARAMETER Comment
    Defines an optional R53 comment.
    Not mandatory, not included if not explicitly defined.
#>

    Param (
        [Parameter(Mandatory=$True)]
        [String]   $Domain,
        [Parameter(Mandatory=$True)]
        [String]   $Type,
        [Parameter(Mandatory=$True)]
        [String]   $Name,
        [Parameter(Mandatory=$True)]
        [String]   $Value,
        [Int]      $TTL = 300,
        [String]   $Comment
    )

    if ($Domain.Substring($Domain.Length-1) -ne ".") {$Domain = $Domain + "."}

    $Change                          = New-Object Amazon.Route53.Model.Change
    # UPSERT: If a resource record set doesn't already exist, AWS creates it. If it does, update it with values in the request.
    $Change.Action                   = "UPSERT"
    $Change.ResourceRecordSet        = New-Object Amazon.Route53.Model.ResourceRecordSet
    $Change.ResourceRecordSet.Name   = "$Name.$Domain"
    $Change.ResourceRecordSet.Type   = $Type
    $Change.ResourceRecordSet.TTL    = $TTL
    $Change.ResourceRecordSet.ResourceRecords.Add(@{Value=if ($Type -eq "TXT") {"""$Value"""} else {$Value}})

    $HostedZone = Get-R53HostedZones | Where-Object {$_.Name -eq $Domain}

    $Parameters = @{
        HostedZoneId        = $HostedZone.Id
        ChangeBatch_Change  = $Change 
        ChangeBatch_Comment = $Comment
    }
    Edit-R53ResourceRecordSet @Parameters
}
Function Start-CMInstance            {
    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory=$true,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true
        )]
        [string[]]  $InstanceID,
        [string]    $DomainName,
        [string]    $Region
    )
    BEGIN {
        $ErrorActionPreference      = "Stop"
        If ($Region){
            $AllRegions = (Get-AWSRegion).Region
            If ($AllRegions -notcontains $Region) { 
                Write-Error "$Region is not a valid AWS Region, Valid regions are $AllRegions"
            }
        }
    }
    PROCESS {
        foreach ($Instance in $InstanceID){
            $Parameters       = @{InstanceID  = $Instance}
            if ($Region)        {$Parameters.add('Region',$Region)}
            $StartingInstance = Start-EC2Instance @Parameters
            $PreviousState    = ($StartingInstance).PreviousState.Name.Value
            $CurrentState     = ($StartingInstance).CurrentState.Name.Value
            Write-Host "Instance $InstanceID $PreviousState --> $CurrentState"
            Start-Sleep 1
            If ($DomainName -and (Get-Command Set-R53Record)){
                Write-Verbose "Getting Public DNS name"
                $RunningInstance  = (Get-EC2Instance @Parameters).RunningInstance
                $NameTag          = $RunningInstance.Tags | Where-Object {$_.Key -eq "Name"} | Select -ExpandProperty Value
                If (!$NameTag) {
                    Write-Warning "No Name Tag on instance $Instance, can't apply DNS Name."
                } Else {
                    $PublicCName      = $RunningInstance.PublicDnsName
                    Write-Output "Updating R53 CName $Name.$DomainName"
                    Set-R53Record -Domain $DomainName -Type CNAME -Name $NameTag -Value $PublicCName -TTL 30 | Out-Null
                }
            }
        }
    }
    END {}
}
Function Stop-CMAllInstances         {
    $AllRegions = (Get-AWSRegion).Region
    foreach ($Region in $AllRegions) {
        $Instances = (Get-EC2Instance -Region $Region).RunningInstance
        foreach ($Instance in $Instances)
        {
            $Tags          = ($Instances.tags).Key
            $InstanceState = ($Instances.State).Name
            if ($Tags -notcontains "Persistent" -and $InstanceState -ne "stopped")
            {
                $InstanceID = ($Instances).InstanceId
                Write-Output "Stopping $InstanceID"
                Stop-EC2Instance -InstanceId $InstanceID -Region $Region | Out-Null
            }
        }
    }
}
Function Send-SSMPowerShellCommand   {
<#
.Synopsis
    Sends PowerShell Commands to a AWS EC2 SSM Managed Instance and displays results.
.DESCRIPTION
    Sends PowerShell Commands to a SSM Managed Instance and waits for the command to finish then brings back the output from that command to the console
 
.NOTES
    Name: Send-SSMPowerShellCommand
    Author: Chad Miles
    DateUpdated: 2017-05-06
    Version: 1.0.1
 
.EXAMPLE
   C:\> $SSMCommands = {Get-Service Bits}
   C:\> Send-SSMPowerShellCommand -InstanceID i-abcdef1234567890 -Command $SSMCommands | Format-List
    
   InstanceID : i-abcdef1234567890
   Output :
                Status Name DisplayName
                ------ ---- -----------
                Running BITS Background Intelligent Transfer Ser...
    
.EXAMPLE
   C:\> $SSMCommands = {Get-Service Bits}
   C:\> Send-SSMPowerShellCommand -InstanceID i-abcdef1234567890,i-1234567890abcdef -Region us-west-2 -Command $SSMCommands | Format-List
    
   InstanceID : i-abcdef1234567890
   Output :
                Status Name DisplayName
                ------ ---- -----------
                Running BITS Background Intelligent Transfer Ser...
 
   InstanceID : i-1234567890abcdef
   Output :
                Status Name DisplayName
                ------ ---- -----------
                Running BITS Background Intelligent Transfer Ser...
   .EXAMPLE
   C:\> $SSMCommands = {Get-Service Bits}
   C:\> Get-SSMInstanceInformation | Send-SSMPowerShellCommand -Command $SSMCommands | Format-List
    
   InstanceID : {i-abcdef1234567890}
   Output :
                Status Name DisplayName
                ------ ---- -----------
                Running BITS Background Intelligent Transfer Ser...
 
   InstanceID : {i-1234567890abcdef}
   Output :
                Status Name DisplayName
                ------ ---- -----------
                Running BITS Background Intelligent Transfer Ser...
 
 
#>
    
    [Cmdletbinding()]
    Param(
        [Parameter(Mandatory               =$true,
            ValueFromPipeline              =$true,
            ValueFromPipelineByPropertyName=$true)]
        [string[]] $InstanceId,
        [string]   $Region,
        [Parameter(Mandatory               =$true)]
        [string]   $Command
    )
    BEGIN {
        $ErrorActionPreference      = "Stop"
        If ($Region){
            $AllRegions    = (Get-AWSRegion).Region
            If ($AllRegions -notcontains $Region) {
                Write-Error "$Region is not a valid AWS Region, Valid regions are $AllRegions"
            }
        }
    }
    PROCESS {
        foreach ($Instance in $InstanceId){
            $Parameters    = @{InstanceID  = $Instance}
            if ($Region)     {$Parameters.add('Region',$Region)}
            $CommandID     = (Send-SSMCommand @Parameters -DocumentName "AWS-RunPowerShellScript" -Parameter @{commands="$Command"}).CommandId
            $SSMRunStatus  = $false
            While (!$SSMRunStatus) {
                Start-Sleep -Seconds 1
                $SSMCommandStatus    = (Get-SSMCommand @Parameters -CommandId $CommandID).Status.Value
                if ($SSMCommandStatus -eq "Success") {
                    $SSMRunStatus    = $true
                    $SSMOutPut       = (Get-SSMCommandInvocationDetail @Parameters -CommandId $CommandID).StandardOutputContent
                } elseif ($SSMCommandStatus -eq "Failed") {
                    $SSMRunStatus    = $true
                    Write-Error "SSM Run Command to Failed"
                }
            }
            $OutputProperties = @{
                Output        = $SSMOutPut
                InstanceID    = $InstanceID
            }
            $OutputObject     = New-Object -TypeName PSObject -Property $OutputProperties
            Write-Output      $OutputObject
        }
    }
    END{}
}
Function Set-CMEC2InstanceType       {
<#
.Synopsis
    Changes an EC2 instance type leaving it in the same state
.DESCRIPTION
    Changes the EC2 instance type of a running instance by shutting it down, changing the instance type and starting it again in one cmdlet or leaves it stopped if it was stopped.
 
.NOTES
    Name: Set-CMEC2InstanceType
    Author: Chad Miles
    DateUpdated: 2017-05-06
    Version: 1.0.0
 
.EXAMPLE
   C:\> Set-CMEC2InstanceType -InstanceID i-1234567890abcdef -InstanceType m4.large
    
   Shutting down Instance i-1234567890abcdef
   ........
 
   InstanceID Status InstanceType
   ---------- ------ ------------
   i-1234567890abcdef running m4.large
 
   In this example one instance was specified for changing running and was stopped, changed and started again.
    
.EXAMPLE
   C:\> (Get-EC2instances).instances | Where {$_.InstanceType -eq "t2.large"} | Set-CMEC2InstanceType -InstanceType m4.large
 
   Shutting down Instance i-1234567890fedcba
   ........
 
   InstanceID Status InstanceType
   ---------- ------ ------------
   i-1234567890fedcba running m4.large
   i-1234567890abcdef stopped m4.large
 
   In this example all the instances that were of instance type t2.large where changed to m4.large, of which there were two. However, one of the instances was in a stopped state and the other was running and they were both left in their respective states.
   
  #>
    
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory                = $true,
            ValueFromPipeline               = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string[]] $InstanceId,
        [string]   $Region,
        [Parameter(Mandatory                = $true)]
        [string]   $InstanceType
    )
    BEGIN {
        $ErrorActionPreference      = "Stop"
        If ($Region){
            $AllRegions    = (Get-AWSRegion).Region
            If ($AllRegions -notcontains $Region) {
                Write-Error "$Region is not a valid AWS Region, Valid regions are $AllRegions"
            }
        }
    }
    PROCESS {
        foreach ($Instance in $InstanceId){
            $Parameters         = @{InstanceID  = $Instance}
            if ($Region)          {$Parameters.add('Region',$Region)}
            $InstanceStartInfo  = (Get-EC2Instance @Parameters).Instances
            $InstanceStatus     = $InstanceStartInfo.State.Name
            If ($InstanceInfo.InstanceType -eq $InstanceType) {
                Write-Warning "Instance $Instance is already of instance type $InstanceType, skipping"
            } else {
                if ($InstanceStatus -ne "stopped"){
                    $StopInstance       = Stop-EC2Instance @Parameters
                    Write-Host            "Stopping Instance $Instance"
                    $InstanceStatus     = $InstanceStartInfo.State.Name
                    while ($InstanceStatus -ne "stopped"){
                        Start-Sleep -Seconds 3
                        $InstanceStatus = (Get-EC2Instance @Parameters).Instances.State.Name
                        Write-Host "." -NoNewline
                    }
                    Write-Host ""
                }
                Write-Verbose "Editing Instance Type"
                Edit-EC2InstanceAttribute @Parameters -InstanceType $InstanceType
                if ($InstanceStartInfo.State.Name -eq "running") { 
                    Write-Verbose "Starting Instance"
                    $StartInstance = Start-EC2Instance @Parameters
                    $InstanceStatus = $StartInstance.CurrentState.Name
                }
                $OutputProperties = @{
                    InstanceID    = $Instance
                    Status        = $InstanceStatus
                    InstanceType  = $InstanceType
                }
                $OutputObject     = New-Object -TypeName PSObject -Property $OutputProperties
                Write-Output      $OutputObject
            }
        }
    }
    END{}
}
Function Connect-RemoteDesktop       {
<#
.SYNOPSIS
Function to connect an RDP session without the password prompt
     
.DESCRIPTION
This function provides the functionality to start an RDP session without having to type in the password
     
.PARAMETER ComputerName
This can be a single computername or an array of computers to which RDP session will be opened
 
.PARAMETER User
The user name that will be used to authenticate
 
.PARAMETER Password
The password that will be used to authenticate
 
.PARAMETER Credential
The PowerShell credential object that will be used to authenticate against the remote system
 
.PARAMETER Admin
Sets the /admin switch on the mstsc command: Connects you to the session for administering a server
 
.PARAMETER MultiMon
Sets the /multimon switch on the mstsc command: Configures the Remote Desktop Services session monitor layout to be identical to the current client-side configuration
 
.PARAMETER FullScreen
Sets the /f switch on the mstsc command: Starts Remote Desktop in full-screen mode
 
.PARAMETER Public
Sets the /public switch on the mstsc command: Runs Remote Desktop in public mode
 
.PARAMETER Width
Sets the /w:<width> parameter on the mstsc command: Specifies the width of the Remote Desktop window
 
.PARAMETER Height
Sets the /h:<height> parameter on the mstsc command: Specifies the height of the Remote Desktop window
 
.NOTES
Name: Connect-RemoteDesktop
Author: Jaap Brasser
DateUpdated: 2016-10-28
Version: 1.2.5
Blog: http://www.jaapbrasser.com
 
.LINK
http://www.jaapbrasser.com
 
.EXAMPLE
. .\Connect-RemoteDesktop.ps1
     
Description
-----------
This command dot sources the script to ensure the Connect-Mstsc function is available in your current PowerShell session
 
.EXAMPLE
Connect-RemoteDesktop -ComputerName server01 -User contoso\jaapbrasser -Password (ConvertTo-SecureString 'supersecretpw' -AsPlainText -Force)
 
Description
-----------
A remote desktop session to server01 will be created using the credentials of contoso\jaapbrasser
 
.EXAMPLE
Connect-RemoteDesktop server01,server02 contoso\jaapbrasser (ConvertTo-SecureString 'supersecretpw' -AsPlainText -Force)
 
Description
-----------
Two RDP sessions to server01 and server02 will be created using the credentials of contoso\jaapbrasser
 
.EXAMPLE
server01,server02 | Connect-RemoteDesktop -User contoso\jaapbrasser -Password supersecretpw -Width 1280 -Height 720
 
Description
-----------
Two RDP sessions to server01 and server02 will be created using the credentials of contoso\jaapbrasser and both session will be at a resolution of 1280x720.
 
.EXAMPLE
server01,server02 | Connect-Mstsc -User contoso\jaapbrasser -Password (ConvertTo-SecureString 'supersecretpw' -AsPlainText -Force) -Wait
 
Description
-----------
RDP sessions to server01 will be created, once the mstsc process is closed the session next session is opened to server02. Using the credentials of contoso\jaapbrasser and both session will be at a resolution of 1280x720.
 
.EXAMPLE
Connect-Mstsc -ComputerName server01:3389 -User contoso\jaapbrasser -Password (ConvertTo-SecureString 'supersecretpw' -AsPlainText -Force) -Admin -MultiMon
 
Description
-----------
A RDP session to server01 at port 3389 will be created using the credentials of contoso\jaapbrasser and the /admin and /multimon switches will be set for mstsc
 
.EXAMPLE
Connect-Mstsc -ComputerName server01:3389 -User contoso\jaapbrasser -Password (ConvertTo-SecureString 'supersecretpw' -AsPlainText -Force) -Public
 
Description
-----------
A RDP session to server01 at port 3389 will be created using the credentials of contoso\jaapbrasser and the /public switches will be set for mstsc
 
.EXAMPLE
Connect-Mstsc -ComputerName 192.168.1.10 -Credential $Cred
 
Description
-----------
A RDP session to the system at 192.168.1.10 will be created using the credentials stored in the $cred variable.
 
.EXAMPLE
Get-AzureVM | Get-AzureEndPoint -Name 'Remote Desktop' | ForEach-Object { Connect-Mstsc -ComputerName ($_.Vip,$_.Port -join ':') -User contoso\jaapbrasser -Password (ConvertTo-SecureString 'supersecretpw' -AsPlainText -Force) }
 
Description
-----------
A RDP session is started for each Azure Virtual Machine with the user contoso\jaapbrasser and password supersecretpw
 
.EXAMPLE
PowerShell.exe -Command "& {. .\Connect-Mstsc.ps1; Connect-Mstsc server01 contoso\jaapbrasser (ConvertTo-SecureString 'supersecretpw' -AsPlainText -Force) -Admin}"
 
Description
-----------
An remote desktop session to server01 will be created using the credentials of contoso\jaapbrasser connecting to the administrative session, this example can be used when scheduling tasks or for batch files.
#>

    [cmdletbinding(SupportsShouldProcess,DefaultParametersetName='UserPassword')]
    param (
        [Parameter(Mandatory=$true,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true,
            Position=0)]
        [Alias('CN')]
            [string[]]     $ComputerName,
        [Parameter(ParameterSetName='UserPassword',Mandatory=$true,Position=1)]
        [Alias('U')] 
            [string]       $User,
        [Parameter(ParameterSetName='UserPassword',Mandatory=$true,Position=2)]
        [Alias('P')] 
            [string]       $Password,
        [Parameter(ParameterSetName='Credential',Mandatory=$true,Position=1)]
        [Alias('C')]
            [PSCredential] $Credential,
        [Alias('A')]
            [switch]       $Admin,
        [Alias('MM')]
            [switch]       $MultiMon,
        [Alias('F')]
            [switch]       $FullScreen,
        [Alias('Pu')]
            [switch]       $Public,
        [Alias('W')]
            [int]          $Width,
        [Alias('H')]
            [int]          $Height,
        [Alias('WT')]
            [switch]       $Wait
    )

    begin {
        [string]$MstscArguments = ''
        switch ($true) {
            {$Admin}      {$MstscArguments += '/admin '}
            {$MultiMon}   {$MstscArguments += '/multimon '}
            {$FullScreen} {$MstscArguments += '/f '}
            {$Public}     {$MstscArguments += '/public '}
            {$Width}      {$MstscArguments += "/w:$Width "}
            {$Height}     {$MstscArguments += "/h:$Height "}
        }

        if ($Credential) {
            $User     = $Credential.UserName
            $Password = $Credential.GetNetworkCredential().Password
        }
    }
    process {
        foreach ($Computer in $ComputerName) {
            $ProcessInfo = New-Object System.Diagnostics.ProcessStartInfo
            $Process = New-Object System.Diagnostics.Process
            
            # Remove the port number for CmdKey otherwise credentials are not entered correctly
            if ($Computer.Contains(':')) {
                $ComputerCmdkey = ($Computer -split ':')[0]
            } else {
                $ComputerCmdkey = $Computer
            }

            $ProcessInfo.FileName    = "$($env:SystemRoot)\system32\cmdkey.exe"
            $ProcessInfo.Arguments   = "/generic:TERMSRV/$ComputerCmdkey /user:$User /pass:$($Password)"
            $ProcessInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
            $Process.StartInfo       = $ProcessInfo
            if ($PSCmdlet.ShouldProcess($ComputerCmdkey,'Adding credentials to store')) {
                [void]$Process.Start()
            }

            $ProcessInfo.FileName    = "$($env:SystemRoot)\system32\mstsc.exe"
            $ProcessInfo.Arguments   = "$MstscArguments /v $Computer"
            $ProcessInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Normal
            $Process.StartInfo       = $ProcessInfo
            if ($PSCmdlet.ShouldProcess($Computer,'Connecting mstsc')) {
                [void]$Process.Start()
                if ($Wait) {
                    $null = $Process.WaitForExit()
                }       
            }
        }
    }
}


Set-Alias li   Get-CMEC2Instances 
Set-Alias tts  Convert-TextToSpeech
Set-Alias npe  New-CMEasyPassword