Alter object value upon assignment to PSCustomObject - powershell

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
}

Related

LastPass Enterprise API New User Request

I am trying to use the LastPass Enterprise API to automate the creation of new users in our system using Powershell. I cannot seem to get the call to the API correct in my code. I am almost certain it has something to do with the "data" object. This is the batch object I am passing through the body.
$lastPassObject = #{
cid = "G1TUROWN";
provhash = "N0TM!NE";
cmd = "batchadd";
data = #(
{
username = $email;
fullname = $firstName + " " + $lastName;
password = "Toys4Trucks22!";
password_reset_required = "true";
}
)
}
Here is my API call
Invoke-RestMethod -Uri "https://lastpass.com/enterpriseapi.php" -Method Post -Body $lastPassObject -ContentType "application/json"
Followed by the Error I am receiving
Reference to the API: https://support.lastpass.com/help/add-new-users-via-lastpass-api
You need to convert your body to json before sending it.
Also, you put your Data section in a scriptblock within an array.
This need to be a hashtable, not a scriptblock.
$lastPassObject = #{
cid = "G1TUROWN";
provhash = "N0TM!NE";
cmd = "batchadd";
data = #(
# This need to be an hashtable. You were missing the #
#{
username = $email;
fullname = $firstName + " " + $lastName;
password = "Toys4Trucks22!";
password_reset_required = $true;
}
)
}
$Body = $lastPassObject | ConvertTo-Json
Invoke-RestMethod -Uri "https://lastpass.com/enterpriseapi.php" -Method Post -Body $Body -ContentType "application/json"
If you still have issues after that, make sure to check what the actual json look like (after ConvertTo-Json) so you know exactly what you are sending and can spot more easily discrepancies. For instance, when I first did it, I immediately saw that the data section was all wrong, formatting wise and spotted the missing # because of that.
Also, still by looking at the converted json and the example from their doc, you can see that password_reset_required is a boolean. I changed your "true" to $true so that the correlating json would be what was expected.

Jira Rest Api in Powershell

Could you help me?
I am trying to create an issue in Jira using the Powershell Invoke-WebRequest cmdlet. And I am getting 400 Bad Request error.
I was able to send a successful request using Postman, so I made sure the body syntax is correct and I have sufficient rights.
My code:
$body = #{
"fields" = #{
"project"=
#{
"key"= "ProjectKey"
}
"summary"= "Test"
"description"= "Test"
"issuetype" =#{
"id"= "10705"
}
"priority"= #{
"id"= "18"
}
"reporter"= #{"name"= "MyName"}
}
}
$Headers = #{
Authorization = "Basic QWxla0Zblablablablablablabla" #I took it from Postman
}
$restapiuri = "https://jira.domain.com/rest/api/2/issue"
Invoke-RestMethod -Uri $restapiuri -ContentType "application/json" -Body $body -Method POST -Headers $Headers
for example, I can successfully execute
Invoke-RestMethod "https://jira.domain.com/rest/api/2/issue/createmeta" -Headers $Headers
I've already spent a lot of time-solving this problem, but still can't create an issue.
Any help, please 🙏🙏🙏
For basic authentication with Jira SERVER, the credentials need to be supplied within the header in Base64 encoding, which you need to do before supplying it via the Powershell Invoke-WebRequest method. Here's how to do it:
$username = "The username here"
$password = "The password or token here"
# Convert the username + password into a Base64 encoded hash for basic authentication
$pair = "${username}:${password}"
$bytes = [System.Text.Encoding]::ASCII.GetBytes($pair)
$base64 = [System.Convert]::ToBase64String($bytes)
$headers = #{ Authorization = "Basic $base64" }
Next, in PowerShell, if you build the body of the request as a table, like you've shown, don't don't need to wrap the table elements with inverted commas, just leave them as is, but you do need to convert the table to JSON format before submitting it, like this:
$body = #{
fields = #{
project = #{
key = "ProjectKey"
}
issuetype = #{
id = "10705" # Or refer to the Issue type by its name eg. name = "Story"
}
summary = "Test"
}
}
# Convert the body to JSON format
$body = $body | ConvertTo-Json -Depth 50
I'm assuming your $Uri string contains an actual URL to a Jira Server, not the example 'jira.domain.com'
Start with a simple request in the body, like the one I've shown, that contains only the minimum required to create the Issue, which will check your basic code is working before making the request more complex.

Unable to ingest JSON data into Azure Event Hub

I wrote the below Powershell script to get the JSON data from an API endpoint (https://data.melbourne.vic.gov.au/resource/vh2v-4nfs) and then write this data in JSON format to Azure Event hub. I am able to successfully get the data from the endpoint however the data is not getting ingested into Azure Event Hub.
Can anyone please let me know what's wrong with the below code:
$url = "https://data.melbourne.vic.gov.au/resource/vh2v-4nfs"
$apptoken = "k7lQcUCVFoROv7rQh9fSSXMkZ"
# Set header to accept JSON
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Accept","application/json")
$headers.Add("X-App-Token",$apptoken)
$results = Invoke-RestMethod -Uri $url -Method get -Headers $headers
$results
$method = "POST"
$URI = "https://YOURNS.servicebus.windows.net/eh-streetparking/messages"
$signature = "SharedAccessSignature sr=YOURNS.servicebus.windows.net%2feh-streetparking&sig=K6bfL1VjW9FUcL0B5xaI%3d&se=16722&skn=eh-sap-streetparking"
#$authInfo = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$signature"))
# API headers
$headers = #{
"Authorization"=$signature;
# "Content-Type"="application/json;type=entry;charset=utf-8";
"Content-Type"="application/json";
}
# execute the Azure REST API
foreach ( $result in $results)
{
Invoke-RestMethod -Uri $URI -Method $method -Headers $headers -Body $result
}
The value you have presented as the return result from your Invoke-RestMethod is actually a deserialized PowerShell object, not JSON. It appears to be having its quotes removed at some point too.
PSObject ($results) looks like this: $x = #{account_id="12345"; username="12345"; is_locked="False"; employee_id="12345"; first_name="John"; middle_initial="Roger"; last_name="Doe"; full_name="John Roger Doe"}
You can do this to access individual values:
$x.full_name
Finally, follow this syntax to send POST request:
$Cred = Get-Credential
$Url = "https://server.contoso.com:8089/services/search/jobs/export"
$Body = #{
search = "search index=_internal | reverse | table index,host,source,sourcetype,_raw"
output_mode = "csv"
earliest_time = "-2d#d"
latest_time = "-1d#d"
}
Invoke-RestMethod -Method 'Post' -Uri $url -Credential $Cred -Body $body -OutFile output.csv

How to escape non-ascii symbols in PowerShell 5.1?

I'm trying to send a file content to server:
$uri = ...
$headers = #{
...
"Content-Type" = "application/json"
}
[string] $content = Get-Content .\filename -Encoding utf8 -Raw
$body = #{
...
"content" = $content
} | ConvertTo-Json
$response = Invoke-WebRequest $uri -Method 'PUT' -Headers $headers -Body $body
But all of non-ascii symbols are changed to another similar symbols or question marks
How can I escape them?
I've read documentation and I know about parameter -EscapeHandling of cmdlet ConvertTo-Json, but it's available from PowerShell 6.2, I have only 5.1
As a result, I wrote a simple function:
function EscapeNonAscii([string] $s)
{
$sb = New-Object System.Text.StringBuilder;
for ([int] $i = 0; $i -lt $s.Length; $i++)
{
$c = $s[$i];
if ($c -gt 127)
{
$sb = $sb.Append("\u").Append(([int] $c).ToString("X").PadLeft(4, "0"));
}
else
{
$sb = $sb.Append($c);
}
}
return $sb.ToString();
}
And used it like this:
$updateFileResponse = Invoke-WebRequest $updateFileUri -Method 'PUT' -Headers $headers -Body (EscapeNonAscii $body)
It helped. For everybody who will google it in the future, it's a request to GitLab API Update existing file in repository
PS: I use PS as C# because I know it badly. If somebody knows how to rewrite this fragment better please let me know.
PPS: And also I know that StringBuilder.Append changes an existing object, but I add here assigning ($sb = $sb.Append($c) instead of simple $sb.Append($c)) because it prints every action to console. If you know how to fix it please let me know.

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