I'm trying to get my head around how P2P works and how one can implement working P2P without having to forward ports on the router. As far as I know to do this we need to "punch holes" and one of the means of doing this is with NATUPnP.
Below I've got some powershell code which sets up a TCP listener on local port 42008:
$port = 42008
# Punch hole
$upnp = New-Object -ComObject "HNetCfg.NATUPnP"
$upnp.StaticPortMappingCollection.Add(42002, "TCP", $port, "localhost", $true, "Socket test")
# Create a TCP listener object
$listener = New-Object System.Net.Sockets.TcpListener([System.Net.IPAddress]::Any, $port)
# Start the listener
$listener.Start()
# Wait for incoming connections
while ($true) {
# Accept incoming connections
$client = $listener.AcceptTcpClient()
# Get the stream for the client
$stream = $client.GetStream()
# Read data from the stream
$buffer = New-Object byte[] 1024
$read = $stream.Read($buffer, 0, 1024)
# Convert the data to a string and print it
$data = [System.Text.Encoding]::ASCII.GetString($buffer, 0, $read)
Write-Host "Received: $data"
# Close the stream and the client
$stream.Close()
$client.Close()
}
As far as I understand it the hole punching above should redirect messages sent to <publicIP>:42002 to localhost:<$port> where the TCP Listener is listening. I then run the following client:
$upnp = New-Object -ComObject "HNetCfg.NATUPnP"
$externalIP = $upnp.StaticPortMappingCollection.item(42002, "TCP").ExternalIPAddress
$externalPort = 42002
# Connect to the remote machine
$client = New-Object System.Net.Sockets.TcpClient($externalIP, $externalPort)
# Get the stream for the client
$stream = $client.GetStream()
# Write data to the stream
$data = "Hello, World 2!"
$buffer = [System.Text.Encoding]::ASCII.GetBytes($data)
$stream.Write($buffer, 0, $buffer.Length)
# Close the stream and the client
$stream.Close()
$client.Close()
In this example I open up upnp just to grab the external IP and the external port, then I ping a message via TcpClient.
As far as I know this should work? But I keep getting the following exception:
New-Object : Exception calling ".ctor" with "2" argument(s): "No connection could be made because the target machine actively refused it <XX.XX.XX.XX>:42002"
At C:\Users\sancarn\Desktop\tbd\Powershell\SocketClient.ps1:7 char:11
+ $client = New-Object System.Net.Sockets.TcpClient($externalIP, $exter ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException
+ FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand
Why is my machine actively refusing connection? I've actively disabled my firewall just to check whether that was the issue, but to no avail. I can't think what else could be wrong unless I've just got the wrong end of the stick as to how to use it? Any ideas?
Related
The problem:
A client requires that we upload extracted data from our system to their box.com platform, rather than our normal SFTP utility. I have box.com credentials, and am aware they require FTPS not SFTP, and require passive mode. I've cribbed a fragment from ThomasMaurer's Powershell FTP Upload and Download script. Powershell version on my server is 4.0
Code fragment is:
#config
$Username = "username#host.com"
$Password = "redactedpassword"
$LocalFile = "C:\path\to\my\file.csv"
$RemoteFile = "ftp://ftp.box.com:990/file.csv"
#Create FTPWebRequest
$FTPRequest = [System.Net.FtpWebRequest]::Create($RemoteFile)
$FTPRequest = [System.Net.FtpWebRequest]$FTPRequest
$FTPRequest.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$FTPRequest.Credentials = New-Object System.Net.NetworkCredential($Username, $Password)
$FTPRequest.UseBinary = $true
$FTPRequest.UsePassive = $true
#read file for upload
$FileContent = gc -en byte $LocalFile
$FTPRequest.ContentLength = $FileContent.Length
#get stream request by bytes
$run = $FTPRequest.GetRequestStream()
$run.Write($FileContent,0,$FileContent.Length)
#cleanup
$run.Close()
$run.Dispose()
The error(s):
Exception calling "GetRequestStream" with "0" argument(s): "System error." At C:\path\to\my\powershellscript.ps1:28 char:1
+ $Run = $FTPRequest.GetRequestStream()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: () [], MethodInvocationException
+ FullyQualifiedErrorId: WebException
I also get downstream errors on calling the $FileContent.Length property and $run.close and $run.dispose().
Has anyone successfully automated to box (specifically) or to a passive implicit-ssl using only PowerShell 4.0 commands, and do you have a solid pattern I could reuse? Many thanks
I'm uploading files with a derived version of System.Net.WebClient, which supports FTP over TLS. This can easily be achieved by embedding C# code in PowerShell:
$typeDefinition = #"
using System;
using System.Net;
public class FtpClient : WebClient
{
protected override WebRequest GetWebRequest(Uri address)
{
FtpWebRequest ftpWebRequest = base.GetWebRequest(address) as FtpWebRequest;
ftpWebRequest.EnableSsl = true;
return ftpWebRequest;
}
}
"#
Add-Type -TypeDefinition $typeDefinition
$ftpClient = New-Object FtpClient
$ftpClient.UploadFile("ftp://your-ftp-server/yourfile.name", "STOR", "C:\YourLocalFile.name")
The answer by #h0r41i0 solves the problem by using WebClient. But as the WebClient internally uses (Ftp)WebRequest, it cannot be the solution on its own.
I'll assume that the "System error" occurs because either OP is trying to connect to a secure port (990) with an insecure connection.
Or because the file is too large and the OP code tries to read it whole to memory:
$FileContent = gc -en byte $LocalFile
In either case, there's no reason to give up on FtpWebRequest. Just use a secure connection (FtpWebRequest.EnableSsl). And an efficient way to feed the data from the file to the FTP stream, for example Stream.CopyTo:
$request = [Net.WebRequest]::Create("ftp://ftp.example.com/remote/path/file.zip")
$request.Credentials = New-Object System.Net.NetworkCredential("username", "password")
$request.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$request.EnableSsl = $True
$fileStream = [System.IO.File]::OpenRead("C:\local\path\file.zip")
$ftpStream = $request.GetRequestStream()
$fileStream.CopyTo($ftpStream)
$ftpStream.Dispose()
$fileStream.Dispose()
For other options, see Upload files with FTP using PowerShell.
Though note that .NET framework does not support implicit TLS (what is typical use of 990). Only explicit TLS. But support for the explicit TLS is more common ayway. See Does .NET FtpWebRequest Support both Implicit (FTPS) and explicit (FTPES)?
Probably too late to be useful to original questioner, but I found this other answer did the trick for me: Cyril Gupta's answer to Upload files with ftp using powershell
Here is my revised edition, including URL encoding (since the box.com usernames are email addresses which include the "at sign"):
## https://stackoverflow.com/a/2485696/537243
## User comment complains can't turn off passive mode,
## but that is exactly what we want here!
[Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null
# config
$Username = "foo#bar.com"
$Password = "s3cr3tpAssw0rd"
$Servername = "ftp.box.com"
# This is what we need URI it to look like:
# ftp://foo%40bar.com:s3cr3tpAssw0rd#ftp.box.com/
$baseURI = "ftp://$([System.Web.HttpUtility]::UrlEncode($Username)):$([System.Web.HttpUtility]::UrlEncode($Password))#$($Servername)"
$LocalFile = "C:\tmp\to_upload\data.csv"
$RemoteFile = "date.csv"
$ftpURI = "$($baseURI)/$($RemoteFile)"
Write-output "ftp uri: $($ftpURI)";
$webclient = New-Object -TypeName System.Net.WebClient;
$ftpURI = New-Object -TypeName System.Uri -ArgumentList $ftpURI; #"convert" it
$webclient.UploadFile($ftpURI, $LocalFile);
Write-output "Uploaded $($LocalFile) ... "; # of course since we didn't use try/catch or other error dectection this is a bit presuming.
Also should note this example uses plain FTP, not FTPS or SFTP.
I need help with a Powershell TCP client that can monitor when the server closes the connection. The starting point for this code is from here and looks like this:
## Connect-Computer.ps1
## Interact with a service on a remote TCP port
param(
[string] $remoteHost = "localhost",
[int] $port = 23
)
try
{
## Open the socket, and connect to the computer on the specified port
write-host "Connecting to $remoteHost on port $port"
$socket = new-object System.Net.Sockets.TcpClient($remoteHost, $port)
if($socket -eq $null) { return; }
$stream = $socket.GetStream()
$writer = new-object System.IO.StreamWriter($stream)
$buffer = new-object System.Byte[] 1024
$encoding = new-object System.Text.AsciiEncoding
while($true)
{
## Allow data to buffer for a bit
start-sleep -m 500
## Read all the data available from the stream, writing it to the
## screen when done.
while($stream.DataAvailable)
{
$read = $stream.Read($buffer, 0, 1024)
write-host -n ($encoding.GetString($buffer, 0, $read))
}
## Read the user's command, quitting if they hit ^D
$command = read-host
## Write their command to the remote host
$writer.WriteLine($command)
$writer.Flush()
}
}
finally
{
## Close the streams
$writer.Close()
$stream.Close()
}
The problem is when the server closes the port the client continues to wait. How can I use a buffered read but still check if the port is still open? I dont need to know immediately if the port closes, just after a few minutes.
Thanks in advance for any help.
I'm trying to create a script that my customers can use to run a quick health check on an application of ours. My goal is for them to be able to run the script directly on their workstation and get the results back. The program is required to communicate to remote devices over specific ports, and I'd like the script to check those connections and display if they're working properly or not.
I found this script in a post a few years back that seems to work beautifully, but I can only seem to get it to check the connection between the workstation and the final device, not between our server and the final device:
$hostname = 'HOSTNAME'
$port = '104'
function Test-Port($hostname, $port) {
# This works no matter in which form we get $host - hostname or ip address
try {
$ip = [System.Net.Dns]::GetHostAddresses($hostname) |
Select-Object IPAddressToString -ExpandProperty IPAddressToString
if ($ip.GetType().Name -eq "Object[]") {
#If we have several ip's for that address, let's take first one
$ip = $ip[0]
}
} catch {
Write-Host "Possibly $hostname is wrong hostname or IP"
return
}
$t = New-Object Net.Sockets.TcpClient
# We use Try\Catch to remove exception info from console if we can't connect
try {
$t.Connect($ip, $port)
} catch {}
if ($t.Connected) {
$t.Close()
$msg = "Port $port is operational"
} else {
$msg = "Port $port on $ip is closed, "
$msg += "You may need to contact your IT team to open it. "
}
Write-Host $msg
}
Test-Port $hostname $port
I have attempted running the last line as:
Test-Port -ComputerName 'SERVERSHOSTNAME' $hostname $port
But that throws:
Test-Connection : A positional parameter cannot be found that accepts
argument '153'.
At line:1 char:1
+ Test-Connection -ComputerName 'SERVERSHOSTNAME' $hostname $port
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Test-Connection], ParameterBindingException
+ FullyQualifiedErrorId :
I've also tried adding a
foreach ($server in $servers)
But it still seems to be checking the connection from the workstation (I checked this by putting in an IP on the server list that doesn't exist). Anybody have any idea how I can get this to work?
I'm having trouble writing a PowerShell script that can both send and receive data via a TCP connection. It only seems to let me do one or the other.
Below is what I have so far. I want to listen and wait for a connection, then once established, receive a string containing an IP address, do some fancy lookup to see what user is logged into that machine, then send back the username. If I only send data, it works. If I only receive data, it works. If I try to do both, only the receive works. What am I doing wrong?
$port = 1234
do {
$user = ""
$endpoint = new-object System.Net.IPEndPoint ([system.net.ipaddress]::any, $port)
$listener = new-object System.Net.Sockets.TcpListener $endpoint
$listener.start()
$client = $listener.AcceptTcpClient() # will block here until connection
$stream = $client.GetStream();
$reader = New-Object System.IO.StreamReader $stream
$writer = New-Object System.IO.StreamWriter $stream
$add = $reader.ReadLine()
#$reader.close()
write-host "Request from " $add
if($add) {
$user = & wmic /Node:$add ComputerSystem Get UserName
write-host "User returned is " $user[2]
}
if($user[2] -eq "ERROR:") {
$user[2] = "ErrNoUserW"
} elseif(!$user[2]) {
$user[2] = "ErrServerW"
}
$writer.Write($user[2])
#$writer.close()
$stream.close()
$client.close()
$listener.stop()
} while(1)
I thought that the stream could only be read or write at any given time. Have you tried closing the stream then re-opening it?
I need a named pipe to read and write.
In a program I create the pipe server using from kernel32.dll:
string PipeName = "\\\\.\\pipe\\myMT4";
int PipeMode = PIPE_TYPE_MESSAGE|PIPE_READMODE_MESSAGE|PIPE_WAIT; # tried too: PIPE_NOWAIT
int hPipe = CreateNamedPipeW(
PipeName,
PIPE_ACCESS_DUPLEX,
PipeMode,
PIPE_UNLIMITED_INSTANCES,1024,1024,
NMPWAIT_USE_DEFAULT_WAIT,NULL);
The handle hPipe is valid - every things seems to be ok here!
But in a PowerShell-script I want to open a client, connect and open the writer -
and cannot connect => timed out
function connect{
Param ([PSObject] $h)
...
$h.Pipe = New-Object -TypeName System.IO.Pipes.NamedPipeClientStream "\\.\pipe\PipeTest"
$h.Pipe.Connect( 5000 )
$h.Writer = New-Object -TypeName System.IO.StreamWriter $h.Pipe, $h.Encode
I really would prefer this way to have a similar access when reading and writen
either from/to the pipe and sockets e.g.:
function write{
Param ([PSObject] $h, [string] $line )
try {
$h.Writer.Write($line)
}
What is wrong?
Thanks in advance,
Gooly.
PS:
It seems that the program cannot deal with pipe-servers - I have to open a pipe-client, and that works but that causes other problems:
I define for the PowerShell-pipe-server :
$pipeName = "testpipe"
$pipeDir = [System.IO.Pipes.PipeDirection]::InOut
$pipeMsg = [System.IO.Pipes.PipeTransmissionMode]::Message
$pipeOpti = [System.IO.Pipes.PipeOptions]::Asynchronous
$pipe = New-Object system.IO.Pipes.NamedPipeServerStream(
$pipeName, $pipeDir, 1, $pipeMsg, $pipeOpti )
$pipe.WaitForConnection() #
$sw = new-object System.IO.StreamWriter $pipe
$sw.AutoFlush = $true
$sw.WriteLine("Server pid is $pid")
$sw.Dispose()
$pipe.Dispose()
1) My first problem is now that the powerShell-pipe-server is blocked by
$pipe.WaitForConnection()
until a client connects but it must handle 2 different sockets independently meanwhile and
2) if the client closes the connection I was unable to tell the client to open the same pipe again and the client gets the Windows-error: ERROR_PIPE_BUSY 231
Form the program I connect to the server with the kernel32.dll-function:
int CallNamedPipeW(string PipeName,
string outBuffer, int outBufferSz,
uint& inBuffer[], int inBufferSz,
int& bytesRead[], int timeOut
);
Any ideas?
Hmm, I can get named pipes to work between two different PowerShell sessions so I don't think it is an inherent PowerShell limitation:
Here is the server script:
$pipe = new-object System.IO.Pipes.NamedPipeServerStream 'testpipe','Out'
$pipe.WaitForConnection()
$sw = new-object System.IO.StreamWriter $pipe
$sw.AutoFlush = $true
$sw.WriteLine("Server pid is $pid")
$sw.Dispose()
$pipe.Dispose()
Here is the client script:
$pipe = new-object System.IO.Pipes.NamedPipeClientStream '.','testpipe','In'
$pipe.Connect()
$sr = new-object System.IO.StreamReader $pipe
while (($data = $sr.ReadLine()) -ne $null) { "Received: $data" }
$sr.Dispose()
$pipe.Dispose()
Client outputs:
Received: Server pid is 22836