|
|
|
<#
|
|
|
|
.SYNOPSIS
|
|
|
|
Designed to set a Windows host to connect to the httptester container running
|
|
|
|
on the Ansible host. This will setup the Windows host file and forward the
|
|
|
|
local ports to use this connection. This will continue to run in the background
|
|
|
|
until the script is deleted.
|
|
|
|
|
|
|
|
Run this with SSH with the -R arguments to foward ports 8080 and 8443 to the
|
|
|
|
httptester container.
|
|
|
|
|
|
|
|
.PARAMETER Hosts
|
|
|
|
A list of hostnames, delimited by '|', to add to the Windows hosts file for the
|
|
|
|
httptester container, e.g. 'ansible.host.com|secondary.host.test'.
|
|
|
|
#>
|
|
|
|
[CmdletBinding()]
|
|
|
|
param(
|
|
|
|
[Parameter(Mandatory=$true, Position=0)][String]$Hosts
|
|
|
|
)
|
|
|
|
$Hosts = $Hosts.Split('|')
|
|
|
|
|
|
|
|
$ProgressPreference = "SilentlyContinue"
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$os_version = [Version](Get-Item -Path "$env:SystemRoot\System32\kernel32.dll").VersionInfo.ProductVersion
|
|
|
|
Write-Verbose -Message "Configuring HTTP Tester on Windows $os_version for '$($Hosts -join "', '")'"
|
|
|
|
|
|
|
|
Function Get-PmapperRuleBytes {
|
|
|
|
<#
|
|
|
|
.SYNOPSIS
|
|
|
|
Create the byte values that configures a rule in the PMapper configuration
|
|
|
|
file. This isn't really documented but because PMapper is only used for
|
|
|
|
Server 2008 R2 we will stick to 1 version and just live with the legacy
|
|
|
|
work for now.
|
|
|
|
|
|
|
|
.PARAMETER ListenPort
|
|
|
|
The port to listen on localhost, this will be forwarded to the host defined
|
|
|
|
by ConnectAddress and ConnectPort.
|
|
|
|
|
|
|
|
.PARAMETER ConnectAddress
|
|
|
|
The hostname or IP to map the traffic to.
|
|
|
|
|
|
|
|
.PARAMETER ConnectPort
|
|
|
|
This port of ConnectAddress to map the traffic to.
|
|
|
|
#>
|
|
|
|
param(
|
|
|
|
[Parameter(Mandatory=$true)][UInt16]$ListenPort,
|
|
|
|
[Parameter(Mandatory=$true)][String]$ConnectAddress,
|
|
|
|
[Parameter(Mandatory=$true)][Int]$ConnectPort
|
|
|
|
)
|
|
|
|
|
|
|
|
$connect_field = "$($ConnectAddress):$ConnectPort"
|
|
|
|
$connect_bytes = [System.Text.Encoding]::ASCII.GetBytes($connect_field)
|
|
|
|
$data_length = [byte]($connect_bytes.Length + 6) # size of payload minus header, length, and footer
|
|
|
|
$port_bytes = [System.BitConverter]::GetBytes($ListenPort)
|
|
|
|
|
|
|
|
$payload = [System.Collections.Generic.List`1[Byte]]@()
|
|
|
|
$payload.Add([byte]16) > $null # header is \x10, means Configure Mapping rule
|
|
|
|
$payload.Add($data_length) > $null
|
|
|
|
$payload.AddRange($connect_bytes)
|
|
|
|
$payload.AddRange($port_bytes)
|
|
|
|
$payload.AddRange([byte[]]@(0, 0)) # 2 extra bytes of padding
|
|
|
|
$payload.Add([byte]0) > $null # 0 is TCP, 1 is UDP
|
|
|
|
$payload.Add([byte]0) > $null # 0 is Any, 1 is Internet
|
|
|
|
$payload.Add([byte]31) > $null # footer is \x1f, means end of Configure Mapping rule
|
|
|
|
|
|
|
|
return ,$payload.ToArray()
|
|
|
|
}
|
|
|
|
|
|
|
|
Write-Verbose -Message "Adding host file entries"
|
|
|
|
$hosts_file = "$env:SystemRoot\System32\drivers\etc\hosts"
|
|
|
|
$hosts_file_lines = [System.IO.File]::ReadAllLines($hosts_file)
|
|
|
|
$changed = $false
|
|
|
|
foreach ($httptester_host in $Hosts) {
|
|
|
|
$host_line = "127.0.0.1 $httptester_host # ansible-test httptester"
|
|
|
|
if ($host_line -notin $hosts_file_lines) {
|
|
|
|
$hosts_file_lines += $host_line
|
|
|
|
$changed = $true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($changed) {
|
|
|
|
Write-Verbose -Message "Host file is missing entries, adding missing entries"
|
|
|
|
[System.IO.File]::WriteAllLines($hosts_file, $hosts_file_lines)
|
|
|
|
}
|
|
|
|
|
|
|
|
# forward ports
|
|
|
|
$forwarded_ports = @{
|
|
|
|
80 = 8080
|
|
|
|
443 = 8443
|
|
|
|
}
|
|
|
|
if ($os_version -ge [Version]"6.2") {
|
|
|
|
Write-Verbose -Message "Using netsh to configure forwarded ports"
|
|
|
|
foreach ($forwarded_port in $forwarded_ports.GetEnumerator()) {
|
|
|
|
$port_set = netsh interface portproxy show v4tov4 | `
|
|
|
|
Where-Object { $_ -match "127.0.0.1\s*$($forwarded_port.Key)\s*127.0.0.1\s*$($forwarded_port.Value)" }
|
|
|
|
|
|
|
|
if (-not $port_set) {
|
|
|
|
Write-Verbose -Message "Adding netsh portproxy rule for $($forwarded_port.Key) -> $($forwarded_port.Value)"
|
|
|
|
$add_args = @(
|
|
|
|
"interface",
|
|
|
|
"portproxy",
|
|
|
|
"add",
|
|
|
|
"v4tov4",
|
|
|
|
"listenaddress=127.0.0.1",
|
|
|
|
"listenport=$($forwarded_port.Key)",
|
|
|
|
"connectaddress=127.0.0.1",
|
|
|
|
"connectport=$($forwarded_port.Value)"
|
|
|
|
)
|
|
|
|
$null = netsh $add_args 2>&1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Write-Verbose -Message "Using Port Mapper to configure forwarded ports"
|
|
|
|
# netsh interface portproxy doesn't work on local addresses in older
|
|
|
|
# versions of Windows. Use custom application Port Mapper to acheive the
|
|
|
|
# same outcome
|
|
|
|
# http://www.analogx.com/contents/download/Network/pmapper/Freeware.htm
|
|
|
|
$s3_url = "https://s3.amazonaws.com/ansible-ci-files/ansible-test/pmapper-1.04.exe"
|
|
|
|
|
|
|
|
# download the Port Mapper executable to a temporary directory
|
|
|
|
$pmapper_folder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([System.IO.Path]::GetRandomFileName())
|
|
|
|
$pmapper_exe = Join-Path -Path $pmapper_folder -ChildPath pmapper.exe
|
|
|
|
$pmapper_config = Join-Path -Path $pmapper_folder -ChildPath pmapper.dat
|
|
|
|
New-Item -Path $pmapper_folder -ItemType Directory > $null
|
|
|
|
|
|
|
|
$stop = $false
|
|
|
|
do {
|
|
|
|
try {
|
|
|
|
Write-Verbose -Message "Attempting download of '$s3_url'"
|
|
|
|
(New-Object -TypeName System.Net.WebClient).DownloadFile($s3_url, $pmapper_exe)
|
|
|
|
$stop = $true
|
|
|
|
} catch { Start-Sleep -Second 5 }
|
|
|
|
} until ($stop)
|
|
|
|
|
|
|
|
# create the Port Mapper rule file that contains our forwarded ports
|
|
|
|
$fs = [System.IO.File]::Create($pmapper_config)
|
|
|
|
try {
|
|
|
|
foreach ($forwarded_port in $forwarded_ports.GetEnumerator()) {
|
|
|
|
Write-Verbose -Message "Creating forwarded port rule for $($forwarded_port.Key) -> $($forwarded_port.Value)"
|
|
|
|
$pmapper_rule = Get-PmapperRuleBytes -ListenPort $forwarded_port.Key -ConnectAddress 127.0.0.1 -ConnectPort $forwarded_port.Value
|
|
|
|
$fs.Write($pmapper_rule, 0, $pmapper_rule.Length)
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
$fs.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
Write-Verbose -Message "Starting Port Mapper '$pmapper_exe' in the background"
|
|
|
|
$start_args = @{
|
|
|
|
CommandLine = $pmapper_exe
|
|
|
|
CurrentDirectory = $pmapper_folder
|
|
|
|
}
|
|
|
|
$res = Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments $start_args
|
|
|
|
if ($res.ReturnValue -ne 0) {
|
|
|
|
$error_msg = switch($res.ReturnValue) {
|
|
|
|
2 { "Access denied" }
|
|
|
|
3 { "Insufficient privilege" }
|
|
|
|
8 { "Unknown failure" }
|
|
|
|
9 { "Path not found" }
|
|
|
|
21 { "Invalid parameter" }
|
|
|
|
default { "Undefined Error: $($res.ReturnValue)" }
|
|
|
|
}
|
|
|
|
Write-Error -Message "Failed to start pmapper: $error_msg"
|
|
|
|
}
|
|
|
|
$pmapper_pid = $res.ProcessId
|
|
|
|
Write-Verbose -Message "Port Mapper PID: $pmapper_pid"
|
|
|
|
}
|
|
|
|
|
|
|
|
Write-Verbose -Message "Wait for current script at '$PSCommandPath' to be deleted before running cleanup"
|
|
|
|
$fsw = New-Object -TypeName System.IO.FileSystemWatcher
|
|
|
|
$fsw.Path = Split-Path -Path $PSCommandPath -Parent
|
|
|
|
$fsw.Filter = Split-Path -Path $PSCommandPath -Leaf
|
|
|
|
$fsw.WaitForChanged([System.IO.WatcherChangeTypes]::Deleted, 3600000) > $null
|
|
|
|
Write-Verbose -Message "Script delete or timeout reached, cleaning up Windows httptester artifacts"
|
|
|
|
|
|
|
|
Write-Verbose -Message "Cleanup host file entries"
|
|
|
|
$hosts_file_lines = [System.IO.File]::ReadAllLines($hosts_file)
|
|
|
|
$new_lines = [System.Collections.ArrayList]@()
|
|
|
|
$changed = $false
|
|
|
|
foreach ($host_line in $hosts_file_lines) {
|
|
|
|
if ($host_line.EndsWith("# ansible-test httptester")) {
|
|
|
|
$changed = $true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
$new_lines.Add($host_line) > $null
|
|
|
|
}
|
|
|
|
if ($changed) {
|
|
|
|
Write-Verbose -Message "Host file has extra entries, removing extra entries"
|
|
|
|
[System.IO.File]::WriteAllLines($hosts_file, $new_lines)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($os_version -ge [Version]"6.2") {
|
|
|
|
Write-Verbose -Message "Cleanup of forwarded port configured in netsh"
|
|
|
|
foreach ($forwarded_port in $forwarded_ports.GetEnumerator()) {
|
|
|
|
$port_set = netsh interface portproxy show v4tov4 | `
|
|
|
|
Where-Object { $_ -match "127.0.0.1\s*$($forwarded_port.Key)\s*127.0.0.1\s*$($forwarded_port.Value)" }
|
|
|
|
|
|
|
|
if ($port_set) {
|
|
|
|
Write-Verbose -Message "Removing netsh portproxy rule for $($forwarded_port.Key) -> $($forwarded_port.Value)"
|
|
|
|
$delete_args = @(
|
|
|
|
"interface",
|
|
|
|
"portproxy",
|
|
|
|
"delete",
|
|
|
|
"v4tov4",
|
|
|
|
"listenaddress=127.0.0.1",
|
|
|
|
"listenport=$($forwarded_port.Key)"
|
|
|
|
)
|
|
|
|
$null = netsh $delete_args 2>&1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Write-Verbose -Message "Stopping Port Mapper executable based on pid $pmapper_pid"
|
|
|
|
Stop-Process -Id $pmapper_pid -Force
|
|
|
|
|
|
|
|
# the process may not stop straight away, try multiple times to delete the Port Mapper folder
|
|
|
|
$attempts = 1
|
|
|
|
do {
|
|
|
|
try {
|
|
|
|
Write-Verbose -Message "Cleanup temporary files for Port Mapper at '$pmapper_folder' - Attempt: $attempts"
|
|
|
|
Remove-Item -Path $pmapper_folder -Force -Recurse
|
|
|
|
break
|
|
|
|
} catch {
|
|
|
|
Write-Verbose -Message "Cleanup temporary files for Port Mapper failed, waiting 5 seconds before trying again:$($_ | Out-String)"
|
|
|
|
if ($attempts -ge 5) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
$attempts += 1
|
|
|
|
Start-Sleep -Second 5
|
|
|
|
}
|
|
|
|
} until ($true)
|
|
|
|
}
|