├── .editorconfig ├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── CloudRemoting-sandbox.ps1 ├── CloudRemoting.psd1 ├── CloudRemoting.psm1 ├── Examples ├── pssession.examples.ps1 └── rdp.examples.ps1 ├── Functions ├── AWS │ ├── Enter-EC2PSSession.ps1 │ ├── Enter-EC2RdpSession.ps1 │ ├── Get-EC2Credential.ps1 │ ├── Get-EC2InstanceAddress.ps1 │ ├── New-EC2PSSession.ps1 │ ├── PemFile │ │ ├── Clear-DefaultEC2PemFile.ps1 │ │ ├── Get-DefaultEC2PemFile.ps1 │ │ ├── Set-DefaultEC2PemFile.ps1 │ │ └── Test-EC2PemFile.ps1 │ └── SSM │ │ ├── Clear-DefaultSSMOutput.ps1 │ │ ├── Get-SSMCommandResult.ps1 │ │ ├── Invoke-SSMCommand.ps1 │ │ └── Set-DefaultSSMOutput.ps1 ├── Common.ps1 ├── Enter-RdpSession.ps1 └── Expand-Object.ps1 ├── Images ├── ec2_pssession.gif ├── ec2_rdp.gif └── ssm_command.gif ├── LICENSE ├── README.md ├── Tests ├── Module │ ├── Module.tests.ps1 │ ├── ScriptAnalyzer.tests.ps1 │ └── expected_commands.csv ├── PemFile │ ├── PemFile.tests.ps1 │ ├── Set-DefaultEC2PemFile.tests.ps1 │ ├── Test-EC2PemFile.tests.ps1 │ ├── empty.txt │ └── notempty.txt └── TestCommon.ps1 └── appveyor.yml /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Default to all files 7 | [*] 8 | end_of_line = crlf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | # Powershell source files 13 | [*.{ps1,psm1,psd1}] 14 | indent_style = space 15 | indent_size = 4 16 | 17 | [*.{xml,ps1xml,config,json}] 18 | indent_style = space 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | *.ps1 text 4 | *.psm1 text 5 | *.psd1 text 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode/ 2 | /build/ 3 | Test.xml 4 | vendor/packages/ 5 | *.sandbox.ps1 6 | TestsResults.xml 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 (Mar 10, 2016) 2 | - Initial version of `CloudRemoting` to establish `PSSession` and `RdpSession` to 3 | EC2 Instances with an EC2 PrivateKey 4 | 5 | ## 0.1.0 (Apr 30, 2016) 6 | - Finalizing module, examples and docs for release 7 | -------------------------------------------------------------------------------- /CloudRemoting-sandbox.ps1: -------------------------------------------------------------------------------- 1 | Remove-Module CloudRemoting -Force -ErrorAction SilentlyContinue 2 | Import-Module $PSScriptRoot -Verbose 3 | cd $PSScriptRoot 4 | -------------------------------------------------------------------------------- /CloudRemoting.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for 'CloudRemoting' module 3 | # Created by: Akos Murati 4 | # Generated on: 02/28/2016 5 | # 6 | 7 | @{ 8 | 9 | # Script module or binary module file associated with this manifest. 10 | RootModule = 'CloudRemoting.psm1' 11 | 12 | # Version number of this module. 13 | ModuleVersion = '0.6.3.0' 14 | 15 | # ID used to uniquely identify this module 16 | GUID = 'd58ec090-b8b8-40ee-8292-049de3f17f4c' 17 | 18 | # Author of this module 19 | Author = 'Akos Murati (akos@murati.hu)' 20 | 21 | # Company or vendor of this module 22 | CompanyName = 'murati.hu' 23 | 24 | # Copyright statement for this module 25 | Copyright = '(c) 2016 murati.hu . All rights reserved.' 26 | 27 | # Description of the functionality provided by this module 28 | Description = @' 29 | AWS EC2 KeyPair based passwordless Rdp and PSRemoting module to support 30 | scripted and automated remote management of EC2 Instances in Amazon AWS. 31 | 32 | The module also provides an Invoke-Command like cmdlet for AWS SSM Run command 33 | that fully integrates to PowerShell with optional CliXml serialization. 34 | '@ 35 | 36 | # Minimum version of the Windows PowerShell engine required by this module 37 | # PowerShellVersion = '' 38 | 39 | # Name of the Windows PowerShell host required by this module 40 | # PowerShellHostName = '' 41 | 42 | # Minimum version of the Windows PowerShell host required by this module 43 | # PowerShellHostVersion = '' 44 | 45 | # Minimum version of Microsoft .NET Framework required by this module 46 | # DotNetFrameworkVersion = '' 47 | 48 | # Minimum version of the common language runtime (CLR) required by this module 49 | # CLRVersion = '' 50 | 51 | # Processor architecture (None, X86, Amd64) required by this module 52 | # ProcessorArchitecture = '' 53 | 54 | # Modules that must be imported into the global environment prior to importing this module 55 | # RequiredModules = @() 56 | 57 | # Assemblies that must be loaded prior to importing this module 58 | # RequiredAssemblies = @() 59 | 60 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 61 | # ScriptsToProcess = @() 62 | 63 | # Type files (.ps1xml) to be loaded when importing this module 64 | # TypesToProcess = @() 65 | 66 | # Format files (.ps1xml) to be loaded when importing this module 67 | # FormatsToProcess = @() 68 | 69 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 70 | # NestedModules = @() 71 | 72 | # Functions to export from this module 73 | FunctionsToExport = @( 74 | 'Enter-RdpSession' 75 | 'Enter-EC2RdpSession' 76 | 77 | 'New-EC2PSSession' 78 | 'Enter-EC2PSSession' 79 | 80 | 'Get-EC2Credential' 81 | 'Get-EC2InstanceAddress' 82 | 83 | 'Clear-DefaultEC2PemFile' 84 | 'Get-DefaultEC2PemFile' 85 | 'Set-DefaultEC2PemFile' 86 | 'Test-EC2PemFile' 87 | 88 | 'Set-DefaultSSMOutput' 89 | 'Clear-DefaultSSMOutput' 90 | 'Invoke-SSMCommand' 91 | 92 | 'Expand-Object' 93 | ) 94 | 95 | # Cmdlets to export from this module 96 | CmdletsToExport = @() 97 | 98 | # Variables to export from this module 99 | VariablesToExport = @() 100 | 101 | # Aliases to export from this module 102 | AliasesToExport = @( 103 | 'rdp' 104 | 'ec2rdp' 105 | 'ec2sn' 106 | 'ssm' 107 | ) 108 | 109 | # DSC resources to export from this module 110 | #DscResourcesToExport = @() 111 | 112 | # List of all modules packaged with this module 113 | ModuleList = @() 114 | 115 | # List of all files packaged with this module 116 | # FileList = @() 117 | 118 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 119 | PrivateData = @{ 120 | 121 | PSData = @{ 122 | 123 | # Tags applied to this module. These help with module discovery in online galleries. 124 | Tags = @( 125 | 'Amazon' 126 | 'AWS' 127 | 'EC2', 128 | 'Instance', 129 | 130 | 'KeyPair', 131 | 'Pem', 132 | 'PemFile', 133 | 'PrivateKey', 134 | 'Private','Key', 135 | 'Password-less' 136 | 'Administrator', 137 | 'Credential', 138 | 139 | 'Remoting', 140 | 'PSRemoting', 141 | 'winrm', 142 | 'winrs', 143 | 'PSSession', 144 | 'Remote', 145 | 146 | 'cmdkey', 147 | 'mstsc', 148 | 'rdp', 149 | 'rds', 150 | 'ts', 151 | 'Invoke','SSM','Run','Command', 152 | 'Enter','Remote','Desktop', 153 | 'Terminal','Services','Client', 154 | 'Windows','Credential','Store' 155 | ) 156 | 157 | # A URL to the license for this module. 158 | LicenseUri = 'https://github.com/murati-hu/CloudRemoting/LICENSE' 159 | 160 | # A URL to the main website for this project. 161 | ProjectUri = 'https://github.com/murati-hu/CloudRemoting' 162 | 163 | # A URL to an icon representing this module. 164 | # IconUri = '' 165 | 166 | # ReleaseNotes of this module 167 | ReleaseNotes = 'https://github.com/murati-hu/CloudRemoting/CHANGELOG.md' 168 | 169 | } # End of PSData hashtable 170 | 171 | } # End of PrivateData hashtable 172 | 173 | # HelpInfo URI of this module 174 | HelpInfoURI = 'https://github.com/murati-hu/CloudRemoting' 175 | 176 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 177 | # DefaultCommandPrefix = '' 178 | 179 | } 180 | -------------------------------------------------------------------------------- /CloudRemoting.psm1: -------------------------------------------------------------------------------- 1 | Write-Verbose "CloudRemoting module" 2 | 3 | if (Get-Module AWSPowershell -ListAvailable) { 4 | Import-Module AWSPowershell 5 | } 6 | elseif (Get-Module AWSPowerShell.NetCore -ListAvailable) { 7 | Import-Module AWSPowerShell.NetCore 8 | } 9 | else { 10 | Write-Warning "AWSPowershell or AWSPowerShell.NetCore is not found, but some of the cmdlets requires it." 11 | Write-Warning "Please make sure you have installed it for proper functioning." 12 | Write-Warning "You can install it by 'PowerShellGet\Install-Module AWSPowershell -Scope CurrentUser' or by 'https://s3.amazonaws.com/aws-cli/AWSCLI64.msi'" 13 | } 14 | 15 | $functionFilter = Join-Path $PSScriptRoot "Functions\*.ps1" 16 | Get-ChildItem -Path $functionFilter -Recurse | Foreach-Object { 17 | Write-Verbose "Loading file $($_.Name).." 18 | . $_.FullName 19 | } 20 | -------------------------------------------------------------------------------- /Examples/pssession.examples.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Demo script for opening admin PSSessions to AWS EC2 instances 4 | 5 | .NOTES 6 | Please set parameters explicitly, otherwise the demo will 7 | default to the first random EC2 instance based on your 8 | AWSPowershell module defaults. 9 | #> 10 | 11 | [CmdletBinding()] 12 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost","")] 13 | param( 14 | [string]$InstanceId, 15 | [string]$Region=$(Get-DefaultAWSRegion | Select-Object -ExpandProperty Region), 16 | [string]$KeyPath="~/.ssh", 17 | [string]$PemFile=$(Join-Path $KeyPath "aws_dev_keypair.pem") 18 | ) 19 | 20 | function Get-RandomEc2InstanceForDemo { 21 | # If the demo parameters are not set 22 | # it will try to get a random EC2 instance 23 | 24 | if ([string]::IsNullOrEmpty($instanceId)) { 25 | 26 | $random = Get-EC2Instance | Select-Object -ExpandProperty Instances | Sort-Object LaunchTime -Descending | Select-Object -First 1 27 | 28 | $script:InstanceId=$random.InstanceId 29 | $script:Region = $random.Placement.AvailabilityZone -replace '\w$','' 30 | $Script:PemFile = Join-Path $KeyPath "$($random.KeyName).pem" 31 | } 32 | } 33 | 34 | # Import modules 35 | Import-Module AWSPowerShell 36 | Remove-Module CloudRemoting -ErrorAction SilentlyContinue 37 | Import-Module (Split-Path $PSScriptRoot -Parent) # CloudRemoting 38 | 39 | Get-RandomEc2InstanceForDemo # If parameters not specifies an exact instance 40 | 41 | Write-Host "Using instance $InstanceId @ $Region" -ForegroundColor Cyan 42 | Write-Warning "Please make sure $PemFile keypair is in place." 43 | 44 | #region AWS EC2 Instance RDP Session 45 | Write-Host "Enter Administrator EC2 PSsession..." -ForegroundColor Cyan 46 | 47 | Read-Host " by InstanceId and Region - press ENTER to continue..." | Out-Null 48 | New-EC2PSSession -InstanceId $InstanceId -Region $Region -PemFile $PemFile 49 | 50 | 51 | Read-Host " by InstanceObject from pipe press ENTER to continue..." | Out-Null 52 | $instance = Get-EC2Instance -Instance $InstanceId -Region $Region 53 | $session = $instance | New-EC2PSSession -PemFile $PemFile 54 | 55 | $session 56 | Read-Host "ENTER to enter and then EXIT to close PSSession..." | Out-Null 57 | Enter-PSSession -Session $session 58 | 59 | #endregion 60 | -------------------------------------------------------------------------------- /Examples/rdp.examples.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Demo script for opening RDP sessions to AWS EC2 instances 4 | 5 | .NOTES 6 | Please set parameters explicitly, otherwise the demo will 7 | default to the first random EC2 instance based on your 8 | AWSPowershell module defaults. 9 | #> 10 | 11 | [CmdletBinding()] 12 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost","")] 13 | param( 14 | [string]$InstanceId, 15 | [string]$Region=$(Get-DefaultAWSRegion | Select-Object -ExpandProperty Region), 16 | [string]$KeyPath="~/.ssh", 17 | [string]$PemFile=$(Join-Path $KeyPath "aws_dev_keypair.pem") 18 | ) 19 | 20 | function Get-RandomEc2InstanceForDemo { 21 | # If the demo parameters are not set 22 | # it will try to get a random EC2 instance 23 | 24 | if ([string]::IsNullOrEmpty($instanceId)) { 25 | 26 | $random = Get-EC2Instance | Select-Object -ExpandProperty Instances | Sort-Object LaunchTime -Descending | Select-Object -First 1 27 | 28 | $script:InstanceId=$random.InstanceId 29 | $script:Region = $random.Placement.AvailabilityZone -replace '\w$','' 30 | $Script:PemFile = Join-Path $KeyPath "$($random.KeyName).pem" 31 | } 32 | } 33 | 34 | # Import modules 35 | Import-Module AWSPowerShell 36 | Remove-Module CloudRemoting -ErrorAction SilentlyContinue 37 | Import-Module (Split-Path $PSScriptRoot -Parent) # CloudRemoting 38 | 39 | Get-RandomEc2InstanceForDemo # If parameters not specifies an exact instance 40 | 41 | Write-Host "Using instance $InstanceId @ $Region" -ForegroundColor Cyan 42 | Write-Warning "Please make sure $PemFile keypair is in place." 43 | 44 | #region AWS EC2 Instance RDP Session 45 | Write-Host "Admin EC2 RDP session..." -ForegroundColor Cyan 46 | 47 | Read-Host " by InstanceId and Region - press ENTER to continue..." | Out-Null 48 | Enter-EC2RdpSession -InstanceId $InstanceId -Region $Region -PemFile $PemFile 49 | 50 | 51 | Read-Host " by InstanceObject from pipe press ENTER to continue..." | Out-Null 52 | $instance = Get-EC2Instance -Instance $InstanceId -Region $Region 53 | $instance | Enter-EC2RdpSession -PemFile $PemFile 54 | # or you can use 'ec2rdp' alias 55 | 56 | #endregion 57 | 58 | #region Custom RDP Session 59 | 60 | Write-Host "Custom RDP session..." -ForegroundColor Cyan 61 | Read-Host " by Name and custom credential object - press ENTER to continue..." | Out-Null 62 | 63 | $credential = Get-Credential 64 | Enter-RdpSession -Credential $credential -CleanupCredentials # -Computer 65 | 66 | # Or you can use the 'rdp' alias 67 | # rdp 'app01.azure.local' -Credential $credential -CleanupCredentials 68 | 69 | #endregion 70 | -------------------------------------------------------------------------------- /Functions/AWS/Enter-EC2PSSession.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Opens a PSSession to an Ec2 instance with Administrator credentials 4 | .DESCRIPTION 5 | The cmdlet accepts pipeline input of EC2 instances and requires a 6 | private-key file to decrypt and logon with the administrator credentials. 7 | 8 | .PARAMETER InstanceId 9 | Mandatory - EC2 Instance Id for the target machine 10 | .PARAMETER Region 11 | Mandatory - Region parameter for the EC2 Instance if -InstanceID is 12 | specified. 13 | 14 | .PARAMETER Reservation 15 | Accepts an EC2 Reservation pipeline input from Get-Ec2Instance output. 16 | .PARAMETER Instance 17 | Accepts an Amazon EC2 Instance object from the pipeline 18 | .PARAMETER PemFile 19 | Mandatory - Path to the PrivateKey file to decrypt 20 | 21 | .EXAMPLE 22 | Get-Ec2Instance i-2492acfc | Enter-EC2PSSession -PemFile '~/ssh/dev.pem' 23 | .EXAMPLE 24 | Enter-EC2PSSession -Verbose -InstanceId i-2492acfc -Region us-west-2 -PemFile '~/ssh/dev.pem' 25 | 26 | #> 27 | function Enter-EC2PSSession { 28 | [CmdletBinding(DefaultParameterSetName='ByInstanceId')] 29 | param( 30 | [Parameter(Mandatory=$true,ParameterSetName="ByInstanceId")] 31 | [string]$InstanceId, 32 | 33 | [Parameter(Mandatory=$true,ParameterSetName="ByInstanceId")] 34 | [string]$Region, 35 | 36 | [Parameter(Mandatory=$true,ParameterSetName="ByReservationObject", ValueFromPipeline=$true)] 37 | [Amazon.EC2.Model.Reservation]$Reservation, 38 | 39 | [Parameter(Mandatory=$true,ParameterSetName="ByInstanceObject", ValueFromPipeline=$true)] 40 | [Amazon.EC2.Model.Instance[]]$Instance, 41 | 42 | [Parameter()] 43 | [ValidateNotNullOrEmpty()] 44 | [string]$PemFile=$script:DefaultEc2PemFile, 45 | 46 | [Parameter()] 47 | [ValidateSet($null, 'PrivateIpAddress','PublicIpAddress','PrivateDnsName','PublicDnsName')] 48 | [string]$AddressProperty 49 | ) 50 | 51 | Begin { Test-EC2PemFile -PemFile $PemFile -ErrorAction Stop | Out-Null } 52 | 53 | Process { 54 | if ($InstanceId) { $Reservation = Get-EC2Instance -Instance $InstanceId -Region $Region } 55 | if ($Reservation) { $InputObject = $Reservation } 56 | if ($Instance) { $InputObject = $Instance } 57 | 58 | $InputObject | New-EC2PSSession -PemFile $PemFile -AddressProperty $AddressProperty | Enter-PSSession 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Functions/AWS/Enter-EC2RdpSession.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Opens an RDP session to an Ec2 instance with Administrator credentials 4 | .DESCRIPTION 5 | The cmdlet accepts pipeline input of EC2 instances and requires a 6 | private-key file to decrypt and logon with the administrator credentials. 7 | 8 | Th cmdlet uses cmdkey.exe in the background to enable credential passthrough. 9 | 10 | .PARAMETER InstanceId 11 | Mandatory - EC2 Instance Id for the target machine 12 | .PARAMETER Region 13 | Mandatory - Region parameter for the EC2 Instance if -InstanceID is 14 | specified. 15 | .PARAMETER Reservation 16 | Accepts an EC2 Reservation pipeline input from Get-Ec2Instance output. 17 | .PARAMETER Instance 18 | Accepts an Amazon EC2 Instance object from the pipeline 19 | .PARAMETER PemFile 20 | Mandatory - Path to the PrivateKey file to decrypt 21 | .PARAMETER AddressProperty 22 | Optional - String to try to use a specific private or public address 23 | 24 | .EXAMPLE 25 | Get-Ec2Instance i-2492acfc | Enter-EC2RdpSession -PemFile '~/ssh/ec2-dev.pem' 26 | .EXAMPLE 27 | Enter-EC2RdpSession -InstanceId i-2492acfc -Region us-west-2 -PemFile '~/ssh/ec2-dev.pem' 28 | #> 29 | function Enter-EC2RdpSession { 30 | [CmdletBinding(DefaultParameterSetName='ByInstanceId')] 31 | param( 32 | [Parameter(Mandatory=$true,ParameterSetName="ByInstanceId")] 33 | [string]$InstanceId, 34 | 35 | [Parameter(Mandatory=$true,ParameterSetName="ByInstanceId")] 36 | [string]$Region, 37 | 38 | [Parameter(ParameterSetName="ByReservationObject", ValueFromPipeline=$true)] 39 | [Amazon.EC2.Model.Reservation]$Reservation, 40 | 41 | [Parameter(ParameterSetName="ByInstanceObject", ValueFromPipeline=$true)] 42 | [Amazon.EC2.Model.Instance[]]$Instance, 43 | 44 | [Parameter()] 45 | [ValidateNotNullOrEmpty()] 46 | [string]$PemFile=$script:DefaultEc2PemFile, 47 | 48 | [Parameter()] 49 | [ValidateSet($null, 'PrivateIpAddress','PublicIpAddress','PrivateDnsName','PublicDnsName')] 50 | [string]$AddressProperty='PrivateIpAddress' 51 | ) 52 | 53 | Begin { Test-EC2PemFile -PemFile $PemFile -ErrorAction Stop | Out-Null } 54 | 55 | Process { 56 | if ($InstanceId) { $Reservation = Get-EC2Instance -Instance $InstanceId -Region $Region } 57 | if ($Reservation) { $Instance = $Reservation.Instances } 58 | 59 | foreach ($i in $Instance) { 60 | Write-Debug "Fetching credentials for $($i.InstanceId)" 61 | $credential = $i | Get-EC2Credential -PemFile $PemFile 62 | if ($credential) { 63 | foreach ($address in ($i | Get-EC2InstanceAddress -AddressProperty $AddressProperty | Select-Object -Unique)) { 64 | if (!$address) { continue } 65 | Enter-RdpSession -ComputerName $address -Credential $credential 66 | } 67 | } else { 68 | Write-Debug "Credential cannot be fetched" 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Functions/AWS/Get-EC2Credential.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Returns Administrator credentials for an EC2 Instance 4 | .DESCRIPTION 5 | The cmdlet accepts pipeline input of EC2 instances and requires a 6 | private-key file to decrypt and logon with the administrator credentials. 7 | 8 | .PARAMETER InstanceObject 9 | Accepts an EC2 Reservation pipeline input from Get-Ec2Instance output. 10 | .PARAMETER InstanceId 11 | Accepts an Amazon EC2 Instance object from the pipeline 12 | .PARAMETER Region 13 | Mandatory - AWS Region if InstanceId is specified instead of InstanceObject 14 | .PARAMETER PemFile 15 | Mandatory - Path to the PrivateKey file to decrypt 16 | 17 | .EXAMPLE 18 | Get-EC2Credential -InstanceId i-d56ef3 -PemFile '~/ssh/ec2-dev.pem' -Region us-west-2 19 | .EXAMPLE 20 | Get-ECInstance -InstanceId i-d56ef3 | Get-EC2Credential -PemFile '~/ssh/ec2-dev.pem' 21 | #> 22 | function Get-EC2Credential { 23 | [CmdletBinding(DefaultParameterSetName='ByInstanceId')] 24 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText","")] 25 | param( 26 | [Parameter(Mandatory=$true, ParameterSetName="ByInstanceId")] 27 | [string]$InstanceId, 28 | 29 | [Parameter(Mandatory=$true, ParameterSetName="ByInstanceId")] 30 | [string]$Region, 31 | 32 | [Parameter(ParameterSetName="ByInstanceObject", ValueFromPipeline=$true)] 33 | [Amazon.EC2.Model.Instance]$InstanceObject, 34 | 35 | [Parameter(Mandatory=$true,ParameterSetName="ByReservationObject", ValueFromPipeline=$true)] 36 | [Amazon.EC2.Model.Reservation]$Reservation, 37 | 38 | [Parameter()] 39 | [ValidateNotNullOrEmpty()] 40 | [string]$PemFile=$script:DefaultEc2PemFile, 41 | 42 | [Parameter()] 43 | [switch]$AsText 44 | ) 45 | 46 | Write-Verbose "ParameterSet: $($PsCmdlet.ParameterSetName)" 47 | 48 | Test-EC2PemFile -PemFile $PemFile -ErrorAction Stop | Out-Null 49 | $PemFile = Resolve-Path $PemFile 50 | 51 | if ($Reservation) { $InstanceObject = $Reservation.Instances | Select-Object -First 1 } 52 | 53 | if ($InstanceObject) { 54 | $InstanceId = $InstanceObject.InstanceId 55 | $Region = $InstanceObject.Placement.AvailabilityZone -replace '\w$','' 56 | Write-Verbose "Required Private-key: $($InstanceObject.KeyName)" 57 | } 58 | 59 | Write-Verbose "Fetching Credentials for $InstanceId@$Region" 60 | Write-Verbose "Keyfile used: $PemFile" 61 | $rawPassword = Get-EC2PasswordData -Region $Region -InstanceId $InstanceId -Decrypt -PemFile $PemFile 62 | if ($AsText) { return $rawPassword } 63 | 64 | if ($rawPassword) { 65 | $securePassword = ConvertTo-SecureString $rawPassword -AsPlainText -Force 66 | New-Object System.Management.Automation.PSCredential('~\Administrator',$securePassword) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Functions/AWS/Get-EC2InstanceAddress.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Returns all or a prefered address from an EC2 Instance 4 | .DESCRIPTION 5 | The cmdlet accepts pipeline input of EC2 instances. 6 | 7 | If -AddressProperty is defined, it returns only that, otherwise it returns 8 | all. The valid addresses are: 9 | - PrivateIpAddress 10 | - PublicIpAddress 11 | - PrivateDnsName 12 | - PublicDnsName 13 | 14 | .PARAMETER InstanceObject 15 | Mandatory - Ec2 Instance Object to get the addresses from 16 | .PARAMETER AddressProperty 17 | Optional - Name of the Address property to be filtered 18 | 19 | .EXAMPLE 20 | Get-Ec2Instance i-2492acfc | Select -ExpandProperty Instances | Get-EC2InstanceAddress 21 | .EXAMPLE 22 | Get-Ec2Instance i-2492acfc | Select -ExpandProperty Instances | Get-EC2InstanceAddress -AddressProperty PublicDnsName 23 | #> 24 | function Get-EC2InstanceAddress { 25 | [cmdletbinding()] 26 | param( 27 | [Parameter(Mandatory=$true,ValueFromPipeline=$true)] 28 | [Amazon.EC2.Model.Instance]$InstanceObject, 29 | 30 | [Parameter()] 31 | [ValidateSet($null,'PrivateIpAddress','PublicIpAddress','PrivateDnsName','PublicDnsName')] 32 | [string]$AddressProperty 33 | ) 34 | if ($InstanceObject) { 35 | if ($AddressProperty) { 36 | Write-Verbose "Address filtering for '$AddressProperty'" 37 | $InstanceObject.$AddressProperty 38 | } else { 39 | Write-Verbose "Returning unfiltered addresses" 40 | $InstanceObject.PrivateIpAddress 41 | $InstanceObject.PublicIpAddress 42 | $InstanceObject.PrivateDnsName 43 | $InstanceObject.PublicDnsName 44 | $InstanceObject.Tags | Where-Object Key -eq ComputerName | Select-Object -ExpandProperty Value 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Functions/AWS/New-EC2PSSession.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Opens a PSSession to an Ec2 instance with Administrator credentials 4 | .DESCRIPTION 5 | The cmdlet accepts pipeline input of EC2 instances and requires a 6 | private-key file to decrypt and logon with the administrator credentials. 7 | 8 | .PARAMETER InstanceId 9 | Mandatory - EC2 Instance Id for the target machine 10 | .PARAMETER Region 11 | Mandatory - Region parameter for the EC2 Instance if -InstanceID is 12 | specified. 13 | 14 | .PARAMETER Reservation 15 | Accepts an EC2 Reservation pipeline input from Get-Ec2Instance output. 16 | .PARAMETER Instance 17 | Accepts an Amazon EC2 Instance object from the pipeline 18 | .PARAMETER PemFile 19 | Mandatory - Path to the PrivateKey file to decrypt 20 | .PARAMETER AddressProperty 21 | Optional - String to try to use a specific private or public address 22 | 23 | .EXAMPLE 24 | New-EC2PSSession -Verbose -InstanceId i-2492acfc -Region us-west-2 -PemFile '~/ssh/dev.pem' 25 | .EXAMPLE 26 | Get-Ec2Instance i-ade67df | New-EC2PSSession -PemFile '~/ssh/dev.pem' 27 | #> 28 | function New-EC2PSSession { 29 | [CmdletBinding(DefaultParameterSetName='ByInstanceId')] 30 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions","")] 31 | param( 32 | [Parameter(Mandatory=$true,ParameterSetName="ByInstanceId")] 33 | [string[]]$InstanceId, 34 | 35 | [Parameter(Mandatory=$true,ParameterSetName="ByInstanceId")] 36 | [string]$Region, 37 | 38 | [Parameter(Mandatory=$true,ParameterSetName="ByReservationObject", ValueFromPipeline=$true)] 39 | [Amazon.EC2.Model.Reservation]$Reservation, 40 | 41 | [Parameter(Mandatory=$true,ParameterSetName="ByInstanceObject", ValueFromPipeline=$true)] 42 | [Amazon.EC2.Model.Instance[]]$Instance, 43 | 44 | [Parameter()] 45 | [ValidateNotNullOrEmpty()] 46 | [string]$PemFile=$script:DefaultEc2PemFile, 47 | 48 | [Parameter()] 49 | [ValidateSet($null, 'PrivateIpAddress','PublicIpAddress','PrivateDnsName','PublicDnsName')] 50 | [string]$AddressProperty 51 | 52 | #Authentication Mechanism 53 | #[System.Management.Automation.Runspaces.AuthenticationMechanism]$Authentication 54 | ) 55 | 56 | Begin { Test-EC2PemFile -PemFile $PemFile -ErrorAction Stop | Out-Null } 57 | 58 | Process { 59 | if ($InstanceId) { 60 | $Reservation = Get-EC2Instance -Instance $InstanceId -Region $Region 61 | } 62 | 63 | if ($Reservation) { $Instance = $Reservation.Instances } 64 | 65 | foreach ($i in $Instance) { 66 | Write-Verbose "Fetching credentials for $($i.InstanceId)" 67 | $credential = $i | Get-EC2Credential -PemFile $PemFile 68 | if ($credential) { 69 | foreach ($address in ($i | Get-EC2InstanceAddress -AddressProperty $AddressProperty | Select-Object -Unique)) { 70 | if (!$address) { continue } 71 | try { 72 | Write-Verbose "Trying to connect to address '$address'.." 73 | $session = $null 74 | $session = New-PSSession -ComputerName $address -Credential $credential 75 | if ($session) { 76 | Write-Verbose "Session established on '$address'.." 77 | return $session 78 | } 79 | } catch { 80 | Write-Error -ErrorRecord $_ 81 | } 82 | } 83 | } else { 84 | Write-Warning "Credential cannot be fetched. Make sure you pass valid key for '$($i.KeyName)'" 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Functions/AWS/PemFile/Clear-DefaultEC2PemFile.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Clears the default EC2 PemFile for this module 4 | .DESCRIPTION 5 | The cmdlet clears a previously set Default EC2 PemFile 6 | for this module cmdlets 7 | 8 | .EXAMPLE 9 | Clear-DefaultEC2PemFile 10 | #> 11 | function Clear-DefaultEC2PemFile { 12 | Write-Verbose "Clearing `$script:DefaultEc2PemFile" 13 | $script:DefaultEc2PemFile = $null 14 | } 15 | -------------------------------------------------------------------------------- /Functions/AWS/PemFile/Get-DefaultEC2PemFile.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Returns the default EC2 PemFile set for this module 4 | .DESCRIPTION 5 | The cmdlet returns previously set Default EC2 PemFile 6 | for this module. 7 | 8 | If it was never set or cleared, the function returns 9 | $null 10 | 11 | .EXAMPLE 12 | Get-DefaultEC2PemFile 13 | #> 14 | function Get-DefaultEC2PemFile { 15 | $script:DefaultEc2PemFile 16 | } 17 | -------------------------------------------------------------------------------- /Functions/AWS/PemFile/Set-DefaultEC2PemFile.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Sets the default EC2 PemFile for this module 4 | .DESCRIPTION 5 | The cmdlet sets the default EC2 PemFile for all CloudRemoting 6 | functions, so the user doesn't have to specify it all the time 7 | 8 | .PARAMETER PemFile 9 | Mandatory - Path to the PrivateKey file to be used by default 10 | 11 | .EXAMPLE 12 | Set-DefaultEC2PemFile '~/ssh/ec2-dev.pem' 13 | #> 14 | function Set-DefaultEC2PemFile { 15 | [CmdletBinding()] 16 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions","")] 17 | param( 18 | [Parameter(Mandatory=$true,Position=0)] 19 | [ValidateNotNullOrEmpty()] 20 | [string]$PemFile 21 | ) 22 | Test-EC2PemFile -PemFile $PemFile -ErrorAction Stop 23 | 24 | Write-Verbose "Setting `$script:DefaultEc2PemFile to $PemFile" 25 | $script:DefaultEc2PemFile = Resolve-Path $PemFile | Select-Object -ExpandProperty Path 26 | } 27 | -------------------------------------------------------------------------------- /Functions/AWS/PemFile/Test-EC2PemFile.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Validates the default EC2 pemfile 4 | .DESCRIPTION 5 | This cmdlet is used for a vague validation of the PemFile 6 | .PARAMETER PemFile 7 | Mandatory - Path to the PrivateKey file to be used by default 8 | 9 | .EXAMPLE 10 | Test-EC2PemFile '~/ssh/ec2-dev.pem' 11 | #> 12 | function Test-EC2PemFile { 13 | [CmdletBinding()] 14 | [OutputType([System.Boolean])] 15 | param( 16 | [Parameter(Position=0)] 17 | [string]$PemFile 18 | ) 19 | 20 | try { 21 | Write-Verbose "Testing $PemFile path.." 22 | if (-Not (Test-Path -Path $PemFile -ErrorAction Stop)) { throw } 23 | 24 | Write-Verbose "Testing if content is not empty.." 25 | $content = Get-Content -Raw -Path $PemFile -ErrorAction Stop 26 | if([string]::IsNullOrWhiteSpace($content)) { throw } 27 | 28 | return $true 29 | } catch { 30 | Write-Error "Please provide the path to a valid PemFile." 31 | } 32 | return $false 33 | } 34 | -------------------------------------------------------------------------------- /Functions/AWS/SSM/Clear-DefaultSSMOutput.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Clears the default S3 output settings for SSM 4 | .DESCRIPTION 5 | The cmdlet clears a previously set Default S3 Output 6 | setting for SSM 7 | 8 | .EXAMPLE 9 | Clear-DefaultSSMOutput 10 | #> 11 | function Clear-DefaultSSMOutput { 12 | Write-Verbose "Clearing SSM S3 Output settings.." 13 | $Script:DefaultSSMOutputS3BucketName=$null 14 | $Script:DefaultSSMOutputS3KeyPrefix=$null 15 | } 16 | -------------------------------------------------------------------------------- /Functions/AWS/SSM/Get-SSMCommandResult.ps1: -------------------------------------------------------------------------------- 1 | 2 | function Get-SSMCommandResult { 3 | param( 4 | [Parameter(Mandatory=$true)] 5 | [string]$CommandId, 6 | 7 | [Parameter()] 8 | [string]$InstanceId, 9 | 10 | [Parameter()] 11 | [string]$Region=$(Get-DefaultAWSRegion | Select-Object -ExpandProperty Region), 12 | 13 | [Parameter()] 14 | [switch]$EnableCliXml 15 | ) 16 | 17 | Write-Verbose "Fetching results from $InstanceId - $CommandId .." 18 | $invocation = Get-SSMCommandInvocation -CommandId $CommandId -Details $true -InstanceId $InstanceId 19 | if ($invocation.TraceOutput) { Write-Warning $invocation.TraceOutput } 20 | 21 | $result = $invocation | Select-Object -ExpandProperty CommandPlugins 22 | if ($result.Status -ine 'Success') { 23 | Write-Error "'$InstanceId': $($result.Name) invocation failed with ResponseCode $($result.ResponseCode)." 24 | } 25 | 26 | if (-Not $result.Output) { Write-Warning "No output was received from '$InstanceId'" } 27 | $output = $result.Output 28 | 29 | Write-Verbose "Raw output received.." 30 | Write-Verbose $output 31 | 32 | try { 33 | Write-Verbose "Trying to decode output.." 34 | $output = [System.Web.HttpUtility]::HtmlDecode($result.Output) 35 | } catch { 36 | Write-Error "Unable to XML Decode output." 37 | } 38 | 39 | Write-Verbose "Separating ErrorStream.." 40 | $ERROR_REGEX = '-+ERROR-+' 41 | if ($output -imatch $ERROR_REGEX) { 42 | $streams = $output -isplit $ERROR_REGEX 43 | $output = $streams[0] 44 | Write-Error "$i $($streams[1])" 45 | } 46 | 47 | Write-Verbose "Checking result truncation.." 48 | $TRUNCATE_REGEX = '-+Output truncated-+' 49 | if ([string]::IsNullOrWhiteSpace($output) -or $output -imatch $TRUNCATE_REGEX) { 50 | if ($result.OutputS3BucketName) { 51 | $S3Path = "s3://$($result.OutputS3BucketName)/$($result.OutputS3KeyPrefix)" 52 | 53 | try { 54 | Write-Verbose "Fetching full output from $S3Path" 55 | $tempFile = [System.IO.Path]::GetTempFileName() 56 | $pluginName = $result.Name -replace ':', '' 57 | Read-S3Object -BucketName $result.OutputS3BucketName -Key "$($result.OutputS3KeyPrefix)/0.$($pluginName)/stdout" -File $tempFile -Region $Region -ErrorAction Stop | Out-Null 58 | $output = Get-Content -Path $tempFile -Raw 59 | Remove-Item -Path $tempFile -Force -Recurse 60 | } catch { 61 | Write-Error "Unable to read full output from $S3Path" 62 | Write-Verbose $_.Exception 63 | } 64 | } else { 65 | Write-Warning "Unable to fetch full output. Please specify S3 Output, to receive non-truncated results." 66 | } 67 | } 68 | 69 | if ($EnableCliXml) { 70 | Write-Verbose "Try Parsing output as CliXml" 71 | try { 72 | $cliXml = [System.IO.Path]::GetTempFileName() 73 | Set-Content -Path $cliXml -Value $output 74 | $output = Import-Clixml -Path $cliXml 75 | Remove-Item -Path $cliXml -Force 76 | } catch { 77 | Write-Debug $_.Exception 78 | Write-Error "Unable to parse result as CliXml" 79 | } 80 | } 81 | 82 | Write-Verbose "Returning output.." 83 | $output | 84 | Add-Member -NotePropertyName InstanceId -NotePropertyValue $InstanceId -ErrorAction Continue -PassThru | 85 | Add-Member -NotePropertyName SSMCommandInvocation -NotePropertyValue $invocation -ErrorAction Continue -PassThru 86 | } 87 | -------------------------------------------------------------------------------- /Functions/AWS/SSM/Invoke-SSMCommand.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Invokes an AWS SSM Command against EC2 instances 4 | 5 | .DESCRIPTION 6 | This command is an extension for AWS PowerShell module to execute 7 | script on EC2 Instances similarly as Invoke-Command does on regular 8 | PSSessions. 9 | 10 | It takes direct EC2 instances or AWS reservation objects 11 | from pipeline and invokes SSM Commands on those. 12 | 13 | The specified -DocumentName and -Parameters will be executed 14 | synchronously and the response presented on the standard output. 15 | 16 | If -ScriptBlock is set the script will be executed within a 17 | 'AWS-RunPowerShellScript' document. 18 | 19 | 20 | .PARAMETER InstanceId 21 | Mandatory - EC2 Instance Id for the target machine 22 | .PARAMETER Region 23 | Optinal - Region parameter for the EC2 Instance if -InstanceID is 24 | specified. 25 | 26 | .PARAMETER Reservation 27 | Accepts an EC2 Reservation pipeline input from Get-Ec2Instance output. 28 | .PARAMETER Instance 29 | Accepts an Amazon EC2 Instance object from the pipeline 30 | 31 | .PARAMETER ScriptBlock 32 | Optional - Extra ScriptBlock to be executed as a PowerShell Block 33 | The block is executed as 'AWS-RunPowerShellScript' 34 | 35 | .PARAMETER DocumentName 36 | SSM Document to be executed on the target EC2 Instances 37 | Default is 'AWS-RunPowerShellScript' to accept -ScriptBlock 38 | 39 | .PARAMETER Parameter 40 | Optional - Parameter Hash to be passed as key-value pairs to 41 | the SSM Document. 42 | 43 | .PARAMETER EnableCliXml 44 | Optional - Switch to enable PowerShell CliXml serialization 45 | for custom scriptblocks and deserialization for any response 46 | 47 | .EXAMPLE 48 | Get-Ec2Instance | Invoke-SSMCommand { iisreset } 49 | 50 | .EXAMPLE 51 | Invoke-SSMCommand { Resolve-DnsName 'google.com' } -InstanceId i-4660a819 -Region us-west-2 52 | 53 | .EXAMPLE 54 | Get-Ec2Instance -InstanceId i-4660a819 -Region us-west-2 | Invoke-SSMCommand { whoami } -OutputS3BucketName 'my-bucket' -OutputS3KeyPrefix 'ssm-logs' 55 | 56 | #> 57 | 58 | function Invoke-SSMCommand { 59 | [CmdletBinding(DefaultParameterSetName='ByInstanceId')] 60 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions","")] 61 | param( 62 | [Parameter(Mandatory=$true,ParameterSetName="ByInstanceId")] 63 | [string[]]$InstanceId, 64 | 65 | [Parameter(ParameterSetName="ByInstanceId")] 66 | [string]$Region=$(Get-DefaultAWSRegion | Select-Object -ExpandProperty Region), 67 | 68 | [Parameter(Mandatory=$true,ParameterSetName="ByReservationObject", ValueFromPipeline=$true)] 69 | [Amazon.EC2.Model.Reservation]$Reservation, 70 | 71 | [Parameter(Mandatory=$true,ParameterSetName="ByInstanceObject", ValueFromPipeline=$true)] 72 | [Amazon.EC2.Model.Instance[]]$Instance, 73 | 74 | [Parameter()] 75 | [string]$DocumentName='AWS-RunPowerShellScript', 76 | 77 | [Parameter(Position=0)] 78 | [scriptblock]$ScriptBlock, 79 | 80 | [Parameter(Position=1)] 81 | [hashtable]$Parameter, 82 | 83 | [Parameter()] 84 | [string]$OutputS3BucketName=$Script:DefaultSSMOutputS3BucketName, 85 | 86 | [Parameter()] 87 | [string]$OutputS3KeyPrefix=$Script:DefaultSSMOutputS3KeyPrefix, 88 | 89 | [Parameter()] 90 | [Alias('CliXml')] 91 | [switch]$EnableCliXml, 92 | 93 | [Parameter()] 94 | [System.Int32]$TimeoutSecond, 95 | 96 | [Parameter()] 97 | [System.Int32]$SleepMilliseconds=400 98 | ) 99 | 100 | begin { 101 | Add-Type -AssemblyName System.Web 102 | $script:SSMInvocations = @{} 103 | 104 | Write-Verbose "Constructing Parameters.." 105 | if($DocumentName -eq 'AWS-RunPowerShellScript') { 106 | Write-Verbose "Running with generic PowerShell scriptblock.." 107 | if ($EnableCliXml) { 108 | Write-Verbose "Wrapping Scriptblock for CLIXML.." 109 | $Parameter = @{'commands'=@( 110 | '$ConfirmPreference = "None"' 111 | '$tempFile = [System.IO.Path]::GetTempFileName()' 112 | "& { $($ScriptBlock.ToString()) } | Export-Clixml -Path `$tempFile" 113 | 'Get-Content -Path $tempFile' 114 | )} 115 | } else { 116 | $Parameter = @{'commands'=@( 117 | '$ConfirmPreference = "None"' 118 | $ScriptBlock.ToString() 119 | )} 120 | } 121 | } 122 | elseif ($DocumentName -eq 'AWS-RunShellScript') { 123 | Write-Verbose "Running with generic Shell ScriptBlock.." 124 | $Parameter = @{'commands'=@($ScriptBlock.ToString())} 125 | } 126 | } 127 | 128 | Process { 129 | # Convert all input into Instance list 130 | if ($Reservation) { $Instance = $Reservation.Instances } 131 | if ($InstanceId) { 132 | $Instance = $InstanceId | 133 | Foreach-Object { 134 | New-Object psobject -Property @{ 135 | InstanceId = $_ 136 | Placement = @{ AvailabilityZone="$($Region)x" } 137 | } 138 | } 139 | } 140 | 141 | Write-Debug "Processing $Instance ..." 142 | foreach ($i in $Instance) { 143 | $id = $i.InstanceId 144 | $Region = ($i | Select-Object -ExpandProperty Placement | Select-Object -ExpandProperty AvailabilityZone) -replace '\w$','' 145 | 146 | Write-Verbose "Targeting: $id @ $Region" 147 | Write-Verbose "Executing $DocumentName with `n $($Parameter.Keys | ForEach-Object { $Parameter.$_ | Out-String }).." 148 | 149 | if (-Not $Region) { 150 | Write-Warning "Region is not set, execution may fail.." 151 | } else { 152 | Write-Debug "Setting region to $Region .." 153 | Set-DefaultAWSRegion -Region $Region 154 | } 155 | 156 | $SSMCommandArgs = @{ 157 | InstanceId=$id 158 | DocumentName=$DocumentName 159 | Comment="Invoked by $($env:USERNAME)@$($env:USERDOMAIN) from CloudRemoting@$($env:COMPUTERNAME)" 160 | } 161 | 162 | if ($Parameter) { $SSMCommandArgs.Parameter = $Parameter } 163 | if ($OutputS3BucketName) { $SSMCommandArgs.OutputS3BucketName = $OutputS3BucketName } 164 | if ($OutputS3KeyPrefix) { $SSMCommandArgs.OutputS3KeyPrefix = $OutputS3KeyPrefix } 165 | if ($TimeoutSecond) { $SSMCommandArgs.TimeoutSecond = $TimeoutSecond } 166 | 167 | try { 168 | $ssmCommand=Send-SSMCommand @SSMCommandArgs | 169 | Add-Member -NotePropertyName SSMCommandInputObject -NotePropertyValue $i -PassThru -Force 170 | 171 | $script:SSMInvocations.$id = $ssmCommand 172 | } catch { 173 | Write-Error -ErrorRecord $_ 174 | continue 175 | } 176 | } 177 | } 178 | 179 | end { 180 | Write-Verbose "Collecting results $($script:SSMInvocations)" 181 | 182 | while ($script:SSMInvocations.Keys.Count -gt 0) { 183 | $id = $script:SSMInvocations.Keys | Get-Random 184 | $i = $script:SSMInvocations.$id 185 | Write-Verbose "Waiting for $($i.InstanceIds) - $($i.CommandId) - $($i.Status) command..." 186 | $currentCommand=Get-SSMCommand -CommandId $i.CommandId -ErrorAction SilentlyContinue 187 | 188 | if (($null -eq $currentCommand) -or ($currentCommand.Status -imatch 'Success|Fail')) { 189 | $script:SSMInvocations.Remove($id) 190 | Get-SSMCommandResult -CommandId $i.CommandId -InstanceId $id -EnableCliXml:$EnableCliXml | 191 | Add-Member -NotePropertyName SSMCommandInputObject -NotePropertyValue $i.SSMCommandInputObject -PassThru -Force 192 | } 193 | Start-Sleep -Milliseconds $SleepMilliseconds 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /Functions/AWS/SSM/Set-DefaultSSMOutput.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Sets the default SSM Output S3 bucket and prefix 4 | .DESCRIPTION 5 | The cmdlet sets the default S3 bucket and prefix for 6 | capturing SSM outputs. 7 | 8 | .PARAMETER PemFile 9 | Mandatory - Path to the PrivateKey file to be used by default 10 | 11 | .EXAMPLE 12 | Set-DefaultSSMOutput 13 | #> 14 | function Set-DefaultSSMOutput { 15 | [CmdletBinding()] 16 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions","")] 17 | param( 18 | [Parameter(Mandatory=$true,Position=0)] 19 | [ValidateNotNullOrEmpty()] 20 | [string]$BucketName, 21 | 22 | [Parameter(Position=1)] 23 | [ValidateNotNullOrEmpty()] 24 | [string]$KeyPrefix 25 | ) 26 | 27 | $Script:DefaultSSMOutputS3BucketName=$BucketName 28 | $Script:DefaultSSMOutputS3KeyPrefix=$KeyPrefix 29 | } 30 | -------------------------------------------------------------------------------- /Functions/Common.ps1: -------------------------------------------------------------------------------- 1 | # Common Aliases 2 | New-Alias -Name ssm -Value Invoke-SSMCommand -Force 3 | 4 | New-Alias -Name rdp -Value Enter-RdpSession -Force 5 | New-Alias -Name ec2rdp -Value Enter-EC2RdpSession -Force 6 | 7 | New-Alias -Name ec2sn -Value Enter-EC2PSSession -Force 8 | 9 | $script:DefaultEc2PemFile=$null 10 | $Script:DefaultSSMOutputS3BucketName=$null 11 | $Script:DefaultSSMOutputS3KeyPrefix=$null 12 | -------------------------------------------------------------------------------- /Functions/Enter-RdpSession.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Opens an RDP Session to a target machine 4 | .DESCRIPTION 5 | Opens mstsc.exe with the passed -ComputerName parameter. 6 | 7 | If -Credential is set, it will use cmdkey.exe to save the credential 8 | for passthrough. 9 | 10 | .PARAMETER ComputerName 11 | Mandatory - ComputerName, IpAddress or fqdn of the target machine 12 | .PARAMETER Credential 13 | Optional - Credential objeect to be passed to the remote desktop session. 14 | .PARAMETER CleanupCredentials 15 | Optional - Switch to remove any related credentials when the RDP session 16 | exits. 17 | 18 | .EXAMPLE 19 | Enter-RdpSession -ComputerName 'dc01.local' 20 | .EXAMPLE 21 | $cred = Get-Credential 22 | Enter-RdpSession -ComputerName 'dc01.local' -Credential $cred 23 | #> 24 | function Enter-RdpSession { 25 | [CmdletBinding()] 26 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "Credential")] 27 | param( 28 | [Parameter(Mandatory=$true,Position=1)] 29 | [string]$ComputerName, 30 | 31 | [System.Management.Automation.PSCredential] 32 | [System.Management.Automation.CredentialAttribute()] 33 | $Credential, 34 | 35 | [switch]$CleanupCredentials 36 | ) 37 | 38 | $rdcProcess = New-Object System.Diagnostics.Process 39 | if ($Credential) { 40 | $Password = '' 41 | if ($Credential.GetNetworkCredential()) { 42 | $Password=$Credential.GetNetworkCredential().password 43 | } else { 44 | $Password=[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Credential.password)) 45 | } 46 | 47 | Write-Verbose "Adding Credentials for $ComputerName to Windows Credential Store" 48 | $rdcProcess.StartInfo.FileName = [Environment]::ExpandEnvironmentVariables("%SystemRoot%\system32\cmdkey.exe") 49 | $rdcProcess.StartInfo.Arguments = "/generic:TERMSRV/$ComputerName /user:$($Credential.UserName) /pass:`"$Password`"" 50 | $rdcProcess.StartInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden 51 | [void]$rdcProcess.Start() 52 | } 53 | 54 | Write-Verbose "Connecting to RDP Session: $ComputerName" 55 | $rdcProcess.StartInfo.FileName = [Environment]::ExpandEnvironmentVariables("%SystemRoot%\system32\mstsc.exe") 56 | $rdcProcess.StartInfo.Arguments = "/v $ComputerName" 57 | $rdcProcess.StartInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Normal 58 | [void]$rdcProcess.Start() 59 | 60 | 61 | if ($CleanupCredentials) { 62 | Write-Verbose "Waiting for RDP Session to exit..." 63 | [void]$rdcProcess.WaitForExit() 64 | if ($Credential) { 65 | Write-Verbose "Removing Credentials from Windows Credential Store" 66 | $rdcProcess.StartInfo.FileName = [Environment]::ExpandEnvironmentVariables("%SystemRoot%\system32\cmdkey.exe") 67 | $rdcProcess.StartInfo.Arguments = "/delete:TERMSRV/$ComputerName" 68 | [void]$rdcProcess.Start() 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Functions/Expand-Object.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Enriches the inputobject, by expanding properties to the top 4 | level. 5 | .DESCRIPTION 6 | Expands the InputObject with the -ExpandProperty and merges its 7 | content to the top level. 8 | 9 | .PARAMETER InputObject 10 | Mandatory - InputObject to be expanded 11 | .PARAMETER Force 12 | Optional - Switch to to override properties that already exist 13 | 14 | .EXAMPLE 15 | Invoke-SSMCommand { Get-Date } -EnableCliXml | Expand-Object -Force 16 | #> 17 | function Expand-Object { 18 | [CmdletBinding()] 19 | param( 20 | [Parameter(Mandatory=$true, ValueFromPipeline=$true)] 21 | [PsObject[]]$InputObject, 22 | 23 | [Parameter()] 24 | [Alias('Property')] 25 | [string[]]$ExpandProperty=@('Tags','SSMCommandInputObject'), 26 | 27 | [Parameter()] 28 | [switch]$Force 29 | ) 30 | 31 | process { 32 | $InputObject | 33 | ForEach-Object { 34 | foreach ($expandable in ($_| Get-Member -Name $ExpandProperty | Select-Object -ExpandProperty Name)) { 35 | Write-Verbose "Processing '$expandable'.." 36 | if ($_.$expandable | Get-Member -Name @('Key','Value') -ErrorAction SilentlyContinue) { 37 | Write-Verbose "Hash expansion" 38 | foreach($enty in $_.$expandable) { 39 | $_ | Add-Member -NotePropertyName $enty.Key -NotePropertyValue $enty.Value -Force:$Force 40 | } 41 | } 42 | elseif ($_.$expandable) { 43 | Write-Verbose "PSObject expansion" 44 | foreach($property in ($_.$expandable | Get-Member -MemberType Properties)) { 45 | $_ | Add-Member -NotePropertyName $property.Name -NotePropertyValue $_.$expandable.($Property.Name) -Force:$Force 46 | } 47 | } 48 | 49 | $_ 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Images/ec2_pssession.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murati-hu/CloudRemoting/3364c162fac79d54d44511342ac6d295bc5bf4d1/Images/ec2_pssession.gif -------------------------------------------------------------------------------- /Images/ec2_rdp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murati-hu/CloudRemoting/3364c162fac79d54d44511342ac6d295bc5bf4d1/Images/ec2_rdp.gif -------------------------------------------------------------------------------- /Images/ssm_command.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murati-hu/CloudRemoting/3364c162fac79d54d44511342ac6d295bc5bf4d1/Images/ssm_command.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016, Akos Murati 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CloudRemoting PowerShell module 2 | =============================== 3 | 4 | [![Build status](https://ci.appveyor.com/api/projects/status/kdc6a75b8wludjq6?svg=true)](https://ci.appveyor.com/project/muratiakos/cloudremoting) 5 | [![Join the chat at https://gitter.im/murati-hu/CloudRemoting](https://badges.gitter.im/murati-hu/CloudRemoting.svg)](https://gitter.im/murati-hu/CloudRemoting?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | 7 | CloudRemoting module provides an easy and scriptable way to connect to EC2, Azure 8 | or to other machines via RDP, PSRemoting and SSM sessions on top of the standard 9 | cmdlets by: 10 | - Seamless EC2 Administrator Credential decryption for RDP and PSRemoting 11 | - Credential pass-through for RDP Sessions 12 | - Pipeline integrated SSM Run Command execution 13 | 14 | ## Installation 15 | CloudRemoting is available via [PowerShellGallery][PowerShellGallery] and via 16 | [PsGet][psget], so you can simply install it with the following command: 17 | 18 | ```powershell 19 | # Install it from PowerShell Gallery with PS5 / psget.net for older PS versions 20 | Install-Module CloudRemoting -Scope CurrentUser 21 | ``` 22 | # Or install it directly from this repository with psget.net 23 | ```powershell 24 | # Set SSL/TLS 25 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 26 | # install PsGet 27 | (new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/psget/psget/master/GetPsGet.ps1") | iex 28 | # install/update CloudRemoting 29 | PsGet\Install-Module -ModuleUrl https://github.com/murati-hu/CloudRemoting/archive/latest.zip -Update 30 | ``` 31 | 32 | Of course you can download and install the module manually too from 33 | [Downloads][download] 34 | 35 | ## Usage 36 | ```powershell 37 | Import-Module CloudRemoting 38 | ``` 39 | 40 | ## Few Examples 41 | ### Integrated SSM Command execution 42 | AWS SSM Run-command allows us to execute scripts against EC2 Instances from anywhere 43 | without direct network connectivity to the targets. 44 | `Invoke-SSMCommand` is an extension on top of the standard `Send-SSMCommand` to make 45 | this task even easier and fully integrate this feature to PowerShell pipelines. 46 | ```powershell 47 | # Execute scripts with SSM Run Command similarly as Invoke-Command 48 | Get-Ec2Instance i-2492acfc | Invoke-SSMCommand { iisreset } 49 | 50 | # Execute SSM alias, with a CLI serialized command via S3 output 51 | Set-DefaultSSMOutput -BucketName 'ssm-outputs' -KeyPrefix 'logs/' 52 | Get-Ec2Instance i-2492acfc | ssm { Get-WebSite } -EnableCliXml | Select Name,InstanceId 53 | ``` 54 | ![ssm_command](https://cloud.githubusercontent.com/assets/2268036/20460497/82df7c0e-af49-11e6-93f3-acdea3d9aa8c.gif) 55 | 56 | 57 | ### Enter an EC2 Admin RDP Session with Private-Key file 58 | You can use the `Enter-EC2RdpSession` cmdlet or its `ec2rdp` alias to connect to any EC2 instance as an administrator via RDP. 59 | ```powershell 60 | Get-Ec2Instance i-2492acfc | Enter-EC2RdpSession -PemFile '~/.ssh/myprivatekey.pem' 61 | ``` 62 | ![ec2_rdp_session](https://cloud.githubusercontent.com/assets/2268036/14919383/ae1d3438-0e7c-11e6-9026-d995fb2deb50.gif) 63 | 64 | 65 | ### Open EC2 Admin PSSessions with Private-Key 66 | Similarly to the native `PSSession` cmdlets, you can use the `New-EC2PSSession` and `Enter-EC2RdpSession` commands to create or enter to any EC2 PSSession as an administrator: 67 | ```powershell 68 | # Enter to a single EC2 PSSession 69 | Get-Ec2Instance i-2492acfc | Enter-EC2PSSession -PemFile '~/.ssh/myprivatekey.pem' 70 | 71 | # Set Default EC2 PemFile and create multiple EC2 PSSessions 72 | Set-DefaultEC2PemFile -PemFile '~/.ssh/myprivatekey.pem' 73 | Get-Ec2Instance -Filter @{name='tag:env'; value='demo'} | New-EC2PSSession 74 | ``` 75 | ![ec2_multiple_pssession](https://cloud.githubusercontent.com/assets/2268036/14919352/8a8cb82c-0e7c-11e6-9260-23a0fa4dd912.gif) 76 | 77 | Please note that all EC2 cmdlets rely on the official [`AWSPowershell`][AWSPowershell] module. 78 | It expects the module to be installed with valid AWS credentials setup. 79 | 80 | 81 | ### RemoteDesktop to any machine 82 | In order to connect to any machine via RDP, you can simply call `Enter-RdpSession` cmdlet or its `rdp` alias. 83 | ```powershell 84 | # Connect an RDP Session to any machine 85 | $c = Get-EC2Credential # Or retrieve from a persisted creds 86 | Enter-RdpSession -ComputerName '207.47.222.251' -Credential $c 87 | ``` 88 | 89 | ## Documentation 90 | Cmdlets and functions for CloudRemoting have their own help PowerShell help, which 91 | you can read with `help `. 92 | 93 | ## Versioning 94 | CloudRemoting aims to adhere to [Semantic Versioning 2.0.0][semver]. 95 | 96 | ## Issues 97 | In case of any issues, raise an [issue ticket][issues] in this repository and/or 98 | feel free to contribute to this project if you have a possible fix for it. 99 | 100 | ## Development 101 | * Source hosted at [Github.com][repo] 102 | * Report issues/questions/feature requests on [Github Issues][issues] 103 | 104 | Pull requests are very welcome! Make sure your patches are well tested. 105 | Ideally create a topic branch for every separate change you make. For 106 | example: 107 | 108 | 1. Fork the [repo][repo] 109 | 2. Create your feature branch (`git checkout -b my-new-feature`) 110 | 3. Commit your changes (`git commit -am 'Added some feature'`) 111 | 4. Push to the branch (`git push origin my-new-feature`) 112 | 5. Create new Pull Request 113 | 114 | ## Authors 115 | Created and maintained by [Akos Murati][muratiakos] (). 116 | 117 | ## License 118 | Apache License, Version 2.0 (see [LICENSE][LICENSE]) 119 | 120 | [repo]: https://github.com/murati-hu/CloudRemoting 121 | [issues]: https://github.com/murati-hu/CloudRemoting/issues 122 | [muratiakos]: http://murati.hu 123 | [license]: LICENSE 124 | [semver]: http://semver.org/ 125 | [psget]: http://psget.net/ 126 | [download]: https://github.com/murati-hu/CloudRemoting/archive/latest.zip 127 | [PowerShellGallery]: https://www.powershellgallery.com 128 | [AWSPowershell]: https://aws.amazon.com/powershell 129 | -------------------------------------------------------------------------------- /Tests/Module/Module.tests.ps1: -------------------------------------------------------------------------------- 1 | . (Join-Path $PSScriptRoot '../TestCommon.ps1') 2 | 3 | $exportedCommands = (Get-Command -Module $Script:ModuleName) 4 | $expectedCommands = Import-Csv -Path (Join-Path $PSScriptRoot 'expected_commands.csv') 5 | 6 | Describe "$($Script:ModuleName) Module" { 7 | It "ModuleName Should be set" { 8 | $Script:ModuleName | Should Not BeNullOrEmpty 9 | } 10 | 11 | It "Should be loaded" { 12 | Get-Module $Script:ModuleName | Should Not BeNullOrEmpty 13 | } 14 | } 15 | 16 | Describe 'Exported commands' { 17 | # Test if the exported command is expected 18 | Foreach ($command in $exportedCommands) 19 | { 20 | Context $command { 21 | It 'Should be an expected command' { 22 | $expectedCommands.Name -contains $command.Name | Should Be $true 23 | } 24 | 25 | It 'Should have proper help' { 26 | $help = Get-Help $command.Name 27 | $help.description | Should Not BeNullOrEmpty 28 | $help.Synopsis | Should Not BeNullOrEmpty 29 | $help.examples | Should Not BeNullOrEmpty 30 | } 31 | } 32 | } 33 | } 34 | 35 | Describe 'Expected commands' { 36 | # Test if the expected command is exported 37 | Foreach ($command in $expectedCommands) 38 | { 39 | Context $command.Name { 40 | It 'Should be an exported command' { 41 | $exportedCommands.Name -contains $command.Name | Should Be $true 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/Module/ScriptAnalyzer.tests.ps1: -------------------------------------------------------------------------------- 1 | . (Join-Path $PSScriptRoot '../TestCommon.ps1') 2 | 3 | $scriptSources = Get-ChildItem -Path $script:FunctionPath -Filter '*.ps1' -Recurse 4 | $scriptAnalyzer = Get-Module PSScriptAnalyzer -ListAvailable 5 | 6 | $excludeRuleList = @( 7 | 'PSUseShouldProcessForStateChangingFunctions' 8 | ) 9 | 10 | if (-Not $scriptAnalyzer) { 11 | Write-Warning "PSScriptAnalyzer module is not available." 12 | return 13 | } 14 | 15 | Describe "Script Source analysis" { 16 | Import-Module PSScriptAnalyzer 17 | 18 | $scriptSources | ForEach-Object { 19 | Context "Source $($_.FullName)" { 20 | $results = Invoke-ScriptAnalyzer -Path $_.FullName -ErrorVariable $errors -ExcludeRule $excludeRuleList 21 | 22 | it "should have no errors" { 23 | $errors | Should BeNullOrEmpty 24 | } 25 | 26 | it "should not have warnings" { 27 | $results | 28 | Where-Object Severity -eq "Warning" | 29 | Should BeNullOrEmpty 30 | } 31 | 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Tests/Module/expected_commands.csv: -------------------------------------------------------------------------------- 1 | Name 2 | Enter-EC2PSSession 3 | Enter-EC2RdpSession 4 | Enter-RdpSession 5 | Get-EC2Credential 6 | Get-EC2InstanceAddress 7 | New-EC2PSSession 8 | Test-EC2PemFile 9 | Clear-DefaultEC2Pemfile 10 | Get-DefaultEC2Pemfile 11 | Set-DefaultEC2Pemfile 12 | Set-DefaultSSMOutput 13 | Clear-DefaultSSMOutput 14 | Invoke-SSMCommand 15 | Expand-Object 16 | -------------------------------------------------------------------------------- /Tests/PemFile/PemFile.tests.ps1: -------------------------------------------------------------------------------- 1 | . (Join-Path $PSScriptRoot '../TestCommon.ps1') 2 | 3 | Describe "Validation - No Default EC2 PemFile" { 4 | $fakeInstance = 'i-123456' 5 | $fakeRegion = 'us-east-1' 6 | $emptyFile = Resolve-Path(Join-Path $PSScriptRoot '../PemFile/empty.txt') 7 | 8 | function Test-PemFileValidation([string]$FunctionName) { 9 | Context $FunctionName { 10 | it "should throw error if not specified" { 11 | $test = { . $FunctionName -InstanceId $fakeInstance -Region $fakeRegion } 12 | $test | Should Throw $exceptionText 13 | } 14 | 15 | $exceptionText = 'Provide an argument that is not null or empty' 16 | foreach($case in @($null, '')) { 17 | it "should throw '$exceptionText' if '$case' passed" { 18 | $test = { . $FunctionName -InstanceId $fakeInstance -Region $fakeRegion -PemFile $case } 19 | $test | Should Throw $exceptionText 20 | } 21 | } 22 | 23 | $exceptionText = 'Please provide the path to a valid PemFile.' 24 | foreach($case in @('x:\unlikelytoexists',$emptyFile)) { 25 | it "should throw '$exceptionText' if '$case' passed" { 26 | $test = { . $FunctionName -InstanceId $fakeInstance -Region $fakeRegion -PemFile $case } 27 | $test | Should Throw $exceptionText 28 | } 29 | } 30 | } 31 | } 32 | 33 | Clear-DefaultEC2PemFile 34 | @( 35 | 'Get-Ec2Credential' 36 | 'New-EC2PSSession' 37 | 'Enter-EC2PSSession' 38 | 'Enter-EC2RDPSession' 39 | ) | ForEach-Object { 40 | Test-PemFileValidation -FunctionName $_ 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tests/PemFile/Set-DefaultEC2PemFile.tests.ps1: -------------------------------------------------------------------------------- 1 | . (Join-Path $PSScriptRoot '../TestCommon.ps1') 2 | 3 | Describe "Set-DefaultEC2PemFile" { 4 | $emptyFile = Join-Path $PSScriptRoot '../PemFile/empty.txt' 5 | $notEmptyFile = Resolve-Path (Join-Path $PSScriptRoot '../PemFile/notempty.txt') | Select-Object -ExpandProperty Path 6 | 7 | Context "Valid input" { 8 | it "should not throw if valid file passed" { 9 | { Set-DefaultEC2PemFile -PemFile $notEmptyFile } | 10 | Should Not Throw 11 | } 12 | 13 | it "should set the default PemFile" { 14 | Get-DefaultEC2PemFile | Should Be $notEmptyFile 15 | } 16 | } 17 | 18 | Context "Invalid input" { 19 | foreach($case in @($null, '', 'x:\unlikelytoexists',$emptyFile)) { 20 | it "should throw exception if '$case' passed" { 21 | { Set-DefaultEC2PemFile -PemFile $case } | Should Throw 22 | } 23 | 24 | it "should NOT change the default PemFile" { 25 | Get-DefaultEC2PemFile | Should Be $notEmptyFile 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/PemFile/Test-EC2PemFile.tests.ps1: -------------------------------------------------------------------------------- 1 | . (Join-Path $PSScriptRoot '../TestCommon.ps1') 2 | 3 | Describe "Test-EC2PemFile validation" { 4 | $emptyFile = Join-Path $PSScriptRoot '../PemFile/empty.txt' 5 | $notEmptyFile = Join-Path $PSScriptRoot '../PemFile/notempty.txt' 6 | 7 | it "should return false if not specified" { 8 | Test-EC2PemFile -ErrorAction 0 | Should Be $false 9 | } 10 | it "should return false on invalid files" { 11 | Test-EC2PemFile -PemFile $null -ErrorAction 0 | Should Be $false 12 | Test-EC2PemFile -PemFile '' -ErrorAction 0 | Should Be $false 13 | Test-EC2PemFile -PemFile 'x:\unlikelytoexist' -ErrorAction 0 | Should Be $false 14 | Test-EC2PemFile -PemFile $emptyFile -ErrorAction 0 | Should Be $false 15 | } 16 | 17 | it "should return true on valid file" { 18 | Test-EC2PemFile -PemFile $notEmptyFile | Should Be $true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/PemFile/empty.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murati-hu/CloudRemoting/3364c162fac79d54d44511342ac6d295bc5bf4d1/Tests/PemFile/empty.txt -------------------------------------------------------------------------------- /Tests/PemFile/notempty.txt: -------------------------------------------------------------------------------- 1 | --- NOT EMPTY --- 2 | -------------------------------------------------------------------------------- /Tests/TestCommon.ps1: -------------------------------------------------------------------------------- 1 | # Load module from the local filesystem, instead from the ModulePath 2 | Remove-Module CloudRemoting -Force -ErrorAction SilentlyContinue 3 | Import-Module (Split-Path $PSScriptRoot -Parent) 4 | 5 | $Script:ModuleName = 'CloudRemoting' 6 | $script:FunctionPath = Resolve-Path (Join-Path $PSScriptRoot '../Functions') 7 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | install: 2 | - cinst pester 3 | 4 | build: false 5 | 6 | test_script: 7 | - ps: $res = Invoke-Pester -Path ".\Tests" -OutputFormat NUnitXml -OutputFile TestsResults.xml -PassThru 8 | - ps: (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestsResults.xml)) 9 | - ps: if ($res.FailedCount -gt 0) { throw "$($res.FailedCount) tests failed."} 10 | 11 | # Skip on updates to the readme and images 12 | skip_commits: 13 | files: 14 | - '**/*.md' 15 | - '**/*.gif' 16 | --------------------------------------------------------------------------------