Accessing Oracle NetSuite REST API using PowerShell

As part of a recent project I needed to lookup and export data from Oracle NetSuite using PowerShell.

It took me several hours of spinning my wheels on this problem, but I was finally able to connect. I’m writing this post in hopes that I can save someone else a little time and hair pulling.

Credentials Setup
Official documents for Credentials Setup

The first thing you’ll need to do is access NetSuite using an account that has the administrator role or the Integration Application permission assigned.

From the NetSuite application Dashboard you should see a Setup item in the top/main menu:

Oracle NetSuite Setup Main Menu

Click the main Setup link in the menu to view the Setup Manager:

Oracle NetSuite Setup Manager

Click on Company » Enable Features » SuiteCloud

Toward the bottom under SuiteTalk (Web Services) check the REST WEB SERVICES box (SOAP can also be enabled here):

SuiteTalk (Web Services) Enabling Features

Click Save.

While still in Setup Manager click Integration » Manage Integrations:

Integrations

Click New, enter an application NAME, make sure the STATE is enabled.

You can uncheck all of the Token-based Authentication options

Within the OAuth 2.0 section, check CLIENT CREDENTIALS (MACHINE TO MACHINE) GRANT and REST WEB SERVICES under SCOPE

New Integration

Click Save.

After saving you’ll be presented with a CONSUMER KEY / CLIENT ID AND CONSUMER SECRET / CLIENT SECRET:

Client ID and Secret

This information is only presented one time so you’ll need to write it down.

Now we need to create a certificate that will be used to sign our REST requests.

For this I used OpenSSL, but any method for generating a valid x509 certificate should work.

Here is the CMD to generate a local certificate:

openssl req -x509 -newkey rsa:3072 -keyout "{Output Path}\private.pem" -out "{Output Path}\public.pem" -days 365 -nodes

You’ll be prompted to enter the certificate information; you can accept the default values by simply hitting ENTER.

While still in Setup Manager click Integrations » OAuth 2.0 Client Credentials Setup:

OAuth 2.0 Client Credentials Setup

Click Create New.

New Client Credentials Mapping

Select an appropriate user account for the ENTITY. The roles and permissions assigned to this user will then populate the ROLE list.

Select an appropriate ROLE assigned to the user. This will affect what data and operations are available via the REST Web Services.

Select the APPLICATION that was previously setup in Manage Integrations

Choose the public key file (public.pem) that was previously created for the CERTIFICATE

Click Save.

Once the credentials have been saved there will be a CERTIFICATE ID available on the main setup screen. This will be used later in our REST requests.

At this point everything should be setup within NetSuite.

PowerShell  (PowerShell 6 (Core) or better required)

In order to use the REST Web Services you must first acquire an access_token

This is done via a POST request to the API’s token endpoint:

https://<accountID>.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token
Here are the official documents on how to do this from Oracle.

To accomplish this we’ll need to work with JSON Web Tokens (JWTs).

There are some good PowerShell libraries for working with JWT such as Posh-Jwt, however I wanted a deeper understanding of the technology.

In order to create a valid JWT we need to encode some of the configuration information and then appropriately sign the request.

First create and encode the header:

# typ is always JWT
# alg is the Algorithm use to sign the request, others are supported, but RS256 is all that I could get to work
# kid is the certificate ID provided on the OAuth 2.0 Client Credentials Setup screen, we use the private key to sign later
[hashtable]$header = @{
     "typ" = "JWT";
     "alg" = "RS256";
     "kid" = "[Certificate ID from OAuth 2.0 Client Credentials Setup]";
}

# Serialize to JSON, Convert to Base64 string, make Base64 string URL safe
[string]$encodedHeader = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($(ConvertTo-Json $header))) -replace '\+','-' -replace '/','_' -replace '='

Next we need to create and encode the JWT payload:

# iss is the Client ID provided during the Integration Setup, the information is only provided once immediately after setup
# scope is the comma delimited list of possible services:  restlets, rest_webservices, suite_analytics
# aud is always the token endpoint
# exp is the date the JWT expires (60 minutes is the max) in Epoch/Unix numeric time, note this is NOT the expiration of the access_token
# iat is the date the JWT was issued (current date/time) in Epoch/Unix numeric time
[hashtable]$payload = @{
     "iss" = "[Client ID provided during Integration Setup]";
     "scope" = "rest_webservices";
     "aud" = "https://<accountID>.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token";
     "exp" = ([System.DateTimeOffset]$((Get-Date).AddSeconds(3600))).ToUnixTimeSeconds();
     "iat" = ([System.DateTimeOffset]$(Get-Date)).ToUnixTimeSeconds()
}

# Serialize to JSON, Convert to Base64 string, make Base64 string URL safe
[string]$encodedPayload = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($(ConvertTo-Json $payload))) -replace '\+','-' -replace '/','_' -replace '='

Now we need to sign the JWT:

# Combine header and payload
[string]$baseSignature = "$encodedHeader.$encodedPayload"
[byte[]]$byteSignature = [System.Text.Encoding]::UTF8.GetBytes($baseSignature)

# Load certificate
[System.Security.Cryptography.X509Certificates.X509Certificate2]$signingCertificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromPemFile("<File path to public.pem>","<File path to private.pem>")

# Sign using private key
[byte[]]$byteSignedSignature = $signingCertificate.PrivateKey.SignData($byteSignature,[System.Security.Cryptography.HashAlgorithmName]::SHA256,[System.Security.Cryptography.RSASignaturePadding]::Pkcs1)

# Convert to Base64 string and make Base64 string URL safe
[string]$signedSignature = [Convert]::ToBase64String($byteSignedSignature) -replace '\+','-' -replace '/','_' -replace '='

With the JWT token properly configured and signed we’re ready to request an access_token from the API’s token endpoint and then use it to access the API’s customer endpoint.

# grant_type is always client_credentials
[string]$grant_type = "client_credentials"

# client_assertion_type is always urn:ietf:params:oauth:client-assertion-type:jwt-bearer, needs to be URL encoded
[string]$client_assertion_type = [System.Web.HttpUtility]::UrlEncode("urn:ietf:params:oauth:client-assertion-type:jwt-bearer")

# client_assertion is a combination of the $baseSignature and $signedSignature
[string]$client_assertion = "$baseSignature.$signedSignature"

# send access_token request
$response = Invoke-WebRequest `
                 -Uri "https://<accountID>.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token" `
                 -Method "POST" `
                 -Body "grant_type=$grant_type&client_assertion_type=$client_assertion_type&client_assertion=$client_assertion" `
                 -Headers @{"Content-Type"="application/x-www-form-urlencoded";} `
                 -UseBasicParsing

# should get a JSON response body
if ($null -ne $response `
    -and (Test-Json $response.Content)) {
     [hashtable]$token = ConvertFrom-Json $response.Content -AsHashtable
     if ($token.ContainsKey("access_token") `
         -and $token["access_token"].Length -gt 0) {

         # now we can use the access_token in subsequent requests via the Authorization header
         $response = Invoke-WebRequest `
                         -Uri "https://<accountID>.suitetalk.api.netsuite.com/services/rest/record/v1/customer/<id>" `
                         -Method "GET" `
                         -Headers @{"Authorization"="Bearer $($token["access_token"])";} `
                         -UseBasicParsing

         # Convert Byte[] to JSON string, then convert JSON to object
         Write-Output (ConvertFrom-Json ([System.Text.Encoding]::UTF8.GetString($response.Content)))
     }
}

Click here for full script.

Official REST API Developer Guide:  https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/book_1559132836.html#SuiteTalk-REST-Web-Services-API-Guide

In Windows (not sure of *nix OS) the GPG command-line has an obscure switch necessary in order to use the ‐‐passphrase argument: ‐‐pinentry-mode (https://gnupg.org/documentation/manuals/gpgme/Pinentry-Mode.html)

Example:

gpg ‐‐pinentry‐mode loopback ‐‐batch ‐‐yes ‐‐passphrase "<PassPhrase>" "<File>"

Referencing a PSObject property with special characters is pretty easy:

$var = $psobject."Property With Space"

In order to reference properties that include special characters dynamically:

$propertyname = "Property With Space"
$var = $psobject.($propertyname)
Update 11/12/23 — Microsoft GraphAPI is now the preferred method for sending email using O365. Refer to this blog entry

This post is really more about the potential problems one might encounter using Send-MailMessage cmdlet in PowerShell to connect and send email via Office 365.

First a quick example:

[SecureString]$o365Password = ConvertTo-SecureString "Your Office 365 Account Password" -AsPlainText -Force
[PSCredential]$o365Credentials = New-Object System.Management.Automation.PSCredential("Your Office 365 Account Username",$o365Password)
Send-MailMessage `
  -Subject "Your Subject" `
  -Body "Your email message" `
  -To "toSomeone@somewhere.com" `
  -From "fromSomeone@somewhereelse.com" `
  -SmtpServer "outlook.office365.com" `
  -Port 587 `
  -Credential $o365Credentials `
  -UseSsl

If the above settings are set correctly the email should be sent.

Some errors you may encounter:

Send-MailMessage : The SMTP server requires a secure connection or the client was not authenticated. The server response was: 5.7.57 SMTP; Client was not authenticated to send 
anonymous mail during MAIL FROM [XXXXXXXXXXXXXXX.XXXXXXXX.prod.outlook.com]
Send-MailMessage : Unable to connect to the remote server

While trying to automate the creation of new IIS websites using PowerShell I needed a script to create IIS virtual directories with a specific login (i.e. Connect As)

After several hours of searching and trying various solutions I finally arrived at this:

$sitename = "My Website Name"
$virtualdirectory = "virtual1"
$virtualdirectorypath = "C:\My Virtual Path"
$username = "username1"
$password = "password1"

New-WebVirtualDirectory -Site $sitename -Name $virtualdirectory -PhysicalPath $virtualdirectorypath 

Set-WebConfigurationProperty "system.applicationHost/sites/site[@name='$sitename']/application[@path='/']/virtualDirectory[@path='$virtualdirectory']" -name userName -value $username

Set-WebConfigurationProperty "system.applicationHost/sites/site[@name='$sitename']/application[@path='/']/virtualDirectory[@path='$virtualdirectory']" -name password -value $password

I tried several path alternatives, however the XPath queries listed in the snippet are the only iteration that worked.

Hope this saves someone else a little time.

about me

An information technology professional with twenty five years experience in systems administration, computer programming, requirements gathering, customer service, and technical support.