Skip to content

Commit 63de32c

Browse files
authored
Add files via upload
1 parent ac959b5 commit 63de32c

1 file changed

Lines changed: 311 additions & 0 deletions

File tree

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
#requires -Version 5.1
2+
<#
3+
.SYNOPSIS
4+
Exports an inventory of Microsoft Entra application registrations and service principals.
5+
6+
.DESCRIPTION
7+
Enumerates all application registration objects and enterprise application service
8+
principals visible to the current Microsoft Graph session. The script exports separate
9+
CSV reports plus a normalized combined inventory.
10+
11+
The script reuses an existing Microsoft Graph PowerShell session by default. It does not
12+
install modules, request new consent, or disconnect a session that it did not create.
13+
Use -ConnectIfNeeded only when an interactive connection should be attempted.
14+
15+
Application registrations and service principals are different directory objects. An
16+
application registration defines an application, while a service principal represents
17+
an application's local identity in a tenant. Consequently, their counts do not need to
18+
match.
19+
20+
.PARAMETER OutputPath
21+
Directory in which the CSV reports are created. Defaults to the script directory.
22+
23+
.PARAMETER TenantId
24+
Optional tenant ID or verified tenant domain used only with -ConnectIfNeeded.
25+
26+
.PARAMETER ConnectIfNeeded
27+
If no Microsoft Graph session exists, interactively connects with Application.Read.All.
28+
Without this switch, the script stops and explains how to establish a session.
29+
30+
.EXAMPLE
31+
Connect-MgGraph -Scopes "Application.Read.All" -NoWelcome
32+
.\list_all_applications.ps1
33+
34+
Reuses the current Microsoft Graph session and writes reports beside the script.
35+
36+
.EXAMPLE
37+
.\list_all_applications.ps1 -OutputPath "C:\Reports"
38+
39+
Writes reports to C:\Reports while reusing the current Graph session.
40+
41+
.EXAMPLE
42+
.\list_all_applications.ps1 -ConnectIfNeeded -TenantId "contoso.onmicrosoft.com"
43+
44+
Connects interactively only when no Graph session already exists.
45+
46+
.NOTES
47+
Required Microsoft Graph permission: Application.Read.All or another permission that
48+
allows both application and service-principal enumeration.
49+
#>
50+
51+
[CmdletBinding()]
52+
param(
53+
[Parameter()]
54+
[ValidateNotNullOrEmpty()]
55+
[string]$OutputPath = $PSScriptRoot,
56+
57+
[Parameter()]
58+
[ValidateNotNullOrEmpty()]
59+
[string]$TenantId,
60+
61+
[Parameter()]
62+
[switch]$ConnectIfNeeded
63+
)
64+
65+
Set-StrictMode -Version Latest
66+
$ErrorActionPreference = 'Stop'
67+
68+
function Join-Values {
69+
[CmdletBinding()]
70+
param(
71+
[Parameter(ValueFromPipeline)]
72+
[AllowNull()]
73+
[object]$Value
74+
)
75+
76+
process {
77+
if ($null -eq $Value) { return '' }
78+
$items = @($Value) | Where-Object { $null -ne $_ -and "$_" -ne '' }
79+
return ($items -join ';')
80+
}
81+
}
82+
83+
function Get-DateText {
84+
[CmdletBinding()]
85+
param([AllowNull()][object]$Value)
86+
87+
if ($null -eq $Value -or [string]::IsNullOrWhiteSpace("$Value")) { return '' }
88+
try { return ([datetime]$Value).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }
89+
catch { return "$Value" }
90+
}
91+
92+
function Get-GraphProperty {
93+
[CmdletBinding()]
94+
param(
95+
[AllowNull()][object]$InputObject,
96+
[Parameter(Mandatory)][string]$Name
97+
)
98+
99+
if ($null -eq $InputObject) { return $null }
100+
101+
$property = $InputObject.PSObject.Properties[$Name]
102+
if ($null -ne $property) { return $property.Value }
103+
104+
$additionalProperty = $InputObject.PSObject.Properties['AdditionalProperties']
105+
if ($null -ne $additionalProperty -and $null -ne $additionalProperty.Value) {
106+
$dictionary = $additionalProperty.Value
107+
if ($dictionary -is [System.Collections.IDictionary]) {
108+
foreach ($key in $dictionary.Keys) {
109+
if ([string]::Equals([string]$key, $Name, [System.StringComparison]::OrdinalIgnoreCase)) {
110+
return $dictionary[$key]
111+
}
112+
}
113+
}
114+
}
115+
116+
return $null
117+
}
118+
119+
function Get-VerifiedPublisherName {
120+
[CmdletBinding()]
121+
param([AllowNull()][object]$InputObject)
122+
123+
$verifiedPublisher = Get-GraphProperty -InputObject $InputObject -Name 'VerifiedPublisher'
124+
if ($null -eq $verifiedPublisher) { return '' }
125+
126+
$displayName = Get-GraphProperty -InputObject $verifiedPublisher -Name 'DisplayName'
127+
if ($null -ne $displayName) { return $displayName }
128+
129+
if ($verifiedPublisher -is [System.Collections.IDictionary] -and $verifiedPublisher.Contains('displayName')) {
130+
return $verifiedPublisher['displayName']
131+
}
132+
133+
return ''
134+
}
135+
136+
$requiredCommands = @(
137+
'Get-MgContext',
138+
'Connect-MgGraph',
139+
'Get-MgApplication',
140+
'Get-MgServicePrincipal'
141+
)
142+
143+
$missingCommands = @(
144+
foreach ($command in $requiredCommands) {
145+
if (-not (Get-Command $command -ErrorAction SilentlyContinue)) { $command }
146+
}
147+
)
148+
149+
if ($missingCommands.Count -gt 0) {
150+
throw "Missing Microsoft Graph PowerShell command(s): $($missingCommands -join ', '). Install the Microsoft.Graph module before running this script."
151+
}
152+
153+
$context = Get-MgContext
154+
$createdSession = $false
155+
156+
if ($null -eq $context) {
157+
if (-not $ConnectIfNeeded) {
158+
throw 'No active Microsoft Graph session was found. Run Connect-MgGraph -Scopes "Application.Read.All" -NoWelcome, or rerun this script with -ConnectIfNeeded.'
159+
}
160+
161+
$connectParameters = @{
162+
Scopes = @('Application.Read.All')
163+
NoWelcome = $true
164+
}
165+
if ($TenantId) { $connectParameters.TenantId = $TenantId }
166+
167+
Connect-MgGraph @connectParameters
168+
$createdSession = $true
169+
$context = Get-MgContext
170+
}
171+
172+
if ($null -eq $context) {
173+
throw 'Microsoft Graph authentication did not produce an active session.'
174+
}
175+
176+
if (-not (Test-Path -LiteralPath $OutputPath)) {
177+
$null = New-Item -ItemType Directory -Path $OutputPath -Force
178+
}
179+
180+
$resolvedOutputPath = (Resolve-Path -LiteralPath $OutputPath).Path
181+
$timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
182+
$appPath = Join-Path $resolvedOutputPath "entra-application-registrations-$timestamp.csv"
183+
$spPath = Join-Path $resolvedOutputPath "entra-service-principals-$timestamp.csv"
184+
$combinedPath = Join-Path $resolvedOutputPath "entra-application-inventory-$timestamp.csv"
185+
186+
Write-Host "Reusing Microsoft Graph session for $($context.Account)." -ForegroundColor Cyan
187+
Write-Host "Tenant ID: $($context.TenantId) | Authentication: $($context.AuthType)" -ForegroundColor DarkGray
188+
Write-Host 'Retrieving application registrations...' -ForegroundColor Cyan
189+
190+
$applications = @(Get-MgApplication -All -Property @(
191+
'id', 'appId', 'displayName', 'createdDateTime', 'signInAudience',
192+
'publisherDomain', 'verifiedPublisher', 'tags', 'disabledByMicrosoftStatus',
193+
'keyCredentials', 'passwordCredentials'
194+
))
195+
196+
Write-Host 'Retrieving service principals...' -ForegroundColor Cyan
197+
198+
$servicePrincipals = @(Get-MgServicePrincipal -All -Property @(
199+
'id', 'appId', 'displayName', 'accountEnabled', 'servicePrincipalType',
200+
'signInAudience', 'appOwnerOrganizationId', 'createdDateTime',
201+
'publisherName', 'verifiedPublisher', 'homepage', 'replyUrls',
202+
'servicePrincipalNames', 'tags', 'disabledByMicrosoftStatus',
203+
'keyCredentials', 'passwordCredentials'
204+
))
205+
206+
$appReport = @(
207+
foreach ($app in $applications) {
208+
$passwordCredentials = @(Get-GraphProperty -InputObject $app -Name 'PasswordCredentials')
209+
$keyCredentials = @(Get-GraphProperty -InputObject $app -Name 'KeyCredentials')
210+
211+
[pscustomobject][ordered]@{
212+
ObjectType = 'ApplicationRegistration'
213+
DisplayName = Get-GraphProperty -InputObject $app -Name 'DisplayName'
214+
ApplicationClientId = Get-GraphProperty -InputObject $app -Name 'AppId'
215+
ApplicationObjectId = Get-GraphProperty -InputObject $app -Name 'Id'
216+
CreatedDateTimeUtc = Get-DateText (Get-GraphProperty -InputObject $app -Name 'CreatedDateTime')
217+
SignInAudience = Get-GraphProperty -InputObject $app -Name 'SignInAudience'
218+
PublisherDomain = Get-GraphProperty -InputObject $app -Name 'PublisherDomain'
219+
VerifiedPublisher = Get-VerifiedPublisherName -InputObject $app
220+
DisabledByMicrosoftStatus = Get-GraphProperty -InputObject $app -Name 'DisabledByMicrosoftStatus'
221+
PasswordCredentialCount = @($passwordCredentials | Where-Object { $null -ne $_ }).Count
222+
KeyCredentialCount = @($keyCredentials | Where-Object { $null -ne $_ }).Count
223+
Tags = Join-Values (Get-GraphProperty -InputObject $app -Name 'Tags')
224+
}
225+
}
226+
)
227+
228+
$spReport = @(
229+
foreach ($sp in $servicePrincipals) {
230+
$passwordCredentials = @(Get-GraphProperty -InputObject $sp -Name 'PasswordCredentials')
231+
$keyCredentials = @(Get-GraphProperty -InputObject $sp -Name 'KeyCredentials')
232+
233+
[pscustomobject][ordered]@{
234+
ObjectType = 'ServicePrincipal'
235+
DisplayName = Get-GraphProperty -InputObject $sp -Name 'DisplayName'
236+
ApplicationClientId = Get-GraphProperty -InputObject $sp -Name 'AppId'
237+
ServicePrincipalObjectId = Get-GraphProperty -InputObject $sp -Name 'Id'
238+
AccountEnabled = Get-GraphProperty -InputObject $sp -Name 'AccountEnabled'
239+
ServicePrincipalType = Get-GraphProperty -InputObject $sp -Name 'ServicePrincipalType'
240+
CreatedDateTimeUtc = Get-DateText (Get-GraphProperty -InputObject $sp -Name 'CreatedDateTime')
241+
SignInAudience = Get-GraphProperty -InputObject $sp -Name 'SignInAudience'
242+
AppOwnerOrganizationId = Get-GraphProperty -InputObject $sp -Name 'AppOwnerOrganizationId'
243+
PublisherName = Get-GraphProperty -InputObject $sp -Name 'PublisherName'
244+
VerifiedPublisher = Get-VerifiedPublisherName -InputObject $sp
245+
Homepage = Get-GraphProperty -InputObject $sp -Name 'Homepage'
246+
ReplyUrls = Join-Values (Get-GraphProperty -InputObject $sp -Name 'ReplyUrls')
247+
ServicePrincipalNames = Join-Values (Get-GraphProperty -InputObject $sp -Name 'ServicePrincipalNames')
248+
DisabledByMicrosoftStatus = Get-GraphProperty -InputObject $sp -Name 'DisabledByMicrosoftStatus'
249+
PasswordCredentialCount = @($passwordCredentials | Where-Object { $null -ne $_ }).Count
250+
KeyCredentialCount = @($keyCredentials | Where-Object { $null -ne $_ }).Count
251+
Tags = Join-Values (Get-GraphProperty -InputObject $sp -Name 'Tags')
252+
}
253+
}
254+
)
255+
256+
$combinedReport = @(
257+
foreach ($app in $appReport) {
258+
[pscustomobject][ordered]@{
259+
ObjectType = $app.ObjectType
260+
DisplayName = $app.DisplayName
261+
ApplicationClientId = $app.ApplicationClientId
262+
DirectoryObjectId = $app.ApplicationObjectId
263+
AccountEnabled = ''
264+
ServicePrincipalType = ''
265+
SignInAudience = $app.SignInAudience
266+
CreatedDateTimeUtc = $app.CreatedDateTimeUtc
267+
Publisher = $app.PublisherDomain
268+
VerifiedPublisher = $app.VerifiedPublisher
269+
PasswordCredentialCount = $app.PasswordCredentialCount
270+
KeyCredentialCount = $app.KeyCredentialCount
271+
DisabledByMicrosoftStatus = $app.DisabledByMicrosoftStatus
272+
}
273+
}
274+
foreach ($sp in $spReport) {
275+
[pscustomobject][ordered]@{
276+
ObjectType = $sp.ObjectType
277+
DisplayName = $sp.DisplayName
278+
ApplicationClientId = $sp.ApplicationClientId
279+
DirectoryObjectId = $sp.ServicePrincipalObjectId
280+
AccountEnabled = $sp.AccountEnabled
281+
ServicePrincipalType = $sp.ServicePrincipalType
282+
SignInAudience = $sp.SignInAudience
283+
CreatedDateTimeUtc = $sp.CreatedDateTimeUtc
284+
Publisher = $sp.PublisherName
285+
VerifiedPublisher = $sp.VerifiedPublisher
286+
PasswordCredentialCount = $sp.PasswordCredentialCount
287+
KeyCredentialCount = $sp.KeyCredentialCount
288+
DisabledByMicrosoftStatus = $sp.DisabledByMicrosoftStatus
289+
}
290+
}
291+
)
292+
293+
$appReport | Sort-Object DisplayName, ApplicationClientId |
294+
Export-Csv -LiteralPath $appPath -NoTypeInformation -Encoding UTF8
295+
$spReport | Sort-Object DisplayName, ApplicationClientId |
296+
Export-Csv -LiteralPath $spPath -NoTypeInformation -Encoding UTF8
297+
$combinedReport | Sort-Object ObjectType, DisplayName, ApplicationClientId |
298+
Export-Csv -LiteralPath $combinedPath -NoTypeInformation -Encoding UTF8
299+
300+
Write-Host ''
301+
Write-Host 'Inventory complete.' -ForegroundColor Green
302+
Write-Host "Application registrations : $($appReport.Count)"
303+
Write-Host "Service principals : $($spReport.Count)"
304+
Write-Host "Combined inventory rows : $($combinedReport.Count)"
305+
Write-Host "Application report : $appPath"
306+
Write-Host "Service principal report : $spPath"
307+
Write-Host "Combined report : $combinedPath"
308+
309+
if ($createdSession) {
310+
Write-Host 'The Graph session created by this script remains connected for reuse.' -ForegroundColor DarkGray
311+
}

0 commit comments

Comments
 (0)