Accidentally URL Encoding URI Twice, Invalid Signature Response - powershell

I'll try to explain this best I can. I'm trying to perform a simple GET against the NetSuite "employees" API (using PowerShell). As you can see in the $query below, this variable needs to be URL encoded (spaces in the query) which I am doing on line 20 of the below snippet. I'm then taking that encoded URL along with a couple other variables and building the $base_string. I use the $base_string to create the Base64 OAuth signature and, on line 36, URL encode the signature. My response from NetSuite is always Invalid Signature.
When I perform any sort of "standard" query (like the one immediately below, without spaces... meaning no changes to the URL after encoding) I do not get an Invalid Signature response. This leads me to believe the problem is entirely related to the more unique query I am attempting and, possibly, the fact that it is being "double-encoded."
I'd appreciate any feedback as I would really benefit from being able to perform a query against the "custentity" variable in the below snippet.
$query = "/services/rest/record/v1/employee/$($netsuite_id)" # This query will find a user via their NetSuite ID.
$url = "https://$($realm.ToLower().Replace("_","-")).suitetalk.api.netsuite.com"
$query = "/services/rest/record/v1/employee?q=custentity_coupa_emp_id IS $($employee_id)" # This query will find a user via a custom entity --- their Coupa ID.
$oauth_nonce = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes([System.DateTime]::Now.Ticks.ToString()))
$oauth_timestamp = [int64](([datetime]::UtcNow)-(Get-Date "1970-01-01")).TotalSeconds
# BUILD THE BASE STRING VARIABLE
$oAuthParamsForSigning = #{}
$oAuthParamsForSigning.Add("oauth_consumer_key",$oauth_consumer_key)
$oAuthParamsForSigning.Add("oauth_token",$oauth_token)
$oAuthParamsForSigning.Add("oauth_signature_method",$oauth_signature_method)
$oAuthParamsForSigning.Add("oauth_nonce",$oauth_nonce)
$oAuthParamsForSigning.Add("oauth_timestamp",$oauth_timestamp)
$oAuthParamsForSigning.Add("oauth_version",$oauth_version)
$oAuthParamsString = ($oAuthParamsForSigning.Keys | Sort-Object | % {
"$_=$($oAuthParamsForSigning[$_])"
}) -join "&"
$encodedOAuthParamsString = [uri]::EscapeDataString($oAuthParamsString)
# BUILD THE ENCODED FULL URL VARIABLE
$encodedUrl = [uri]::EscapeDataString($url+$query)
# BUILD THE OAUTH SIGNATURE VARIABLE: KEY (CONSUMER SECRET + TOKEN SECRET) + BASE STRING
$base_string = $HTTP_method + "&" + $encodedUrl + "&" + $encodedOAuthParamsString
$key = $oauth_consumer_secret + "&" + $oauth_token_secret
$hmacsha256 = New-Object System.Security.Cryptography.HMACSHA256
$hmacsha256.Key = [System.Text.Encoding]::ASCII.GetBytes($key)
$oauth_signature = [System.Convert]::ToBase64String($hmacsha256.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($base_string)))
# BUILD THE HEADERS VARIABLE
$authHeaderString = ($oAuthParamsForSigning.Keys | Sort-Object | % {
"$_=`"$([uri]::EscapeDataString($oAuthParamsForSigning[$_]))`""
}) -join ","
$authHeaderString += ",realm=`"$([uri]::EscapeDataString($realm))`""
$authHeaderString += ",oauth_signature=`"$([uri]::EscapeDataString($oauth_signature))`""
$authHeaders = #{
"Content-Type"="application/json"
;"Prefer"="transient"
;"Authorization"="OAuth $authHeaderString"
;"Accept"="*/*"
;"Cache-Control"="no-cache"
;"Host"="3489459-sb1.suitetalk.api.netsuite.com"
;"Accept-Encoding"="gzip, deflate, br"
;"Cookie"="NS_ROUTING_VERSION=LAGGING"
}

I have no background with PowerShell but I've had this issue previously with Python and it's tricky without using libraries. I found out that:
Your Base URL cannot contain query params. Meaning your base url should be:
"https://$($realm.ToLower().Replace("_","-")).suitetalk.api.netsuite.com/services/rest/record/v1/employee"
Since you're only doing GET request, just throw your query into a payload (JSON formatted). At least with my SuiteQL this is how I did it. Moving the query to payload worked for me.
For POST Request, the parameters will need to be included in the Base String Creation, sorted alphabetically or by value if keys are the same name.
My query for example for SuiteQL was query = json.dumps({ q: SELECT FROM WHERE })

Related

ExtendedPropertyDefinition declaration for EmailMessage in Powershell throws exception

since I got to know that ExtendedProperties have its limit for a specific mailbox in the EWS cloud I am trying to switch up my code to have only one ExtendedProperty and just change its value each time I am assigning the property to an e-mail message I am sending to then find it and work on the e-mail message object later on in the program.
I am having a hard time setting this up correctly even though I am following the docs, but it just seems to not work out for me.
This is the code part that throws an Exception: "Multiple ambigious overloads found for "ExtendedPropertyDefinition" and the argument count "3" :
# email declaration exposing the $email object
.
.
.
# property declaration and setting the value
# since I want to have only one extended property, this is actually a valid GUID string that I then # convert to a Guid type
$GUIDproperty = "00000000-0000-0000-0000-000000000000"
$propertyGUID = [Guid]$GUIDproperty
# since I want to have a unique value each time set to the existing extended property
$propertyValue = [guid]::NewGuid().ToString()
$propertyName = "Id"
$ExtendedProperty = [Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition]::new($propertyGUID, $propertyName, $propertyType)
# well I dont even reach this part, but just for the big picture
$email.SetExtendedProperty($ExtendedProperty, $propertyValue)
The docs I have followed for that are the following:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.exchange.webservices.data.extendedpropertydefinition.-ctor?view=exchange-ews-api#microsoft-exchange-webservices-data-extendedpropertydefinition-ctor(microsoft-exchange-webservices-data-defaultextendedpropertyset-system-string-microsoft-exchange-webservices-data-mapipropertytype)
https://learn.microsoft.com/en-us/dotnet/api/microsoft.exchange.webservices.data.folder.setextendedproperty?redirectedfrom=MSDN&view=exchange-ews-api#Microsoft_Exchange_WebServices_Data_Folder_SetExtendedProperty_Microsoft_Exchange_WebServices_Data_ExtendedPropertyDefinition_System_Object_
https://learn.microsoft.com/en-us/dotnet/api/system.guid?view=net-7.0
The following works okay for me
$propertyType = [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String
$GUIDproperty = "82e3d64f-e26d-4321-8fc3-c31aa790197c"
$propertyGUID = [Guid]$GUIDproperty
$propertyValue = [guid]::NewGuid().ToString()
$propertyName = "MyPropId"
$ExtendedProperty = [Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition]::new($propertyGUID, $propertyName, $propertyType)
return $ExtendedProperty
You don't specify what you using in the $propertyType so that maybe it, could also be to do with the versions you using. What version of PowerShell and the EWS Managed API are you trying ?

How to bind an array to an HTML tag in powershell?

Below is a snip of my powershell code where my response or my variable($witrevisions) is of type array. I am looking to bind this in a html tag which i have defined in the power shell. As I am very new to coding stuff , I am looking the ways how can I bind array to html tag in best possible way
...continuing my line of code
$response4s = (Invoke-RestMethod -Uri $uriAccount -Method get -Headers $AzureDevOpsAuthenicationHeader).values
$wits = $response4s | where({$_.fields.'System.WorkItemType' -eq 'Task'}) # Only retrieve Tasks
$witrevisions = #()
foreach($wit in $response4s){
$customObject = new-object PSObject -property #{
"Title" = $wit.fields.'System.Title'
"AssignedTo" = $wit.fields.'System.AssignedTo'
}
$witrevisions += $customObject
}
$witrevisions | Select-Object `
Title,
AssignedTo
}
and this the sample response i am getting in $witrevisions which i have exported in text file. its a table with two column one having emails and other having a title name.i have tried to show by giving it a table view for better understanding
Assigned To Title
xyz#outlook.com testingpro
drdr#outlook.com resttesting
and here is the html tag where I trying to bind the $witrevisions.
$DOWNLOAD_PAGE_BODY_CONTENT = "<!DOCTYPE html>
`n<html>
`n<head>
`n <title>Validation</title>
`n</head>
`n<body>
`n
`n<p>Please click the link to download the release.</p>
`n<p></p>
`n<p></p>
`n<p>$witrevisions</p>
`n</body>
`n</html>
`n"
Can someone please tell me how should I do this??
Here is an example of some code that would take your array and emit a table, with an explanation to help you tweak to your specific needs:
"<table><body>$($witrevisions|% {"<tr><td>$($_.Title)</td><td>$($_.AssignedTo)</td></tr>"} )</body></table>"
The double quotes are important because they allow string interpolation (it will replace variables with this value, versus being read a plain text. E.g. '[' + $test + ']' => "[$test]"
If you need to do more complex logic in string interpolation, you can use $(...), the ellipses being regular code.
You can iterate through an array by piping to the ForEach-Object, or it's alias %. All the code in the braces will be executed for each item in the array. The current items is represented by $_.
We're then back to string interpolation and using $(...), which is needed to access the members of the current item.
Note: There are several other ways to accomplish (functionally) the same thing. E.g. foreach(...){} vs |%{...}, so feel free to use a different technique if you are more comfortable with doing something a different way.

How do I read values of a variable of type Map passed from terraform to powershell userdata script?

I need to pass the variable of type map from terraform to powershell userdata script and be able to access the key value pairs of the map in the powershell script. Thank you
userdata.tf
data "template_file" "user_data" {
template = "${file("${path.module}/init.ps1")}"
vars = {
environment = var.env
# I want to pass the values as shown below
hostnames = {"dev":"devhost","test":"testhost","prod":"prodhost"}
}
}
init.ps1
$hostnames = "${hostnames}"
$environment = "${environment}"
if ($environment -eq "dev"){
# print the value of the dev key in the hostname map here
}
The template_file data source is discouraged.
Note In Terraform 0.12 and later, the templatefile function offers a built-in mechanism for rendering a template from a file. Use that function instead, unless you are using Terraform 0.11 or earlier.
The templatefile function is preferred which is why my solution uses it instead.
In either case, only map(string) is supported for template vars. The values must be strings. JSON can encode arbitrary tree structures, including your map of hostnames as strings.
In your terraform code, encode your hostnames to JSON with jsonencode.
userdata.tf:
locals {
user_data = templatefile("${path.module}/init.ps1" ,{
environment = var.env
# I want to pass the values as shown below
hostnames = jsonencode({"dev":"devhost","test":"testhost","prod":"prodhost"})
})
}
In your PowerShell, decode your hostnames from JSON with the ConvertFrom-Json cmdlet.
init.ps1:
$hostnames = '${hostnames}' | ConvertFrom-Json
$environment = "${environment}"
if ($environment -eq "dev"){
# print the value of the dev key in the hostname map here
}
Update: As noted in the comments, -AsHashtable won't necessarily work as it was added in PowerShell 6.0. Windows 10 and Windows Server 2016 include PowerShell 5.1. If you have maps with case-only differences in keys ({"name" = "foo" ; "Name" = "bar"}) then you will need to install PowerShell 6.0 or later and use ConvertFrom-Json -AsHashtable.
In order to include a collection value in a template result you must decide how you want to represent it as a string, because template results are always strings.
PowerShell supports JSON encoding via the ConvertFrom-Json cmdlet, so a JSON string might be a good candidate, although it presents some challenges because you must ensure that the JSON string is written into the result as a valid PowerShell expression, which means we must also apply PowerShell escaping.
Putting that all together, you can adjust the template like this:
$hostnames = '${replace(jsonencode(hostnames), "'", "''")}' | ConvertFrom-Json
$environment = '${replace(environment, "'", "''")}'
if ($environment -eq "dev"){
Write-Output $hostnames["dev"]
}
The jsonencode function produces a JSON-encoded version of the given value. The above then passes that result to replace so that any ' characters in the result will be escaped as '', which then allows placing the entire result in single quotes ' to ensure valid PowerShell syntax.
The result of rendering the template would be something like this:
$hostnames = '{"dev":"devhost","test":"testhost","prod":"prodhost"}' | ConvertFrom-Json -AsHashtable
$environment = 'dev'
if ($environment -eq "dev"){
Write-Output $hostnames["dev"]
}
You seem to be using Terraform 0.12, so you should use the templatefile function instead of the template_file data source. The function is better because it can accept values of any type, whereas the data source can only accept string values (because it is designed for Terraform 0.11).
To use templatefile, find the place where you were previously referring to data.template_file.user_data and use the templatefile function there instead:
templatefile("${path.module}/init.ps1", {
environment = var.env
hostnames = {"dev":"devhost","test":"testhost","prod":"prodhost"}
})
You can then remove the data "template_file" "user_data" block, because this templatefile function call replaces it.

Powershell encode JSON array in URL for RestMethod

I am using Knack to solve a business process issue, however to ensure it's success I need to run a daily script to sync the data between Knack and our HR system.
Knack uses a REST API and I want to apply a filter to the GET call so that I don't have to populate a local table in order to compare the data.
Knack requires a JSON array like the example below to be encoded in the URL in order to filter the results returned. The example shows how to do this in Javascript but I cannot work out how to do this in Powershell would anyone be able to assist?
// Request route
var api_url = 'https://api.knack.com/v1/objects/object_1/records';
// Prepare filters
var filters = {
'match': 'or',
'rules': [
{
'field':'field_1',
'operator':'is',
'value':'Dodgit'
},
{
'field':'field_1',
'operator':'is blank'
}
]
};
// Add filters to route
api_url += '?filters=' + encodeURIComponent(JSON.stringify(filters));
You can do the following:
$api_url = 'https://api.knack.com/v1/objects/object_1/records'
$filters = #'
{
'match': 'or',
'rules': [
{
'field':'field_1',
'operator':'is',
'value':'Dodgit'
},
{
'field':'field_1',
'operator':'is blank'
}
]
}
'#
$CompressedFilters = $filters | ConvertFrom-Json | ConvertTo-Json -Depth 10 -Compress
$encodedUri = "{0}?filters={1}" -f $api_url,[System.Web.HttpUtility]::UrlEncode($CompressedFilters)
Explanation:
$filters is defined as a here-string. $filters is piped into ConvertFrom-Json and then to ConvertTo-Json in order to compress the JSON data easily. However, that does swap single quotes for double quotes.
UrlEncode() from the System.Web.HttpUtility .NET class encodes special characters in URLs.
-f is the string format operator. This helps to build your URI string. The final URI is stored in $encodedUri.
NOTE: The [System.Web.HttpUtility]::UrlEncode encodes special characters with lowercase hex characters. If your system requires uppercase hex characters, then use [System.Net.WebUtility]::UrlEncode instead.

Filtering Azure Backup Jobs using ODATA in the REST API

I'm trying to use the Azure REST API to receive the Recovery Services backup jobs from the last 24 hours. The API appears to default to jobs that match the current UTC date.
As far as I can tell, Azure is using ODATA v4, so I would expect the query $filter=startTime gt (now() sub duration P1D) to work. However, when I execute my script, it appears to ignore the filter and execute its default (current UTC date only).
The documentation for the API method has examples using explicit dates that appear to be formatted as Edm.DateTime, even though the startTime property appears to return in Edm.DateTimeOffset format. But I'm not finding any documentation for calculating the date within the $filter query.
A copy of my script is below. Has anyone been able to do something like this against the Azure REST API?
$ListRSVaultsParams = #{
Uri = 'https://management.azure.com/subscriptions/' + $SubscriptionID + '/providers/Microsoft.RecoveryServices/vaults?api-version=2018-01-10'
Headers = #{
Authorization = $AuthHeader
}
}
$Vaults = (Invoke-RestMethod #ListRSVaultsParams).value
$Jobs = ForEach ($VaultId in $Vaults.id)
{
$Results = #()
$JobUri = 'https://' + $BaseHost + $VaultId + '/backupJobs?api-version=2018-01-10&$filter=startTime gt (now() sub duration P1D)'
do
{
$JobParams = #{
Uri = $JobUri
Headers = #{
Authorization = $AuthHeader
}
}
$JobResults = Invoke-RestMethod #JobParams
$Results += $JobResults.value
$JobUri = $JobResults.nextLink
} while ($JobResults.psobject.name -contains 'nextLink')
$Results
}
we don't support all values supported by OData format yet. Start Time and End Time represent the start/end of the range query. Can you please use something like,
$filter=startTime eq 'string representation of DateTime' instead of greater than?