PowerShell v3 Invoke-WebRequest: Troubles with forms - forms

Since I upgraded to Windows 8 a lot of my PowerShell scripts relying on launching an invisible IE won’t quite work anymore, so I tried switching to the Invoke-WebRequest command. I did a lot of googling but still can’t get my script to work.
This is what it should do:
load up a website with a simple form (username, password, submit-button),
enter the credentials
and submit them.
The Microsoft tech-net examples were not very helpful for me, that is what I pieced together:
$myUrl = "http://some.url"
$response = Invoke-WebRequest -Uri $myUrl -Method Default -SessionVariable $rb
$form = $response.Forms[0]
$form.Fields["user"] = "username"
$form.Fields["password"] = "password"
$response = Invoke-WebRequest -Uri $form.Action -WebSession $rb -Method POST
$response.StatusDescriptionOK
I receive two errors, the first one when trying to write into the user field:
Cannot index into a null array.
$form.Fields["user"] = "username"
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : NullArray
The second one has to do with the $form.Action which I have no idea what it should read:
Invoke-WebRequest : Cannot validate argument on parameter 'Uri'. The argument is null or empty. Supply an argument that is not null or empty and then try the command again.
Again, I relied heavily on example #2 at Microsoft.

Try doing the post directly e.g.:
$formFields = #{username='john doe';password='123'}
Invoke-WebRequest -Uri $myUrl -Method Post -Body $formFields -ContentType "application/x-www-form-urlencoded"

To address your problem with the unsigned/untrusted certificate, add the line
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
before the Invoke-WebRequest statement

The example in the question works, but you have to use rb and not $rb in the first line:
$response = Invoke-WebRequest -Uri $myUrl -Method Default -SessionVariable rb
I also had to use ($myUrl + '/login') since this is my login address.
$response = Invoke-WebRequest -Uri ($myUrl + '/login') -Method Default -SessionVariable rb
And in the last line used ($myUrl + $form.Action):
$response = Invoke-WebRequest -Uri ($myUrl + $form.Action) -WebSession $rb -Method POST

If you're me and have been troubleshooting a bad Web Request, in my case a -Body that was becoming null at my API, then you will want to know about the gotcha that is about interleaving your line continuations with comments. This
$r = iwr -uri $url `
-method 'POST' `
-headers $headers `
# -contenttype 'application/x-www-form-urlencoded' ` # default
-Body $body
Notice the commented out line # -contenttype 'application/x-www-form-urlencoded' # default
Putting a comment truncates the remaining back-ticked line continuation. Therefore, in my case my web request ended up with a request having 0-byte payload.

Related

REST-API Basic Authentication and Invoke-WebRequest via Powershell

Trying to connect to a REST-API via Powershell client. When testing the endpoint in Postman, I have no problems at all. Here's the main part of the function (I have a [pscredential]$Creds parameter that I use to get the username and password):
[string]$username = $Creds.UserName
[string]$password = (New-Object System.Net.NetworkCredential($Creds.UserName, $Creds.Password, 'Null')).Password
[string]$authorizationInfo= ([Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(('{0}:{1}' -f $username, $password))))
Invoke-WebRequest -Uri "https://$($HostName)/api/" -Method Get -Headers #{Authorization = ('Basic {0}' -f $authorizationInfo)}
For some reason the Authorization header is different in my script than in Postman. I can even copy the Authorization header out of Postman and paste it into the -Headers parameter and everything works fine. I just don't see where I'm getting this wrong.
I can't tell you why that's not working, but I can suggest something that works for me all the time with APIs:
$auth = $username + ':' + $upassword
$Encoded = [System.Text.Encoding]::UTF8.GetBytes($auth)
$authorizationInfo = [System.Convert]::ToBase64String($Encoded)
$headers = #{"Authorization"="Basic $($authorizationInfo)"}
Invoke-WebRequest -Uri "https://$($HostName)/api/" -Method GET -Headers $headers
If that doesn't work, try this subtle difference with Invoke-Restmethod:
Invoke-RestMethod -Uri "https://$($HostName)/api/" -Method GET -Headers $headers
Working with APIs is always an adventure. Keep trying. :)

Making a call to a Flask Restful API with invoke-restmethod and JWT auth

I have this API that I made with Flask (Python) and I am unable to get powershell to successfully make an API request to it. The problem is not the API because I tested it with postman and everything works as it should. More precisely it's when I add JWT token auth that it's not working with powershell, in my api the Create, Update and delete functions are protected with JWT token auth and the basic Read function have no auth at all. Below is the powershell code i am using:
Here is the function that generate my token (that part is working, note that I removed username and password from the function for security purposes):
function get-token {
param (
[Parameter(Position=0, Mandatory=$false)]
[string]$user,
[Parameter(Position=1, Mandatory=$false)]
[string]$password
)
$body = #{
username="$user"
password="$password"
}
$jsonbody = $body | ConvertTo-Json
$uri = "https://codegenius.live/api/auth"
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Accept","application/json")
$headers.Add("Content-Type","application/json")
$response = Invoke-RestMethod -Uri $uri -Method Post -Body $jsonbody -Headers $headers
$response.access_token
}
This function will generate a token like this:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1ODIyMzA2OTUsImlhdCI6MTU4MjIzMDM5NSwibmJmIjoxNTgyMjMwMzk1LCJpZGVudGl0eSI6MX0.8wZZJEGxV7P4ZzN23eZ3d5-MGJ00N5zKHuCZXn9XRuw
Here is the call i am trying to make that doesn't work:
$body = #{
tpl_name="my template name"
tpl_subject="my template subject"
tpl_plaintext_content="bla bla bla"
tpl_html_content="bla bla bla"
}
$authheader = "JWT " + (get-token)
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Accept","application/json")
$headers.Add("Content-Type","application/json")
$headers.Add("Authorization",$authheader)
$jsonbody = $body | ConvertTo-Json
$uri = "https://codegenius.live/api/add-email"
$response = Invoke-RestMethod -Uri $uri -Method Post -Body $jsonbody -Headers $headers
The request response message is this one:
Invoke-RestMethod :
500 Internal Server Error
Internal Server Error
The server encountered an internal error and was unable to complete your request. Either the server
is overloaded or
there is an error in the application.
Au caractère Ligne:25 : 17
+ ... $response = Invoke-RestMethod -Uri $uri -Method Post -Body $jsonbody ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation : (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-
RestMethod], WebEx
ception
+ FullyQualifiedErrorId :
WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
This is very strange because as I said it is working with Postman (see below images), I have tried googling the issue but I found no clue on how to actually solve this.
EDIT: the images show that in postman i used http instead of https, this is not the issue. In powershell http or https nothing works!
Change your line to this,
$authheader = "JWT $(get-token)"

PowerShell doesn't recognize provided parameter

Operating System - Windows 10
Powershell version - 5.1.15063.1088
I'm using Windows PowerShell ISE (if that makes any sense). When running simple HTTP Post function
Function MyBeautifulFunction {
$myCredentials = Get-Credential
$body = #{
"query"="SELECT [System.Wheel],[System.SparkPlug],[System.EngineOil],[System.Headlight],[System.RadioButton] FROM JunkYard WHERE [System.Brand] = 'Ford' AND [System.Model] = 'GT' AND [System.Title] CONTAINS 'Rebuild'"
} | ConvertTo-Json
Invoke-RestMethod -Uri 'http://example.com//ford/parts/fuelpump/_apis/wit/wiql?api-version=2.0' `
-Method Post `
-Credential $myCredentials `
-Body $body `
-ContentType 'application/json'
}
MyBeautifulFunction
Editor's note: The Invoke-RestMethod call was originally spread across 2 lines without line continuation, which part of Adam's answer below discusses.
I'm getting following error message in PowerShell ISE console window:
"message":"The request indicated a Content-Type of \"application/x-www-form-urlencoded\" for method type \"POST\" which is not supported. Valid
content types for this method are: application/json, application/json-patch+json.
It's clearly visible that I'm passing 'application/json' in the parameter, but for some reason PowerShell still complains. Has anyone faced this error before? Should I report to Microsoft with it? Thanks for the advice...
Couple of things...
First, I call REST services all the time with a content type of application/json, and I don't have any problems. For example, the following works fine for me:
$url = 'https://jsonplaceholder.typicode.com/posts/1'
$response_from_webservice = Invoke-RestMethod -Uri $url -ContentType 'application/json' -Method 'Get'
jsonplaceholder... is a public service, available for testing. Feel free to run the snippet on your machine in-case you need a sanity check.
Second, this sample code you sent had this Accept parameter:
Invoke-RestMethod -Uri 'http://example.com//ford/parts/fuelpump/_apis/wit/wiql?api-version=2.0' -Method Post -Credential $myCredentials
-Body $body -ContentType 'application/json' -Accept 'application/json'
I don't believe I've seen that before. I am also running Windows 10 with Powershell v5.1.15063.1088. I've checked the docs. I checked out the commandlet's parameters. I didn't find it.
I share this to explain, I can't recreate your scenario with that call signature.
If you want to add an accept entry to the HTTP header, you'd have to do that directly:
$h = new-object 'system.collections.generic.dictionary[[string],[string]]'
$h.add('accept', 'application/json')
$r = Invoke-RestMethod -Uri '...' -ContentType 'application/json' -Method 'Post' -Headers $h
Third, it sort of looks like you're not specifying the content type in your snippet. The snippet has the Invoke-RestMethod on a seperate line from the body and contenttype parameters. Just to be clear, it's not wrapping. It looks like there is a line break there. That means that the second line -Body $body -ContentType 'application/json' -Accept 'application/json' is unassociated with the call that precedes it; the following is a copy and paste from your post:
Invoke-RestMethod -Uri 'http://example.com//ford/parts/fuelpump/_apis/wit/wiql?api-version=2.0' -Method Post -Credential $myCredentials
-Body $body -ContentType 'application/json' -Accept 'application/json'
If you want to put the parameters on a separate line, just use a backtick.
Invoke-RestMethod -Uri 'http://example.com//ford/parts/fuelpump/_apis/wit/wiql?api-version=2.0' `
-Method Post `
-Credential $myCredentials `
-Body $body `
-ContentType `
'application/json'
Now, if you don't specify the ContentType (as I'm suggesting) it will default to "application/x-www-form-urlencoded" on a post. According to Microsoft:
-ContentType
Specifies the content type of the web request.
If this parameter is omitted and the request method is POST,
Invoke-RestMethod sets the content type to
application/x-www-form-urlencoded. Otherwise, the content type is not
specified in the call.
You need to pass headers in to the headers argument
$headers = #{
"Content-Type" = "application/json",
"Accept" = "application/json"
}
Invoke-RestMethod -Uri 'http://example.com/123' -Method Post -Body $body -Headers $headers
See - https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-restmethod

Using Powershell to login to a url and download a file

I am having difficulty creating a Powershell script to login to a website and download a file. The script seems to be logging in just fine (I receive a successful status return) but when I attempt to download the file, I receive an Unauthorized message in return.
Code is below:
$r=Invoke-WebRequest http://testurl/index.htm -SessionVariable fb
$form = $r.Forms[0]
$form.fields["loginusername"] = "user"
$form.fields["loginpassword"] = "pass"
$r=Invoke-WebRequest -Uri ("https://testurl/index.htm" + $form.Action) -WebSession $fb -Method POST -Body $form.Fields
Invoke-WebRequest -Uri "https://testurl/api/table.csv" -WebSession $fb -OutFile "C:\status\current.csv"
Thanks for any guidance!
I've seen the example you're trying to reference. It had a few mistakes when I tried to replicate it, too. I've automated some site logins/downloads; here's an adjustment to try:
$r=Invoke-WebRequest http://testurl/index.htm
$r.Forms.fields.loginusername = 'user'
$r.Forms.fields.loginpassword = 'pass'
Invoke-WebRequest -Uri 'https://testurl' + $form.Action) -SessionVariable fb -Method POST -Body $r
Invoke-WebRequest -Uri 'https://testurl/api/table.csv' -WebSession $fb -OutFile 'C:\status\current.csv'

powershell Invoke-WebRequest WebSession not working

I can't get the following code to work. It appears to log in but then returns the login page with $response. I am guessing it has something to do with the postbacks? Any way to get around that? Thanks!
$login = Invoke-WebRequest -Uri 'http://www.sqlpass.org/UserLogin.aspx' -SessionVariable sqlpass
$login.Forms[0].Fields["txtUsername_14615"] = 'myuser'
$login.Forms[0].Fields["txtPassword_14615"] = 'mypass'
$response = Invoke-WebRequest -Uri 'http://www.sqlpass.org/UserLogin.aspx' -WebSession $sqlpass -Method POST -Body $login
There is an event target field that also needs to be set, also the POST needs a different URL, i tested the solution below and it works:
$login = Invoke-WebRequest -Uri 'http://www.sqlpass.org/UserLogin.aspx' -SessionVariable sqlpass
$form = $login.Forms[0]
$form.Fields["__EVENTTARGET"] = "UserLogin"
$form.Fields["txtUsername_14615"] = 'myuser'
$form.Fields["txtPassword_14615"] = 'mypass'
Invoke-WebRequest -Uri 'http://www.sqlpass.org/UserLogin.aspx?returnurl=%2fdefault.aspx' -WebSession $sqlpass -Method POST -Body $form.Fields
Note: Just as a side note, you can use Web Debugging proxies like Fiddler to debug issues like this, which is exactly what i did.