I was successfully able to open a port on my computer (using only PowerShell) and know when HTTP requests are done to that port. I came up with this simple code:
$listener = [System.Net.Sockets.TcpListener]5566;
$listener.Start();
while ($true) {
$client = $Listener.AcceptTcpClient();
Write-Host "Connected!";
$client.Close();
}
If I open my browser and type http://localhost:5566 in the PowerShell interface it will show a message that a user got connected.
What I need to do is to get the GET parameters of this HTTP request. For example, if instead I had opened my browser and typed http://localhost:5566/test.html?parameter1=xxx¶meter2=yyy.
How can I grab the GET parameters (parameter1 and parameter2) name and values using my simplified code above?
If you are comfortable using the HttpListener instead of the TcpListener. It's easier to do the job.
Below script will output in a browser
Path is /test.html
parameter2 is equal to yyy
parameter1 is equal to xxx
Quick and dirty script
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add("http://localhost:5566/")
try {
$listener.Start();
while ($true) {
$context = $listener.GetContext()
$request = $context.Request
# Output the request to host
Write-Host $request | fl * | Out-String
# Parse Parameters from url
$rawUrl = $request.RawUrl
$Parameters = #{}
$rawUrl = $rawUrl.Split("?")
$Path = $rawUrl[0]
$rawParameters = $rawUrl[1]
if ($rawParameters) {
$rawParameters = $rawParameters.Split("&")
foreach ($rawParameter in $rawParameters) {
$Parameter = $rawParameter.Split("=")
$Parameters.Add($Parameter[0], $Parameter[1])
}
}
# Create output string (dirty html)
$output = "<html><body><p>"
$output = $output + "Path is $Path" + "<br />"
foreach ($Parameter in $Parameters.GetEnumerator()) {
$output = $output + "$($Parameter.Name) is equal to $($Parameter.Value)" + "<br />"
}
$output = $output + "</p></body></html>"
# Send response
$statusCode = 200
$response = $context.Response
$response.StatusCode = $statusCode
$buffer = [System.Text.Encoding]::UTF8.GetBytes($output)
$response.ContentLength64 = $buffer.Length
$output = $response.OutputStream
$output.Write($buffer,0,$buffer.Length)
$output.Close()
}
} finally {
$listener.Stop()
}
Cheers
Glenn
Related
I copied and pasted this code https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/creating-powershell-web-server in a powershell console directory which contains an index.html file
when browsing to http://localhost:8080/index.html I get an error Oops, the page is not available!
Is there something wrong with the code I can't see what ?
# enter this URL to reach PowerShell’s web server
$url = 'http://localhost:8080/'
# HTML content for some URLs entered by the user
$htmlcontents = #{
'GET /' = '<html><building>Here is PowerShell</building></html>'
'GET /services' = Get-Service | ConvertTo-Html
}
# start web server
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add($url)
$listener.Start()
try
{
while ($listener.IsListening) {
# process received request
$context = $listener.GetContext()
$Request = $context.Request
$Response = $context.Response
$received = '{0} {1}' -f $Request.httpmethod, $Request.url.localpath
# is there HTML content for this URL?
$html = $htmlcontents[$received]
if ($html -eq $null) {
$Response.statuscode = 404
$html = 'Oops, the page is not available!'
}
# return the HTML to the caller
$buffer = [Text.Encoding]::UTF8.GetBytes($html)
$Response.ContentLength64 = $buffer.length
$Response.OutputStream.Write($buffer, 0, $buffer.length)
$Response.Close()
}
}
finally
{
$listener.Stop()
}
It does work if you go to just http://localhost:8080/, you'd have to also have an index.html listing in order to browse to that too.
Just modify the $htmlContents section like so:
# HTML content for some URLs entered by the user
$htmlcontents = #{
'GET /' = '<html><building>Here is PowerShell</building></html>'
'GET /services' = Get-Service | ConvertTo-Html
'GET /index.html' = '<html><building>this is my index page</building></html>'
}
You could also have a statement like this.
$htmlcontents = #{
'GET /' = '<html><building>Here is PowerShell</building></html>'
'GET /services' = Get-Service | ConvertTo-Html
'GET /index.html' = '<html><building>this is my index page</building></html>'
'GET /fromPage.html' = Get-content "C:\temp\fence.txt"
}
In Windows PowerShell 3.0 was introduced Invoke-RestMethod cmdlet.
Invoke-RestMethod cmdlet accepts -Body<Object> parameter for setting the body of the request.
Due to a certain limitations Invoke-RestMethod cmdlet could not be used in our case. From the other hand, an alternative solution described in article InvokeRestMethod for the Rest of Us suits our needs:
$request = [System.Net.WebRequest]::Create($url)
$request.Method="Get"
$response = $request.GetResponse()
$requestStream = $response.GetResponseStream()
$readStream = New-Object System.IO.StreamReader $requestStream
$data=$readStream.ReadToEnd()
if($response.ContentType -match "application/xml") {
$results = [xml]$data
} elseif($response.ContentType -match "application/json") {
$results = $data | ConvertFrom-Json
} else {
try {
$results = [xml]$data
} catch {
$results = $data | ConvertFrom-Json
}
}
$results
But it is intended for a GET method only.
Could you please suggest how to extend this code sample with the ability to send the body of the request using POST method (similar to Body parameter in Invoke-RestMethod)?
First, change the line that updates the HTTP method.
$request.Method= 'POST';
Next, you need to add the message body to the HttpWebRequest object. To do that, you need to grab a reference to the request stream, and then add data to it.
$Body = [byte[]][char[]]'asdf';
$Request = [System.Net.HttpWebRequest]::CreateHttp('http://www.mywebservicethatiwanttoquery.com/');
$Request.Method = 'POST';
$Stream = $Request.GetRequestStream();
$Stream.Write($Body, 0, $Body.Length);
$Request.GetResponse();
NOTE: PowerShell Core edition is now open source on GitHub, and cross-platform on Linux, Mac, and Windows. Any issues with the Invoke-RestMethod cmdlet should be reported on the GitHub issue tracker for this project, so they can be tracked and fixed.
$myID = 666;
#the xml body should begin on column 1 no indentation.
$reqBody = #"
<?xml version="1.0" encoding="UTF-8"?>
<ns1:MyRequest
xmlns:ns1="urn:com:foo:bar:v1"
xmlns:ns2="urn:com:foo:xyz:v1"
<ns2:MyID>$myID</ns2:MyID>
</ns13:MyRequest>
"#
Write-Host $reqBody;
try
{
$endPoint = "http://myhost:80/myUri"
Write-Host ("Querying "+$endPoint)
$wr = [System.Net.HttpWebRequest]::Create($endPoint)
$wr.Method= 'POST';
$wr.ContentType="application/xml";
$Body = [byte[]][char[]]$reqBody;
$wr.Timeout = 10000;
$Stream = $wr.GetRequestStream();
$Stream.Write($Body, 0, $Body.Length);
$Stream.Flush();
$Stream.Close();
$resp = $wr.GetResponse().GetResponseStream()
$sr = New-Object System.IO.StreamReader($resp)
$respTxt = $sr.ReadToEnd()
[System.Xml.XmlDocument] $result = $respTxt
[String] $rs = $result.DocumentElement.OuterXml
Write-Host "$($rs)";
}
catch
{
$errorStatus = "Exception Message: " + $_.Exception.Message;
Write-Host $errorStatus;
}
I´m using a normal powershell httplistener script.
The script listenes on port 80 and gives an response.
Now I tried to handle more than one request as the same time. The problem is that the second respons has to wait until the first response was finished by the script.
I tried to start an own job for every http-request - but I can´t send a response to the listener from the PS-Job.
Does anyone know, how to handle parallel httprequests in PS?
Here is the Script I´m using:
$url = 'http://localhost/'
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add($url)
$listener.Start()
Write-Host "Listening at $url..."
while ($listener.IsListening)
{
$context = $listener.GetContext()
$requestUrl = $context.Request.Url
$response = $context.Response
Write-Host ''
Write-Host "> $requestUrl"
$localPath = $requestUrl.LocalPath
$route = $routes.Get_Item($requestUrl.LocalPath)
if ($route -eq $null)
{
$response.StatusCode = 404
}
else
{
$content = & $route
$buffer = [System.Text.Encoding]::UTF8.GetBytes($content)
$response.ContentLength64 = $buffer.Length
$response.OutputStream.Write($buffer, 0, $buffer.Length)
}
$response.Close()
$responseStatus = $response.StatusCode
Write-Host "< $responseStatus"
}
What is the right way to handle more than one request at the same time?
Thanks #all!
You can use multiple runspaces (even the runspace pool) within the same PowerShell process. See this blog post for details on how to do that.
Check the $request.url.LocalPath or another one of the url properties depending on how much of context you are looking for.
Provide a different response depending on the request.
$htmlout = "<html><link rel=""stylesheet"" href=""MyStleSheet.css""><body class=""body"" />Hello World</body></html>"
$css = ".body {background-color: white;font-size: 20px;font-family: calibri;}"
while($Listener.IsListening -and $noBreak){
$Context = $listener.GetContext()
$request = $context.request
if($request.url.LocalPath -match '.css')
{
$buffer = [System.Text.Encoding]::utf8.getbytes($css)
}
if($request.url.LocalPath -match '.html')
{
$buffer = [System.Text.Encoding]::utf8.getbytes($htmlout)
}
$response = $context.response
$response.contentlength64 = $buffer.length
$response.OutputStream.Write($buffer,0,$buffer.length)
}
I want to know how to catch url vars with powershell system.net.HttpListener
Thanks
$listener = New-Object system.net.HttpListener
$listener.Prefixes.Add('http://127.0.0.1:8080')
$listener.Start()
$context = $listener.GetContext() # block
$request = $context.Request
$response = $context.Response
# $var = read post/get var
$page = Get-Content -Path C:\play.html -Raw
$page = $page.Replace('%VAR%',$var)
$buffer = [System.Text.Encoding]::UTF8.GetBytes($page)
$response.ContentLength64 = $buffer.Length
$output = $response.OutputStream
$output.Write($buffer,0,$buffer.Length)
$output.Close()
$listener.Stop()
If the method header is GET then use the QueryString property to get the query parameters. If the method header is POST then check HasEntityBody property and if that is true, read the POST data from the body using the InputSteam property.
I have this function to read the SQL Server errorlog but the problem is that I'm not able to read the errorlog that the server is using at the time. I have been google-ing and it seems that the Fileshare flag isn't working for powershell. Is there some way to set the the Fileshare flag when I try to open the file?
function check_logs{
param($logs)
$pos
foreach($log in $logpos){
if($log.host -eq $logs.host){
$currentLog = $log
break
}
}
if($currentLog -eq $null){
$currentLog = #{}
$logpos.Add($currentLog)
$currentLog.host = $logs.host
$currentLog.event = $logs.type
$currentLog.lastpos = 0
}
$path = $logs.file
if($currentLog.lastpos -ne $null){$pos = $currentLog.lastpos}
else{$pos = 0}
if($logs.enc -eq $null){$br = New-Object System.IO.BinaryReader([System.IO.File]::Open($path, [System.IO.FileMode]::Open))}
else{
$encoding = $logs.enc.toUpper().Replace('-','')
if($encoding -eq 'UTF16'){$encoding = 'Unicode'}
$br = New-Object System.IO.BinaryReader([System.IO.File]::Open($path, [System.IO.FileMode]::Open), [System.Text.Encoding]::$encoding)
}
$required = $br.BaseStream.Length - $pos
if($required -lt 0){
$pos = 0
$required = $br.BaseStream.Length
}
if($required -eq 0){$br.close(); return $null}
$br.BaseStream.Seek($pos, [System.IO.SeekOrigin]::Begin)|Out-Null
$bytes = $br.ReadBytes($required)
$result = [System.Text.Encoding]::Unicode.GetString($bytes)
$split = $result.Split("`n")
foreach($s in $split)
{
if($s.contains(" Error:"))
{
$errorLine = [regex]::Split($s, "\s\s+")
$err = [regex]::Split($errorLine[1], "\s+")
if(log_filter $currentLog.event $err[1..$err.length]){$Script:events = $events+ [string]$s + "`n" }
}
}
$currentLog.lastpos = $br.BaseStream.Position
$br.close()
}
To be clear the error comes when I try to open the file. The error message is:
Exception calling "Open" with "2" argument(s): "The process cannot access the file
'C:\Program Files\Microsoft SQL Server\MSSQL10.MSSQLSERVER\MSSQL\Log\ERRORLOG'
because it is being used by another process."
Gísli
So I found the answer and it was pretty simple.
The binary reader constructor takes as input a stream. I didn't define the stream seperately and that's why I didn't notice that you set the FileShare flag in the stream's constructor.
What I had to do was to change this:
{$br = New-Object System.IO.BinaryReader([System.IO.File]::Open($path, [System.IO.FileMode]::Open))}
To this:
{$br = New-Object System.IO.BinaryReader([System.IO.File]::Open($path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite))}
And then it worked like a charm.
Gísli