Deploying SDF from PowerShell without the browser login
The SuiteCloud CLI’s browser login expires and needs a person at the keyboard. Certificate-based machine-to-machine auth doesn’t. Here’s the full setup on Windows — including the error almost everyone hits on the first try.
The usual way to connect the SuiteCloud CLI to an account is account:setup: a browser window opens, you sign in, you click Allow. It works until the token expires, and then the next deploy stops and waits for someone to sign in again. That’s fine at a desk and useless for a scheduled job, a script, or an automated pipeline.
The alternative is account:setup:ci, which uses OAuth 2.0 client credentials with a certificate. No browser, no expiring session. The setup takes about fifteen minutes. These steps are for Windows and PowerShell; the CLI parts are the same anywhere.
1. Generate the key pair
Git for Windows ships OpenSSL, so you probably have it already. Use an RSA key; elliptic-curve keys are a common cause of private-key errors at the next step.
$ossl = "C:\Program Files\Git\mingw64\bin\openssl.exe"
$dir = "$env:USERPROFILE\.suitecloud-m2m"
New-Item -ItemType Directory -Force $dir | Out-Null
& $ossl req -new -x509 -newkey rsa:4096 -sha256 -nodes -days 730 `
-subj "/CN=SuiteCloud CLI M2M" `
-keyout "$dir\m2m_private.pem" -out "$dir\m2m_public.pem"
# Only your account (and SYSTEM) can read the folder
icacls $dir /inheritance:r /grant:r "$($env:USERNAME):(OI)(CI)F" "SYSTEM:(OI)(CI)F"
Keep the folder out of OneDrive or any other synced location. The private key is a deploy credential.
2. Map the certificate in NetSuite
Go to Setup > Integration > Manage Authentication > OAuth 2.0 Client Credentials (M2M) Setup and create a new mapping:
- Entity: the user the deployments run as.
- Role: ideally a dedicated deployment role rather than Administrator. Anything holding the key can deploy with this role’s permissions.
- Application: SuiteCloud Development Integration — the one NetSuite provides. You don’t need to create your own.
- Certificate: upload the public file,
m2m_public.pem.
Save, and copy the Certificate ID it shows. If the account rejects M2M entirely, check that OAuth 2.0 and SuiteCloud Development Framework are enabled under Enable Features.
3. The error everyone hits
Run account:setup:ci in a normal PowerShell window and you’ll most likely get:
Machine-to-machine authentication is not allowed for the current execution context.
The CLI decides what kinds of authentication it allows from two environment variables, and with neither set it’s in browser mode:
SUITECLOUD_CI_PASSKEY— a passkey (32 to 100 letters and digits) the CLI uses to encrypt the stored M2M credentials. Set alone, it allows setting up M2M auth IDs and nothing else.SUITECLOUD_CI=1— together with the passkey, allows validate, deploy and everything else, using M2M auth IDs.
The catch: auth IDs created in browser mode aren’t visible in machine-to-machine mode, and the reverse. Set these variables machine-wide and your normal browser login, the one your IDE uses, stops working.
4. Store the passkey properly, and register the auth ID
Generate a random passkey once and store it encrypted with Windows DPAPI, so only your Windows login can read it:
$chars = [char[]]'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
$bytes = New-Object byte[] 64
[Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
$pk = -join ($bytes | ForEach-Object { $chars[$_ % $chars.Length] })
ConvertTo-SecureString $pk -AsPlainText -Force |
ConvertFrom-SecureString | Set-Content "$dir\ci_passkey.dpapi"
# Setup context: passkey only
$env:SUITECLOUD_CI_PASSKEY = $pk
suitecloud account:setup:ci --account 1234567 --authid PROD-CI `
--certificateid "<certificate ID from step 2>" `
--privatekeypath "$dir/m2m_private.pem"
Remove-Item Env:SUITECLOUD_CI_PASSKEY
5. A wrapper that turns CI mode on for one command
Rather than setting the variables globally, run CLI commands through a small script that sets them for its own process and clears them afterwards:
# sdf-ci.ps1 - run one SuiteCloud command in machine-to-machine mode
param([Parameter(ValueFromRemainingArguments=$true)][string[]]$CliArgs)
$enc = Get-Content (Join-Path $PSScriptRoot 'ci_passkey.dpapi') |
ConvertTo-SecureString
$env:SUITECLOUD_CI_PASSKEY = [Runtime.InteropServices.Marshal]::PtrToStringBSTR(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($enc))
$env:SUITECLOUD_CI = '1'
try { & suitecloud @CliArgs }
finally {
Remove-Item Env:SUITECLOUD_CI, Env:SUITECLOUD_CI_PASSKEY -ErrorAction SilentlyContinue
}
Point the project at the new auth ID by setting defaultAuthId to PROD-CI in its project.json, then from the project folder:
& "$env:USERPROFILE\.suitecloud-m2m\sdf-ci.ps1" project:validate --server
& "$env:USERPROFILE\.suitecloud-m2m\sdf-ci.ps1" project:deploy
The browser-based auth ID keeps working in the IDE, because the CI variables never exist outside that one command.
Housekeeping
- Expiry. The certificate above is good for two years. Put the renewal date in a calendar now.
- Revocation. Deleting or inactivating the M2M mapping in NetSuite cuts the key off immediately, from anywhere.
- One key per machine or pipeline. Separate mappings mean you can revoke one without breaking the others.
- Delete the stray copies. Once the public certificate is uploaded, any copy outside the key folder can go.
Related: four SDF deploy errors and what they actually mean, and SuiteScript 2.1 before the deadline.