Want to run scripts without ever pasting a password into your code again? Then automation via PowerShell and 1Password is the approach you’re looking for. You store your secrets in a separate vault, retrieve them with one small function, and then log in securely to Azure and Microsoft Graph. In this article you’ll see step by step what to do, including the PowerShell functions that actually work.
This is how you use credentials securely in your scripts, without passwords in your code.
Why automation via PowerShell and 1Password is the right choice
Many administrators put usernames and passwords directly in a script. That’s risky, because one mistake in Git and your secret is out in the open. That’s why this works differently: 1Password stores the data, PowerShell requests it at the moment of execution, and your script itself contains no secret at all.
You also keep control over permissions. You give each script access only to the vault it needs, and your personal data stays out of sight.
Requirements for automation via PowerShell and 1Password
First install the right components, otherwise your script will get stuck at the very first step. You need the 1Password CLI and, depending on your tasks, the modules for Azure and Microsoft Graph.
First install the 1Password CLI with winget:
winget install AgileBits.1Password.CLI
Then open a new PowerShell window, so that op is in your PATH, and check the installation and your connection:
op --version
op signin
op whoami
op vault list
This is what each command does:
- op –version confirms that the installation succeeded.
- op signin logs you in to 1Password. This isn’t always necessary: if you’ve enabled the option Integrate with 1Password CLI in the app under Settings → Developer, op itself will ask for biometric confirmation. If you use a Service Account, you don’t need to log in at all.
- op whoami shows which account you’re connected with.
- op vault list shows which vaults you have access to. Don’t see the Automation vault? Then check your permissions first.
Next, install the PowerShell modules that your script uses:
Install-Module Az -Scope CurrentUser
Install-Module Microsoft.Graph -Scope CurrentUser
Step 1: Create a separate vault for automation
First create a dedicated vault for service credentials. This way you separate automation from your personal passwords. Do this in the app via the + next to Vaults, or via the CLI:
op vault create "Automation" --description "Service credentials and automation secrets"
Then move existing items to the new vault:
op item move '1Password Automation' --destination-vault Automation --current-vault Personal
Or create a new one:
op item create --category login --title '1Password Automation' --vault Automation `
username='<<applicationId>>' password='<<clientSecret>>'
Never put a secret directly in an op item create command. It will end up in your shell history. Create the item in the app or via a function instead.
function New-OPCredential {
param(
[Parameter(Mandatory)][string]$Vault,
[Parameter(Mandatory)][string]$Title,
[Parameter(Mandatory)][string]$UserName
)
$secure = Read-Host "Secret for '$Title'" -AsSecureString
$plain = ConvertFrom-SecureString $secure -AsPlainText # PowerShell 7
$template = @{
title = $Title
category = 'LOGIN'
fields = @(
@{ id = 'username'; type = 'STRING'; purpose = 'USERNAME'; value = $UserName },
@{ id = 'password'; type = 'CONCEALED'; purpose = 'PASSWORD'; value = $plain }
)
} | ConvertTo-Json -Depth 5
$template | op item create --vault $Vault
}
New-OPCredential -Vault 'Automation' -Title '1Password Automation' -UserName '<<applicationId>>'
Step 2: Read credentials from 1Password with one function
Now comes the core component of automation via PowerShell and 1Password. This function reads a username and password and returns a pscredential. It also checks the output, so you get a clear error message if something goes wrong.
function Get-OPCredential {
param(
[Parameter(Mandatory)][string]$Vault,
[Parameter(Mandatory)][string]$Item
)
$user = (op read "op://$Vault/$Item/username" 2>&1 | Out-String).Trim()
if ($LASTEXITCODE -ne 0 -or -not $user) { throw "reading username failed: $user" }
$plain = (op read "op://$Vault/$Item/password" 2>&1 | Out-String).Trim()
if ($LASTEXITCODE -ne 0 -or -not $plain) { throw "reading password failed" }
[pscredential]::new($user, (ConvertTo-SecureString $plain -AsPlainText -Force))
}
$cred = Get-OPCredential -Vault 'Automation' -Item '1Password Automation'
Does your item have different field names? Then view the exact labels with:
op item get '1Password Automation' --vault Automation --format json | ConvertFrom-Json |
Select-Object -ExpandProperty fields | Select-Object id, label, type
Step 3: Use a service principal, not a user account
This is where many administrators go wrong. If you log in with Connect-AzAccount -Credential using a regular user account, you’ll get an error about MFA. Microsoft now enforces MFA for Azure, and password login from the command line therefore no longer works.
The solution is a service principal. That’s an identity for your script, without an MFA prompt. Here’s how to create one:
- In the Azure portal, go to Microsoft Entra ID → App registrations → New registration.
- Copy the Application (client) ID and the Tenant ID.
- Under Certificates & secrets, create a client secret and copy the value immediately. You’ll only see it once.
- In 1Password, store the client ID as the username and the secret as the password.
Also set a short expiration date on the secret and renew it regularly.
Step 4: Give the service principal an Azure role
After the first login you may see an empty Subscription name column, or the message that the account has no access to the subscription. The cause is simple: the identity doesn’t have an RBAC role yet. API permissions don’t help here, because they apply to Graph and not to Azure resources.
So assign a role on the subscription or, even better, on a resource group:
New-AzRoleAssignment `
-ApplicationId '<<applicationId>>' `
-RoleDefinitionName 'Contributor' `
-Scope '/subscriptions/<<subscriptionId>>'
In the Dutch portal this role is called Inzender. Choose the regular Inzender, not a variant such as Automation-inzender. The latter only manages Automation accounts and runbooks. If you’d rather start cautiously, choose Lezer (Reader) and expand later.
Global Administrator is not an option here. That role belongs to Entra ID and not to Azure resources. Moreover, a leaked secret with Global Admin rights gives full control over your tenant. So always apply the principle of least privilege.
Step 5: Log in to Azure
Once the role is active, the login works. This can take a few minutes. Then use:
$cred = Get-OPCredential -Vault 'Automation' -Item '1Password Automation'
Connect-AzAccount -ServicePrincipal -Credential $cred `
-TenantId '<<tenantId>>' `
-Subscription '<<subscriptionId>>'
Do you see the subscription in the output? Then everything works. You can optionally verify it with Get-AzContext and Get-AzSubscription.
Step 6: Add Microsoft Graph for Entra tasks
Do you also want to manage users, groups or app registrations? Then you need Microsoft Graph. Note: the -Scopes parameter only works for interactive users. A service principal uses application permissions.
- Go to App registrations → your app → API permissions → Add a permission → Microsoft Graph → Application permissions.
- Choose only what you really need, for example User.Read.All.
- Click Grant admin consent.
Then log in with the client secret:
Connect-MgGraph -TenantId '<<tenantId>>' -ClientSecretCredential $cred -NoWelcome
For some tasks, such as resetting passwords, you also need an Entra directory role, for example User Administrator. Avoid broad permissions such as Directory.ReadWrite.All, unless your script demonstrably uses them.
Automation via PowerShell and 1Password in one function
Finally, put everything together. This function logs in once and remembers that for the rest of your session. The result: no prompts, no passwords in your code, and a script that can run unattended.
function Use-Azure {
param(
[Parameter(Mandatory)][string]$tenantId,
[Parameter(Mandatory)][string]$subscriptionId
)
if ($global:connected) {
Write-Host "Logon has already been done. No further action needed."
return
}
$cred = Get-OPCredential -Vault 'Automation' -Item '1Password Automation'
Connect-AzAccount -ServicePrincipal -Credential $cred `
-TenantId $tenantId -Subscription $subscriptionId | Out-Null
Connect-MgGraph -TenantId $tenantId -ClientSecretCredential $cred -NoWelcome | Out-Null
$global:connected = $true
Write-Host "Logon was successful"
}
Use-Azure -tenantId '<<tenantId>>' -subscriptionId '<<subscriptionId>>'
Do you also use the Azure CLI? Keep in mind that az login is a separate login with its own token cache. Only run it if you really need az commands:
az login --service-principal `
--username $cred.UserName `
--password (ConvertFrom-SecureString $cred.Password -AsPlainText) `
--tenant $tenantId --only-show-errors | Out-Null
The secret is then briefly visible in the process list. So only use this if there’s no other way. The -AsPlainText argument also requires PowerShell 7. Unless you’re giving a demo, I wouldn’t choose this and would simply perform the login as a manual activity until Microsoft has improved this process.
Scripts without biometrics: the 1Password Service Account
Do you want a server or scheduler to start your script without your fingerprint? Then create a 1Password Service Account. This is also possible with a Personal account, as long as you do it via the website and not via the app. You give the service account read access to the Automation vault only.
- Log in to 1Password.com and open the wizard: create service account. Or go to Developer → Directory, choose Other under Infrastructure Secrets Management and click Create a Service Account.
- Give it a name, for example PowerShell Automation, and leave the option to create vaults turned off.
- Choose only the Automation vault with read access.
- Click Create Account and copy the token immediately. You’ll only see it once, so store it in your personal vault.
Note: you can no longer change the permissions and vault access of a service account afterwards. Moreover, a service account cannot get access to your Personal or Private vault. So put everything the script needs in Automation. If you made a mistake, create a new service account. Don’t see the option in your account? Then check whether your account has sufficient permissions to create service accounts.
Next, set the token as an environment variable and test the connection:
$env:OP_SERVICE_ACCOUNT_TOKEN = '<<token>>'
op whoami
op vault list
op read "op://Automation/Azure SP Automation/username"
The command op whoami should now show the service account and op vault list only the Automation vault. If you get the client ID back, Get-OPCredential works without a biometric prompt. Your op read references stay the same.
Do you want to make the token permanently available for a scheduled task? Then set it for your user and open a new PowerShell window afterwards:
[Environment]::SetEnvironmentVariable('OP_SERVICE_ACCOUNT_TOKEN', '<<token>>', 'User')
Important
Never store the token in your repository or profile script, but in a secret store of your CI/CD environment or as an environment variable. Note: as long as OP_SERVICE_ACCOUNT_TOKEN is set, op always uses this token and not the connection with the desktop app. On your own laptop, you remove the variable with Remove-Item Env:OP_SERVICE_ACCOUNT_TOKEN.
Common mistakes with automation via PowerShell and 1Password
- “userName is null”: the field has a different name or op read gave an error. Check the field names and use the function above.
- MFA message with Connect-AzAccount -Credential: use a service principal instead of a user account.
- “does not have access to subscription”: assign an RBAC role and wait a few minutes. Then run Clear-AzContext -Force.
- Wrong tenant or subscription: check that the app registration and subscription are in the same tenant.
- Graph doesn’t work with -Scopes: use application permissions and -ClientSecretCredential.
In short
Automation via PowerShell and 1Password requires a fixed order: create a separate vault, read your credentials with Get-OPCredential, use a service principal, assign the right RBAC role, and then log in to Azure and Graph. Keep your permissions small, replace secrets regularly, and store nothing in your code. This way you build scripts that are secure, repeatable and maintainable.
Want to know more about this 1Password? Look here. Want to get in touch with me? Click here.