PowerShell WebRequest POST - powershell

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;
}

Related

Powershell - How to receive large response-headers from error-response of a web-request?

I am looking for a solution to parse an error-response of a given web-service.
Below sample works great in general, but if the response is larger than 64kb then the content is not availabe in the exception at all.
I have seen some solutions recommending to use webHttpClient and increase the MaxResponseContentBufferSize here, but how can I do this for a given WebClient-object?
Is there any option to change that BufferSize globally for all net-webcalls like below TLS12-settings?
Here is my sample-code:
# using net-webclient to use individual user-side proxy-settings:
$web = new-object Net.WebClient
[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$url = "address to web-service"
try {
$response = $web.DownloadString($url)
} catch [System.Net.WebException] {
# this part needs to work even if the error-response in larger than 64kb
# unfortunately the response-object is empty in such case
$message = $_.Exception.Response
$stream = $message.GetResponseStream()
$reader = new-object System.IO.StreamReader ($stream)
$body = $reader.ReadToEnd()
write-host "#error:$body"
}
I solved it at the end by switching to system.net.httpclient.
That way I still repect any custom proxy-settings and also avoid the above mentioned 64kb-limit in any error-response. Here a sample how to use it:
$url = "address to web-service"
$cred = Get-Credential
# define settings for the http-client:
Add-Type -AssemblyName System.Net.Http
$ignoreCerts = [System.Net.Http.HttpClientHandler]::DangerousAcceptAnyServerCertificateValidator
$handler = [System.Net.Http.HttpClientHandler]::new()
$handler.ServerCertificateCustomValidationCallback = $ignoreCerts
$handler.Credentials = $cred
$handler.PreAuthenticate = $true
$client = [System.Net.Http.HttpClient]::new($handler)
$client.Timeout = [System.TimeSpan]::FromSeconds(10)
$result = $client.GetAsync($url).result
$response = $result.Content.ReadAsStringAsync().Result
write-host $response

How to get GET parameter?

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&parameter2=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

Adding Azure Table Entity with Powershell with REST API

I've been struggling with what appears to be a common problem: formatting my authorization header for the Azure Table Service REST API. I have been unable to find an example using PowerShell and SharedKey, and am worried that I am making some dumb mistake in working backwards from other examples.
The specific (though unspecific) error is: Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
I have been referencing these articles, as well as other examples:
https://learn.microsoft.com/en-us/rest/api/storageservices/fileservices/Authentication-for-the-Azure-Storage-Services?redirectedfrom=MSDN
https://learn.microsoft.com/en-us/rest/api/storageservices/fileservices/addressing-table-service-resources
I've confirmed that my key is correct, the table exists, has at least one row, and a number of other suggestions without luck.
If there is another approach I should take to accomplish the same goal, I'm all ears. I would like to use the REST API to allow for maximum compatibility on the clients.
$tableEndpoint = 'https://STORAGEACCOUNTNAME.table.core.windows.net/'
$tableName = 'TABLENAME'
$StorageAccountName = 'STORAGEACCOUNTNAME'
$Key = "STORAGEACCOUNTKEY"
Function New-AuthorizationHeader
{
param ($canonicalizedString)
[byte[]]$Bytes = [system.convert]::FromBase64String($Key)
$HMACSHA256 = [System.Security.Cryptography.HMACSHA256]::new($Bytes)
$dataToHmac = [System.Text.Encoding]::UTF8.GetBytes($canonicalizedString)
$Signature = [System.Convert]::ToBase64String($HMACSHA256.ComputeHash($dataToHmac))
[string]$AuthorizationHeader = "{0} {1}:{2}" -f "SharedKey",$StorageAccountName,$Signature
$AuthorizationHeader
}
Function New-Entity
{
param ($jsonContent)
$requestMethod = "POST"
$contentMD5 = [string]::Empty
$storageServiceVersion = '2016-05-31'
$reqDate = (Get-Date -Format r)
$contentType = "application/json"
$canonicalizedResource = "/{0}/{1}" -f $StorageAccountName,($tableEndpoint + $tableName)
$stringToSign = "{0}`n{1}`n{2}`n{3}`n{4}" -f $requestMethod,$contentMD5,$contentType,$reqDate,$canonicalizedResource
$authorizationHeader = New-AuthorizationHeader -canonicalizedString $stringToSign
$content = [System.Text.Encoding]::UTF8.GetBytes($jsonContent)
$fullURI = New-Object -TypeName System.Uri -ArgumentList ($tableEndpoint + $tableName)
$httpWebRequest = [System.Net.WebRequest]::Create($fullURI)
$httpWebRequest.Accept = 'application/json;odata=fullmetadata'
$httpWebRequest.ContentLength = $content.length
$httpWebRequest.ContentType = $contentType
$httpWebRequest.Method = $requestMethod
$httpWebRequest.Headers.Add("x-ms-date", $reqDate)
$httpWebRequest.Headers.Add("x-ms-version", $storageServiceVersion)
$httpWebRequest.Headers.Add("Authorization", $authorizationHeader)
$httpWebRequest.Headers.Add("Accept-Charset", "UTF-8")
$httpWebRequest.Headers.Add("DataServiceVersion", "3.0;NetFx")
$httpWebRequest.Headers.Add("MaxDataServiceVersion", "3.0;NetFx")
$requestStream = $httpWebRequest.GetRequestStream()
$requestStream.Write($content, 0, $content.length)
$requestStream.Close()
$response = $httpWebRequest.GetResponse()
$dataStream = $response.GetResponseStream()
$reader = New-Object -TypeName System.IO.StreamReader($dataStream)
$responseFromServer = $reader.ReadToEnd()
}
$jsonContent = #"
{
"ExecutionStatus"="smapledata",
"PartitionKey"="$ENV:Username",
"RowKey"="PrinterScript"
}
"#
New-Entity -jsonContent $jsonContent
Please make 2 changes above:
Canonical resource should not have table endpoint. So it should be:
$canonicalizedResource = "/{0}/{1}" -f $StorageAccountName,$tableName
JSON body should be properly formatted. So it should be:
$jsonContent = #"
{
"ExecutionStatus":"smapledata",
"PartitionKey":"$ENV:Username",
"RowKey":"PrinterScript"
}
"#
Once you make these changes, the code should work just fine.
Thank you again for your responses, Gaurav.
I verified that my clock is not skewed. In the end, I switched to using a Shared Access Signature, which is probably a better practice anyway.
Using a SAS obviates the need for an Authorization header (and getting that correctly formatted).
Here's the relevant updated PowerShell:
$tableEndpoint = 'https://STORAGEACCOUNT.table.core.windows.net/'
$tableName = 'TABLENAME'
$SAS = "?sv=2016-05-31&ss=t&srt=o&sp=wa&se=2017-09-01T04:08:11Z&st=2017-03-14T20:08:11Z&spr=https&sig=SIGNATURE"
$URI = $tableEndpoint + $tableName + $SAS
If (-NOT $script:RunLogKeyTime)
{
$script:RunLogKeyTime = (Get-Date -Format 'yyyyMMdd-HHmmss')
}
$RequestBody = ConvertTo-Json -InputObject #{
"TagetName"= $TargetName;
"Message"= $Message;
"ComputerName"= $ENV:ComputerName;
"Username"= $ENV:Username;
"EntryType"= $EntryType;
"PartitionKey"= "$Username`_$ScriptIdentifier";
"RowKey"= "$EntryType"}
$EncodedRequestBody = [System.Text.Encoding]::UTF8.GetBytes($RequestBody)
$RequestHeaders = #{
"x-ms-date"=(Get-Date -Format r);
"x-ms-version"="2016-05-31";
"Accept-Charset"="UTF-8";
"DataServiceVersion"="3.0;NetFx";
"MaxDataServiceVersion"="3.0;NetFx";
"Accept"="application/json;odata=nometadata";
"ContentLength"=$EncodedRequestBody.Length}
Invoke-WebRequest -Method POST -Uri $URI -Headers $RequestHeaders -Body $EncodedRequestBody -ContentType "application/json"

Powershell httplistener handle more than one request at the same time

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)
}

How to post a tweet to twitter using Powershell?

Has anyone used the following code? How do I make it post a tweet? I know I have to use the "$req.Context.RawUri = [Uri]'http://api.twitter.com/version/statuses/update.xml" but I can't get the "$res = [xml][DevDefined.OAuth.Consumer.ConsumerRequestExtensions]::ReadBody($req)" right.
Add-Type -Path C:\OAuthDevDefined\DevDefined.OAuth.dll
$cons = New-Object devdefined.oauth.consumer.oauthconsumercontext
$cons.ConsumerKey = 'key'
$cons.ConsumerSecret = 'key'
$cons.SignatureMethod = [devdefined.oauth.framework.signaturemethod]::HmacSha1
$session = new-object DevDefined.OAuth.Consumer.OAuthSession $cons, $null, $null, $null
$accessToken = new-object DevDefined.OAuth.Framework.TokenBase
$at = import-cliXml C:\temp\myTwitterAccessToken.clixml
$accessToken.ConsumerKey, $accessToken.Realm, $accessToken.Token, $accessToken.TokenSecret = `
$at.ConsumerKey, $at.Realm, $at.Token, $at.TokenSecret
$req = $session.Request($accessToken)
$req.Context.RequestMethod = 'GET'
$req.Context.RawUri = [Uri]'http://api.twitter.com/1/statuses/friends_timeline.xml?count=5'
$res = [xml][DevDefined.OAuth.Consumer.ConsumerRequestExtensions]::ReadBody($req)
$res.statuses.status | % { $_.user.Name }
I use OAuth by DevDefined as well. My function looks like this:
function Post-Twitter {
param(
[Parameter(Mandatory=$true)][string]$url
)
if (!$script:accessToken) {
throw 'token is not initialized'
}
try {
$cons = New-Object devdefined.oauth.consumer.oauthconsumercontext
$cons.ConsumerKey = $consumerKey
$cons.ConsumerSecret = $consumerSecret
$cons.SignatureMethod = [devdefined.oauth.framework.signaturemethod]::HmacSha1
$session = new-object DevDefined.OAuth.Consumer.OAuthSession `
$cons,
"http://twitter.com/oauth/request_token",
"http://twitter.com/oauth/authorize",
"http://twitter.com/oauth/access_token"
$token = Get-AccessToken
$req = $session.Request($token)
$req.Context.RequestMethod = 'POST'
$req.Context.RawUri = new-object Uri $url
[DevDefined.OAuth.Consumer.ConsumerRequestExtensions]::ReadBody($req)
} catch {
Write-Warning "Exception: $_"
$null
}
}
Then for simplicity I pass status in query string:
add-type -assembly System.Web
$status = [system.Web.Httputility]::UrlEncode('some tweet')
Post-Twitter "http://api.twitter.com/1/statuses/update.xml?status=$status"
It seems that you know about the consumer key/secret and the token thing, so I'll leave it without further explanation.
I’ve just posted a Powershell Twitter REST API 1.1 Module on TechNet Gallery… You'll be able to post/get from Twitter API! https://goo.gl/s7pmmA