Windows Platform Guide

Complete guide for using Entra Auth Cli on Windows, including DPAPI-based secure storage, PowerShell integration, and Windows-specific features.

Overview

Entra Auth Cli leverages Windows security features for optimal token protection:

  • DPAPI Encryption: Windows Data Protection API for secure token storage
  • Per-User Isolation: Tokens encrypted per-user, per-machine
  • Native Integration: Works with Windows Credential Manager
  • PowerShell Support: First-class PowerShell scripting support

Installation

Using Winget

  # Install using Windows Package Manager
winget install GarrardKitchen.EntraAuthCli
  

Manual Installation

  # Download latest release
$version = "1.0.0"
$url = "https://github.com/garrardkitchen/entra-auth-cli/releases/download/v$version/entra-auth-cli-windows-amd64.exe"

# Download
Invoke-WebRequest -Uri $url -OutFile "$env:TEMP\entra-auth-cli.exe"

# Move to Program Files
Move-Item "$env:TEMP\entra-auth-cli.exe" "C:\Program Files\EntraToken\entra-auth-cli.exe" -Force

# Add to PATH
$path = [Environment]::GetEnvironmentVariable("Path", "User")
[Environment]::SetEnvironmentVariable("Path", "$path;C:\Program Files\EntraToken", "User")
  

Verify Installation

  # Check version
entra-auth-cli --version

# Test basic functionality
entra-auth-cli --help
  

Token Storage

DPAPI Encryption

Tokens are encrypted using Windows DPAPI:

  # Tokens stored at:
$env:LOCALAPPDATA\EntraAuthCli\profiles\

# Each profile has encrypted token file:
# - profile-name.json (configuration)
# - profile-name.token (encrypted token)
  

Security characteristics:

  • Encrypted per-user, per-machine
  • Cannot be decrypted on different machine or by different user
  • Protected by Windows user account
  • Survives password changes

View Storage Location

  # Check storage path
$storagePath = "$env:LOCALAPPDATA\EntraAuthCli\profiles"
Get-ChildItem $storagePath

# Example output:
# Directory: C:\Users\jsmith\AppData\Local\EntraAuthCli\profiles
#
# Mode                 LastWriteTime         Length Name
# ----                 -------------         ------ ----
# -a----        12/28/2025   2:30 PM            456 default.json
# -a----        12/28/2025   2:30 PM           2048 default.token
# -a----        12/28/2025   3:15 PM            512 production.json
# -a----        12/28/2025   3:15 PM           2048 production.token
  

Security Permissions

  # Check file permissions
Get-Acl "$env:LOCALAPPDATA\EntraAuthCli\profiles\default.token" | Format-List

# Verify only current user has access
$acl = Get-Acl "$env:LOCALAPPDATA\EntraAuthCli\profiles\default.token"
$acl.Access | Where-Object { $_.IdentityReference -eq "$env:USERDOMAIN\$env:USERNAME" }
  

PowerShell Integration

Basic Usage

  # Get token
$token = entra-auth-cli get-token --output json | ConvertFrom-Json
$accessToken = $token.access_token

# Use token in API call
$headers = @{
    Authorization = "Bearer $accessToken"
}
$response = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me" -Headers $headers
$response
  

Function Wrapper

  function Get-EntraToken {
    [CmdletBinding()]
    param(
        [string]$Profile = "default",
        [string]$Scope,
        [switch]$Force
    )
    
    $arguments = @("get-token", "--profile", $Profile, "--output", "json")
    
    if ($Scope) {
        $arguments += @("--scope", $Scope)
    }
    
    if ($Force) {
        $arguments += "--force"
    }
    
    try {
        $output = & entra-auth-cli @arguments 2>&1
        if ($LASTEXITCODE -ne 0) {
            throw "Failed to get token: $output"
        }
        
        $token = $output | ConvertFrom-Json
        return $token.access_token
    }
    catch {
        Write-Error "Error getting token: $_"
        return $null
    }
}

# Usage
$token = Get-EntraToken -Profile "production"
$token = Get-EntraToken -Scope "https://graph.microsoft.com/User.Read"
  

Error Handling

  function Invoke-EntraTokenCommand {
    param(
        [Parameter(Mandatory)]
        [string[]]$Arguments,
        [int]$MaxRetries = 3
    )
    
    $attempt = 0
    while ($attempt -lt $MaxRetries) {
        try {
            $output = & entra-auth-cli @Arguments 2>&1
            if ($LASTEXITCODE -eq 0) {
                return $output
            }
            
            $attempt++
            if ($attempt -lt $MaxRetries) {
                Write-Warning "Attempt $attempt failed. Retrying..."
                Start-Sleep -Seconds ($attempt * 2)
            }
        }
        catch {
            $attempt++
            if ($attempt -lt $MaxRetries) {
                Write-Warning "Error: $_. Retrying..."
                Start-Sleep -Seconds ($attempt * 2)
            }
            else {
                throw
            }
        }
    }
    
    throw "Command failed after $MaxRetries attempts"
}

# Usage
$result = Invoke-EntraTokenCommand -Arguments @("get-token", "--output", "json")
  

Microsoft Graph Module Integration

  # Install Microsoft Graph PowerShell SDK
Install-Module Microsoft.Graph -Scope CurrentUser

# Get token from Entra Auth Cli
$token = entra-auth-cli get-token --scope https://graph.microsoft.com/.default --output json | 
    ConvertFrom-Json | Select-Object -ExpandProperty access_token

# Connect to Microsoft Graph
$secureToken = ConvertTo-SecureString $token -AsPlainText -Force
Connect-MgGraph -AccessToken $secureToken

# Use Graph cmdlets
Get-MgUser -Top 10
Get-MgGroup -Top 10

# Disconnect
Disconnect-MgGraph
  

Windows-Specific Features

Task Scheduler Integration

  # Create scheduled task for token refresh
$action = New-ScheduledTaskAction -Execute "entra-auth-cli" -Argument "refresh --profile production"

$trigger = New-ScheduledTaskTrigger -Daily -At 6:00AM

$principal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType S4U

$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries

Register-ScheduledTask -TaskName "RefreshEntraToken" -Action $action -Trigger $trigger -Principal $principal -Settings $settings

# Run task manually
Start-ScheduledTask -TaskName "RefreshEntraToken"

# Check task history
Get-ScheduledTaskInfo -TaskName "RefreshEntraToken"
  

Windows Service Integration

  # Example Windows Service using Entra Auth Cli
# Service.ps1

Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.ServiceProcess;
using System.Timers;

public class EntraTokenService : ServiceBase {
    private Timer timer;
    
    public EntraTokenService() {
        ServiceName = "EntraTokenRefresh";
    }
    
    protected override void OnStart(string[] args) {
        timer = new Timer(3600000); // 1 hour
        timer.Elapsed += RefreshToken;
        timer.Start();
    }
    
    protected override void OnStop() {
        timer?.Stop();
        timer?.Dispose();
    }
    
    private void RefreshToken(object sender, ElapsedEventArgs e) {
        Process.Start(new ProcessStartInfo {
            FileName = "entra-auth-cli",
            Arguments = "refresh --profile production",
            UseShellExecute = false,
            RedirectStandardOutput = true
        });
    }
}
"@

# Install service
$serviceName = "EntraTokenRefresh"
New-Service -Name $serviceName -BinaryPathName "C:\Path\To\Service.exe" -StartupType Automatic
Start-Service $serviceName
  

Event Log Integration

  # Log Entra Auth Cli operations to Windows Event Log
function Write-EntraTokenLog {
    param(
        [Parameter(Mandatory)]
        [string]$Message,
        [ValidateSet('Information', 'Warning', 'Error')]
        [string]$EntryType = 'Information'
    )
    
    $source = "EntraAuthCli"
    $logName = "Application"
    
    # Create source if it doesn't exist
    if (-not [System.Diagnostics.EventLog]::SourceExists($source)) {
        New-EventLog -LogName $logName -Source $source
    }
    
    Write-EventLog -LogName $logName -Source $source -EventId 1000 -EntryType $EntryType -Message $Message
}

# Usage
Write-EntraTokenLog -Message "Token refreshed successfully for profile: production"
Write-EntraTokenLog -Message "Failed to refresh token" -EntryType Error
  

Common Use Cases

Azure DevOps Integration

  # Azure DevOps pipeline task
# build.ps1

param(
    [string]$TenantId = $env:AZURE_TENANT_ID,
    [string]$ClientId = $env:AZURE_CLIENT_ID,
    [string]$ClientSecret = $env:AZURE_CLIENT_SECRET
)

# Create profile
entra-auth-cli create-profile `
    --name azdo `
    --tenant-id $TenantId `
    --client-id $ClientId `
    --client-secret $ClientSecret `
    --scope "https://management.azure.com/.default"

# Get token
$token = entra-auth-cli get-token --profile azdo --output json | ConvertFrom-Json
$accessToken = $token.access_token

# Deploy to Azure
$headers = @{
    Authorization = "Bearer $accessToken"
    "Content-Type" = "application/json"
}

$body = @{
    location = "eastus"
    properties = @{
        template = Get-Content -Path "template.json" | ConvertFrom-Json
    }
} | ConvertTo-Json -Depth 10

Invoke-RestMethod -Method Post `
    -Uri "https://management.azure.com/subscriptions/$subscriptionId/resourcegroups/$resourceGroup/providers/Microsoft.Resources/deployments/$deploymentName?api-version=2021-04-01" `
    -Headers $headers `
    -Body $body
  

PowerShell Module

  # EntraToken.psm1

function Connect-EntraToken {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$ProfileName,
        [switch]$Force
    )
    
    $arguments = @("get-token", "--profile", $ProfileName, "--output", "json")
    if ($Force) { $arguments += "--force" }
    
    $output = & entra-auth-cli @arguments 2>&1
    if ($LASTEXITCODE -ne 0) {
        throw "Authentication failed: $output"
    }
    
    $token = $output | ConvertFrom-Json
    $script:CurrentToken = $token.access_token
    $script:CurrentProfile = $ProfileName
    
    Write-Verbose "Connected to profile: $ProfileName"
}

function Get-CurrentEntraToken {
    if (-not $script:CurrentToken) {
        throw "Not connected. Run Connect-EntraToken first."
    }
    return $script:CurrentToken
}

function Invoke-EntraGraphRequest {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Uri,
        [string]$Method = "GET",
        [object]$Body
    )
    
    $token = Get-CurrentEntraToken
    $headers = @{
        Authorization = "Bearer $token"
        "Content-Type" = "application/json"
    }
    
    $params = @{
        Uri = $Uri
        Method = $Method
        Headers = $headers
    }
    
    if ($Body) {
        $params.Body = $Body | ConvertTo-Json -Depth 10
    }
    
    Invoke-RestMethod @params
}

Export-ModuleMember -Function Connect-EntraToken, Get-CurrentEntraToken, Invoke-EntraGraphRequest

# Usage:
# Import-Module .\EntraToken.psm1
# Connect-EntraToken -ProfileName "production"
# Invoke-EntraGraphRequest -Uri "https://graph.microsoft.com/v1.0/me"
  

Security Best Practices

Secure Profile Creation

  # Read sensitive data securely
$tenantId = Read-Host -Prompt "Tenant ID"
$clientId = Read-Host -Prompt "Client ID"
$clientSecret = Read-Host -Prompt "Client Secret" -AsSecureString

# Convert SecureString to plain text (only for immediate use)
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($clientSecret)
$plainSecret = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)

try {
    entra-auth-cli create-profile `
        --name "secure-profile" `
        --tenant-id $tenantId `
        --client-id $clientId `
        --client-secret $plainSecret
}
finally {
    # Clear sensitive data
    [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
    $plainSecret = $null
}
  

Credential Manager Integration

  # Store credentials in Windows Credential Manager
cmdkey /generic:EntraAuthCli /user:$clientId /pass:$clientSecret

# Retrieve from Credential Manager
$credential = Get-StoredCredential -Target "EntraAuthCli"
$clientSecret = $credential.GetNetworkCredential().Password

# Use in profile creation
entra-auth-cli create-profile `
    --name "from-credman" `
    --client-secret $clientSecret
  

Troubleshooting

DPAPI Errors

Problem: “Unable to decrypt token”

Solutions:

  # Check user profile
whoami
echo $env:USERNAME

# Verify profile ownership
Get-Acl "$env:LOCALAPPDATA\EntraAuthCli\profiles\default.token" | Select-Object Owner

# Recreate profile if ownership changed
entra-auth-cli delete-profile --name default
entra-auth-cli create-profile --name default
  

Path Issues

Problem: “entra-auth-cli is not recognized”

Solutions:

  # Check PATH
$env:Path -split ';' | Select-String -Pattern "EntraToken"

# Add to PATH permanently
$path = [Environment]::GetEnvironmentVariable("Path", "User")
[Environment]::SetEnvironmentVariable("Path", "$path;C:\Program Files\EntraToken", "User")

# Add to PATH for current session
$env:Path += ";C:\Program Files\EntraToken"

# Verify
Get-Command entra-auth-cli
  

PowerShell Execution Policy

Problem: “Cannot run scripts”

Solutions:

  # Check current policy
Get-ExecutionPolicy

# Set policy for current user
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

# Or bypass for specific script
PowerShell -ExecutionPolicy Bypass -File .\script.ps1
  

Performance Optimization

Token Caching

  # Cache token in memory for script duration
$script:TokenCache = @{}

function Get-CachedToken {
    param([string]$Profile = "default")
    
    $now = Get-Date
    if ($script:TokenCache.ContainsKey($Profile)) {
        $cached = $script:TokenCache[$Profile]
        if ($cached.ExpiresAt -gt $now.AddMinutes(5)) {
            return $cached.Token
        }
    }
    
    # Get fresh token
    $tokenJson = entra-auth-cli get-token --profile $Profile --output json | ConvertFrom-Json
    $token = $tokenJson.access_token
    $expiresAt = $now.AddSeconds($tokenJson.expires_in)
    
    $script:TokenCache[$Profile] = @{
        Token = $token
        ExpiresAt = $expiresAt
    }
    
    return $token
}
  

Parallel Execution

  # Parallel API calls with same token
$token = Get-CachedToken
$headers = @{ Authorization = "Bearer $token" }

$users, $groups, $apps = Invoke-Parallel {
    Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users" -Headers $using:headers
    Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/groups" -Headers $using:headers
    Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/applications" -Headers $using:headers
}
  

Next Steps