How to make multi-line uri using invoke-webrequest in Powershell - powershell

I'm trying to concatenate my uri in this invoke so that I can have each variable on a separate line. That way I can make changes easier and don't have to search as hard. I was able to do this in a bash script, but am at a loss for how to do this in Powershell.
Line as follows:
Invoke-WebRequest -Uri (beginning of url)?date=$date"&"time=$time"&"name=$env:computername"&"loginid=$env:username"&"sn=$serialnumber"&"ipaddr=$ipaddr"&"verb=profileclear
Thanks!

I think the easiest way of achieving this would be like this:
$uri = "Https://something.somewhere/?" +
"date=$date&" +
"time=$time&" +
"name=$env:computername&" +
"loginid=$env:username&" +
"sn=$serialnumber&" +
"ipaddr=$ipaddr&" +
"verb=profileclear"
Invoke-WebRequest -Uri $uri

I would create an array of variable strings, join them, and build a uri:
$variables = "date=$date",
"time=$time",
"name=$env:computername",
"loginid=$env:username",
"sn=$serialnumber",
"ipaddr=$ipaddr",
"verb=profileclear"
$uri = [System.UriBuilder]::new('https://contoso.com')
$uri.Query = $variables -join '&'
Invoke-WebRequest -Uri $uri.ToString()

One option is to store the query parameters in an ordered dictionary and construct the URL from that:
$parameters = [ordered]#{
date = $date
time = $time
name = $env:computername
loginid = $env:username
sn = $serialnumber
ipaddr = $ipaddr
verb = 'profileclear'
}
$baseURI = 'https://host.fqdn/path'
# Construct full URI string from base URI + parameters
$queryString = #($parameters.GetEnumerator() |ForEach-Object {
'{0}={1}' -f $_.Key,$_.Value
}) -join '&'
$URI = '{0}?{1}' -f $baseURI,$queryString
Invoke-WebRequest -Uri $URI

Related

Alter object value upon assignment to PSCustomObject

I'm creating a log of attempted posts to an API. The API key is stored in a simple hash table and passed via Invoke-WebRequest:
$headers = #{ 'x-api-key' = 'ABC123DEF456GHI789' }
Try {
[Net.ServicePointManager]::SecurityProtocol = 'tls12, tls11'
$apiResponse = Invoke-WebRequest -Uri $url -Method $method -Headers $headers -Body $body
$status = $apiResponse.StatusCode
$statusDescription = $apiResponse.StatusDescription
} Catch {
$status = $_.Exception.Response.StatusCode.value__
$statusDescription = $_.Exception.Response.StatusDescription
}
I want to obscure the header key in the log, so I created and modified a new variable.
$obscured = $headers | ConvertTo-Json -depth 100 | ConvertFrom-Json
$obscured.'x-api-key' = $obscured.'x-api-key'.Substring(0,2) + '...' + $obscured.'x-api-key'.Substring($obscured.'x-api-key'.Length-2,2)
$logresults += [PSCustomObject]#{
status = $status
statusDescription = $statusDescription
url = $url
method = $method
header = $obscured
body = ConvertFrom-JSON $body
}
I want to retain the header's structure as a key/value pair in the log. The extra steps prepping a new variable seem wasteful. Does PowerShell have a way to change the header key value upon assignment to the PSCustomObject?
AFAIK, there is no easy way to obscure strings (or objects) in PowerShell or even .Net, see my related purpose: #16921 Add [HiddenString] Class. The only thing that exists is the gone crazy SecureString Class with difficult methodes to convert from a string and reveal string (as that is not secure). Besides, the SecureString might get obsolete (as it appears less secure than intended) and possibly replaced by a shrouded buffer which is even more difficult to use for obscuring information (if even possible in PowerShell).
Anyways, in the HiddenString idea you might do something like this:
$ApiKey = [HiddenString](Read-Host -AsSecureString 'Enter Api key:')
See also: How to encrypt/hide ClearText password in PowerShell Transcript
$Headers = #{
'Accept' = 'application/json'
'X-My-Header' = 'Hello World'
'x-api-key' = $ApiKey
}
$apiResponse = Invoke-WebRequest -Uri $url -Method $method -Headers $($Headers.'x-api-key' = $ApiKey.Reveal(); $Headers) -Body $body
$logresults += [PSCustomObject]#{ # Avoid +=, see: https://stackoverflow.com/a/60708579/1701026
status = $status
statusDescription = $statusDescription
url = $url
method = $method
header = $Headers
body = ConvertFrom-JSON $body
}

Get-content not producing an array that Invoke-restmethod can process

As a follow-up to this question, instead of using a long array in the script I wanted to draw from a text file. So I replaced this:
$URLs = 'http://websiteone.com','http://websitetwo.com','http://websitethree.com'
with this
$URLs = Get-Content ./urlfile.txt
or (functionally the same as far I know) this
$URLs = #(Get-Content ./urlfile.txt)
But I end up with Invoke-RestMethod : The remote server returned an error: (400) Bad Request.
Incorporating the great response form my last question, my foreach loop looks like this:
foreach($URL in $URLs) {
$BODY = #([pscustomobject]#{"client" = #{"clientId" = "company"; "clientVersion" = "1.0"}; "threatInfo" = #{"threatTypes" = "MALWARE","SOCIAL_ENGINEERING","THREAT_TYPE_UNSPECIFIED","UNWANTED_SOFTWARE","POTENTIALLY_HARMFUL_APPLICATION"; "platformTypes" = "ANY_PLATFORM"; "threatEntryTypes" = "URL","EXECUTABLE","THREAT_ENTRY_TYPE_UNSPECIFIED"; "threatEntries" = #{"url" = $URL}}})
$JSONBODY = $BODY | ConvertTo-Json
$Result = Invoke-RestMethod -Method 'POST' -Uri $Uri -Body $JSONBODY -Headers $HEADERS
if ( ([string]::IsNullOrEmpty($Result)) ) {} else {write-host $URL "ALERT: Safe browsing match!"}
}
... but this doesn't work if I create the array with the Get-Content cmdlet. If I run the script either way, then type $URLs, I get the exact same data returned. What am I doing wrong with get-content?
The Invoke-RestMethod cmdlet is there to make one Rest request at a time and can't take an array.
You will need to add a forEach loop to step through your $urls one at a time, something like this:
foreach($url in $urls){
$result = Invoke-RestMethod -Uri $url
#do something with $result
}
So to integrate into your sample from the previous question, you should have a urls.txt file which looks like this:
http://google.com
http://small.com
https://fast.com/
And then your code would look like this:
$URLs = get-content .\urls.txt
$HEADERS = #{ 'Content-Type' = "application/json" }
$GOOGLE_API_KEY='[API Key]'
$Uri = 'https://safebrowsing.googleapis.com/v4/threatMatches:find?key='+ $GOOGLE_API_KEY
foreach($URL in $URLs) {
$BODY = #([pscustomobject]#{"client" = #{"clientId" = "company"; "clientVersion" = "1.0"}; "threatInfo" = #{"threatTypes" = "MALWARE","SOCIAL_ENGINEERING","THREAT_TYPE_UNSPECIFIED","UNWANTED_SOFTWARE","POTENTIALLY_HARMFUL_APPLICATION"; "platformTypes" = "ANY_PLATFORM"; "threatEntryTypes" = "URL"; "threatEntries" = #{"url" = $URL}}})
$JSONBODY = $BODY | ConvertTo-Json
$result = Invoke-RestMethod -Method 'POST' -Uri $Uri -Body $JSONBODY -Headers $HEADERS
[pscustomObject]#{SiteName=$url;ThreatInfo=$result.Matches}
}
This would load up the list of $urls from your text file, then run a Rest Request on each, storing the result in $result. Finally, it will make a new PowerShell Object with the site name and show you if there are any matches from the Google SafeBrowsing API.
You'll need to run the command interactively and see which properties from $result are meaningful to you, but you can see all of the expected properties in the Google API Docs.
Edit
Found the bug. It turns out when we use Get-Content the object returned back retains some of the document formatting information from the original file! We can see this by inspecting $JSONBODY. We also see that the conversion to Json from [PSCustomObject is leaving a lot of cruft behind too.
To fix this, we should cast $URL into a string using the ToString() method and also ditch casting to [psCustomObject] too as shown below.
$BODY = #{
"client" = #{
"clientId" = "company"; "clientVersion" = "1.0"
};
"threatInfo" = #{
"threatTypes" = "MALWARE",
"SOCIAL_ENGINEERING",
"THREAT_TYPE_UNSPECIFIED",
"UNWANTED_SOFTWARE",
"POTENTIALLY_HARMFUL_APPLICATION"; "platformTypes" = "ANY_PLATFORM"; "threatEntryTypes" = "URL"; "threatEntries" = #{
"url" = $URL.ToString()
}
}
}
$JSONBODY = $BODY | ConvertTo-Json

FreshAgent Get Asset Data

I am trying to fetch asset data from the Fresh client with Powershell. I am able to get any asset by typing it's name but I want to save some of the variables it returns so I can use it further.
$naam = Read-Host "Voer product naam in"
# Set global variables
$APIKey = 'Myapikey'
$EncodedCredentials = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $APIKey,$null)))
$HTTPHeaders = #{ "X-ApiKey" = $APIKey}
$HTTPHeaders.Add('Authorization', ("Basic {0}" -f $EncodedCredentials))
$HTTPHeaders.Add('Content-Type', 'application/json')
$URL = 'https://helpdesk.company.nl/cmdb/items/list.json?field=name&q='+$naam
(Invoke-WebRequest -Method Get -Uri $URL -Headers $HTTPHeaders ).content
The following are some of the values that return after I run the above
{"config_items":[{"id":25000477949,"name":"SYS-MB1334","description":null,"ci_type_id":25000015988,"impact":1,"created_at":"2020-03-12T10:14:40+01:00","updated_at":"2020-04-24T16:42:42+02:00"
I would like to save the name and id variable for example
Unfortunately, the JSON you show is invalid.
Suppose the json returned from
$output = (Invoke-WebRequest -Method Get -Uri $URL -Headers $HTTPHeaders ).Content
looks like:
{"config_items":
[{"id":25000477949,"name":"SYS-MB1334","description":null,"ci_type_id":25000015988,"impact":1,"created_at":"2020-03-12T10:14:40+01:00","updated_at":"2020-04-24T16:42:42+02:00"},
{"id":12345678901,"name":"SYS-MB9876","description":null,"ci_type_id":12358745896,"impact":1,"created_at":"2020-03-12T10:14:40+01:00","updated_at":"2020-04-24T16:42:42+02:00"}]
}
Then you can collect the properties you need from the config_items using:
$result = ($output | ConvertFrom-Json).config_items |
Select-Object #{Name = 'Id'; Expression = {$_.id}},
#{Name = 'Name'; Expression = {$_.name}}
# output on screen
$result
# output to CSV file
$result | Export-Csv -Path 'X:\TheOutput.csv' -NoTypeInformation
Output on screen would look like
Id Name
-- ----
25000477949 SYS-MB1334
12345678901 SYS-MB9876
Hope that helps

How to replace Invoke-RestMethod into PowerShell 2.0

I have created a script in PowerShell 5.1 that retrieves mail messages not older that one day with 'report' as a subject and save their attachments into local drive. The problem is that in the production environment I have only Powershell 2.0. I am using Invoke-RestMethod in my code like this:
$url = "https://outlook.office365.com/api/v1.0/me/messages"
$date = (get-date).AddDays(-1).ToString("yyyy-MM-dd")
$subject = "'report'"
$messageQuery = "" + $url + "?`$select=Id&`$filter=HasAttachments eq true and DateTimeReceived ge " + $date + " and Subject eq " + $subject
$messages = Invoke-RestMethod $messageQuery -Credential $cred
foreach ($message in $messages.value)
{
$query = $url + "/" + $message.Id + "/attachments"
$attachments = Invoke-RestMethod $query -Credential $cred
foreach ($attachment in $attachments.value)
{
$attachment.Name
# SAVE ATTACHMENT CODE HERE
}
}
Is there a simple way to convert the code in order to be suitable for PowerShell 2.0?
Invoke-WebRequest
This command is basically the same as Invoke-RestMethod except in how it handles the data after it receives it. You are going to have to make some small modifications on how you parse your data.
I'll wager you are receiving JSON data so you would just run your Invoke-WebRequest command and pipe it to ConvertFrom-JSON and assign the results to a var. This will let you then do something like $x.messages | % { $_ }
You will need to implement this converter in 2.0. You can copy and paste from: PowerShell 2.0 ConvertFrom-Json and ConvertTo-Json implementation
XML is supported in PS 2.0 natively.

Make Invoke-WebRequest loop through each URL it finds

I'm new to PowerShell and I'm trying to make the Invoke-WebRequest cmdlet loop through each url the webscrape finds. All I have so far is this :
$site = Invoke-WebRequest -UseBasicParsing -Uri www.example.com/examples
$site.Links | Out-GridView
Any help would be appreciated!
Add your links to a comma separated list.
Split the list and loop each item.
Request each item.
As below:
$option = [System.StringSplitOptions]::RemoveEmptyEntries
$urlCollection = "link1,link2,link3"
$separator = ","
$urlList = $urlCollection.Split($separator, $option)
foreach ($url in $urlList) {
Invoke-WebRequest $url
# Give feedback on how far we are
Write-Host ("Initiated request for {0}" -f $url)
}