Inventory Azure AI Foundry Like a Boss (Projects, Endpoints, Creators + CSV)
Inventory Azure AI Foundry Like a Boss (Projects, Endpoints, Creators + CSV)
If you’re the Azure admin everyone pings when “where did that AI project go?”—this one’s for you. The script below scans every subscription you can see, finds all Azure AI Foundry projects, captures who created them (from ARM systemData), builds the project endpoint, pulls account keys (masked on screen, full in CSV), prints a clean colored table, and exports a ready-to-filter CSV.
What you’ll get
-
Console table (keys masked): Account, Project, Location, CreatedBy, CreatedAt (UTC), Endpoint, Key1/Key2 (masked)
-
CSV saved to
C:\scripts\ai-foundry-projects-createdby-<timestamp>.csvwith full values:-
Project,ProjectEndpoint -
CreatedBy,CreatedByType,CreatedAt,LastModifiedBy,LastModifiedAt -
AccountKey1,AccountKey2
-
Prerequisites
-
PowerShell: 7.x recommended (Windows PowerShell 5.1 works too)
-
Azure permissions:
-
To discover projects: Reader or higher on target subscriptions/resource groups
-
To export account keys: a role that includes
Microsoft.CognitiveServices/accounts/listKeys/action
(e.g., Owner, Contributor, or Cognitive Services Contributor) on each account
-
-
Network: Allow outbound to Azure Resource Manager (
management.azure.com)
One-time setup (modules)
Open an elevated PowerShell and run:
# Allow running local scripts (current user only)
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force
# Make sure PowerShellGet can install modules (optional but helpful)
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
# Install the Azure modules this script uses
Install-Module Az.Accounts -Scope CurrentUser -Force -AllowClobber
Install-Module Az.Resources -Scope CurrentUser -Force -AllowClobber
Tip: If you’ve never connected to Azure from this machine,
Connect-AzAccountwill open a sign-in prompt the first time you run the script.
Copy-paste script
Save as Export-AIF-Projects.ps1 and run it as your Azure admin identity.
param(
[string]$OutFolder = 'C:\scripts'
)
$ErrorActionPreference = 'Stop'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
function Info($m){ Write-Host "[INFO] $m" -ForegroundColor Cyan }
function Warn($m){ Write-Host "[WARN] $m" -ForegroundColor Yellow }
# --- Ensure modules ---
$need = @('Az.Accounts','Az.Resources')
$miss = $need | Where-Object { -not (Get-Module -ListAvailable $_) }
if ($miss) { Install-Module Az -Scope CurrentUser -Force -AllowClobber }
Import-Module Az.Accounts -ErrorAction Stop
Import-Module Az.Resources -ErrorAction Stop
# --- Output folder ---
if (-not (Test-Path $OutFolder)) { New-Item -ItemType Directory -Path $OutFolder | Out-Null }
Info "CSV will be exported to: $OutFolder"
# --- Sign in ---
Info "Signing in…"
Connect-AzAccount | Out-Null
# --- Helpers ---
function Normalize-ProjectName([string]$Name){ ($Name -split '/')[ -1 ] }
function Build-ProjectEndpoint([string]$Account,[string]$Project){
"https://$($Account.ToLowerInvariant()).services.ai.azure.com/api/projects/$([uri]::EscapeDataString($Project))"
}
# List projects under an account (ARM; GA with fallbacks)
function Get-FoundryProjectsForAccount {
param([string]$SubscriptionId,[string]$ResourceGroup,[string]$AccountName)
$apis = @("2025-06-01","2024-10-01-preview","2024-05-01-preview")
foreach ($api in $apis) {
$url = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroup/providers/Microsoft.CognitiveServices/accounts/$AccountName/projects?api-version=$api"
try {
$r = Invoke-AzRestMethod -Method GET -Uri $url
if ($r.StatusCode -eq 200) { return ($r.Content | ConvertFrom-Json).value }
} catch { }
}
@()
}
# Get one project's systemData (createdBy, createdAt, etc.)
function Get-ProjectSystemData {
param([string]$SubscriptionId,[string]$ResourceGroup,[string]$AccountName,[string]$ProjectName)
$apis = @("2025-06-01","2024-10-01-preview","2024-05-01-preview")
foreach ($api in $apis) {
$url = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroup/providers/Microsoft.CognitiveServices/accounts/$AccountName/projects/$([uri]::EscapeDataString($ProjectName))?api-version=$api"
try {
$r = Invoke-AzRestMethod -Method GET -Uri $url
if ($r.StatusCode -eq 200) { return (($r.Content | ConvertFrom-Json).'systemData') }
} catch { }
}
$null
}
# Accounts – List Keys (management plane)
function Get-AccountKeys {
param([string]$SubscriptionId,[string]$ResourceGroup,[string]$AccountName)
$api = "2025-06-01"
$url = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroup/providers/Microsoft.CognitiveServices/accounts/$AccountName/listKeys?api-version=$api"
try {
$r = Invoke-AzRestMethod -Method POST -Uri $url
if ($r.StatusCode -eq 200) { return ($r.Content | ConvertFrom-Json) }
} catch {
$code=$null; try{$code=[int]$_.Exception.Response.StatusCode}catch{}
Warn "Could not read keys for account '$AccountName' (HTTP $code). Need 'Microsoft.CognitiveServices/accounts/listKeys/action'."
}
$null
}
# Mask keys for console
function Mask-Key([string]$k){
if (-not $k -or $k.Length -lt 8) { return $k }
$k.Substring(0,4) + '***' + $k.Substring($k.Length-3,3)
}
# Pretty console table
function Show-ColoredTable {
param([Parameter(Mandatory)]$Rows)
$headerColor = 'Cyan'
$rowColors = @('Gray','White')
Write-Host ""
Write-Host ("{0,-16} {1,-22} {2,-11} {3,-24} {4,-19} {5,-58} {6,-12} {7,-12}" -f `
'Account','Project','Location','CreatedBy','CreatedAt(UTC)','Endpoint','Key1','Key2') -ForegroundColor $headerColor
Write-Host ('-'*180) -ForegroundColor DarkGray
$i=0
foreach ($r in $Rows) {
$fg = $rowColors[$i % $rowColors.Count]
$k1 = Mask-Key $r.AccountKey1; $k2 = Mask-Key $r.AccountKey2
Write-Host ("{0,-16} {1,-22} {2,-11} {3,-24} {4,-19} {5,-58} {6,-12} {7,-12}" -f `
$r.Account, $r.Project, $r.Location, $r.CreatedBy, $r.CreatedAt, $r.ProjectEndpoint, $k1, $k2) -ForegroundColor $fg
$i++
}
}
# --- Collect ---
$rows = New-Object System.Collections.Generic.List[object]
$subs = Get-AzSubscription
foreach ($sub in $subs) {
Info "Scanning subscription: $($sub.Name) [$($sub.Id)]"
Select-AzSubscription -SubscriptionId $sub.Id | Out-Null
$accounts = Get-AzResource -ResourceType "Microsoft.CognitiveServices/accounts" -ExpandProperties
if (-not $accounts) { Info "No Cognitive Services accounts in $($sub.Name)."; continue }
foreach ($acct in $accounts) {
$rg = $acct.ResourceGroupName
$name = $acct.Name
$location = $acct.Location
# Account keys once per account
$keys = Get-AccountKeys -SubscriptionId $sub.Id -ResourceGroup $rg -AccountName $name
# Projects
$projects = Get-FoundryProjectsForAccount -SubscriptionId $sub.Id -ResourceGroup $rg -AccountName $name
if (-not $projects -or $projects.Count -eq 0) {
# still output a row to show keys for the account
$rows.Add([pscustomobject]@{
SubscriptionName = $sub.Name
SubscriptionId = $sub.Id
ResourceGroup = $rg
Location = $location
Account = $name
Project = ''
ProjectEndpoint = ''
CreatedBy = ''
CreatedByType = ''
CreatedAt = ''
LastModifiedBy = ''
LastModifiedAt = ''
AccountKey1 = $keys.key1
AccountKey2 = $keys.key2
}) | Out-Null
continue
}
foreach ($p in $projects) {
$proj = Normalize-ProjectName $p.name
$endpoint = Build-ProjectEndpoint -Account $name -Project $proj
$sys = Get-ProjectSystemData -SubscriptionId $sub.Id -ResourceGroup $rg -AccountName $name -ProjectName $proj
$createdBy = $sys.createdBy
$createdByType = $sys.createdByType
$createdAtUtc = $null; if ($sys.createdAt) { $createdAtUtc = ([datetime]$sys.createdAt).ToUniversalTime().ToString("yyyy-MM-dd HH:mm") }
$lastBy = $sys.lastModifiedBy
$lastAtUtc = $null; if ($sys.lastModifiedAt) { $lastAtUtc = ([datetime]$sys.lastModifiedAt).ToUniversalTime().ToString("yyyy-MM-dd HH:mm") }
$rows.Add([pscustomobject]@{
SubscriptionName = $sub.Name
SubscriptionId = $sub.Id
ResourceGroup = $rg
Location = $location
Account = $name
Project = $proj
ProjectEndpoint = $endpoint
CreatedBy = $createdBy
CreatedByType = $createdByType
CreatedAt = $createdAtUtc
LastModifiedBy = $lastBy
LastModifiedAt = $lastAtUtc
AccountKey1 = $keys.key1
AccountKey2 = $keys.key2
}) | Out-Null
}
}
}
# --- Output ---
if ($rows.Count -eq 0) {
Warn "No AI Foundry projects/accounts found."
return
}
Show-ColoredTable -Rows $rows
$csv = Join-Path $OutFolder ("ai-foundry-projects-createdby-{0}.csv" -f (Get-Date -Format 'yyyyMMdd-HHmmss'))
$rows |
Select-Object SubscriptionName,SubscriptionId,ResourceGroup,Location,Account,Project,ProjectEndpoint,CreatedBy,CreatedByType,CreatedAt,LastModifiedBy,LastModifiedAt,AccountKey1,AccountKey2 |
Export-Csv -NoTypeInformation -Encoding UTF8 $csv
Info "Exported CSV: $csv"
How to run
# If you skipped the one-time setup, run those steps first.
# Then execute:
.\Export-AIF-Projects.ps1
When it completes, look for:
[INFO] Exported CSV: C:\scripts\ai-foundry-projects-createdby-YYYYMMDD-HHmmss.csv
Open that in Excel/Power BI and filter by subscription, project, creator, or endpoint.
Troubleshooting quickies
-
No projects appear → Ensure your signed-in identity has Reader on the subscriptions/resource groups.
-
Keys are blank → You’re missing
accounts/listKeysrights on the Cognitive Services account; grant Contributor or a role that includes that action. -
Need non-interactive (app-only) auth? → Use a service principal with
Connect-AzAccount -ServicePrincipal …. I can share an app-only variant if you want.

Comments
Post a Comment