private/export/Export-ZtGraphEntity.ps1
|
function Export-ZtGraphEntity { <# .SYNOPSIS Export all graph items of a specified url. .DESCRIPTION Export all graph items of a specified url. Will generate them in a paged file format, with each response set written to a single file., a numbered suffix counting up with each page. It will also ensure, that previous data is first cleaned up (e.g. if the last export was interrupted). .PARAMETER Name The name of the entity to export. This will also become the name of the folder under which the content is stored. .PARAMETER Uri The relative Uri from where to collect the data. E.g.: beta/applications To export all applications. .PARAMETER QueryString Additional query information to include with the request. Use this to speciy page-size information, properties to collect or other relevant parameters needed for this to work. .PARAMETER RelatedPropertyNames Additional sub-datasets to retrieve for each entity. For example in cases, where multiple requests are needed - such as "oauth2PermissionGrants" for Service Principals. .PARAMETER MaximumQueryTime Maximum time we will spend on this query, iterating through the pages. .PARAMETER ExportPath Where all the results are stored. .EXAMPLE PS C:\> Export-ZtGraphEntity -Name Application -Uri 'beta/applications' -QueryString '$top=999' -ExportPath C:\assessment\export Retrieves all applications (=App Registrations) from the tenant using the beta api and page-size of 999. #> [CmdletBinding()] param ( # The folder for the entity. e.g. ServicePrincipals [Parameter(Mandatory = $true)] [string] $Name, # The entity to export. e.g. /beta/servicePrincipals [Parameter(Mandatory = $true)] [string] $Uri, # Parameters to include. e.g. $expand=appRoleAssignments&$top=999 [Parameter(Mandatory = $false)] [string] $QueryString, # The additional properties/relations to be queried for each object. e.g. oauth2PermissionGrants [string[]] $RelatedPropertyNames, # The maximum time (in minutes) the assessment should spend on querying this entity. [int] $MaximumQueryTime, # The folder to output the report to. [Parameter(Mandatory = $true)] [string] $ExportPath ) # Get maximum size limit for SignIn logs (1GB by default) $maxSizeBytes = Get-PSFConfigValue -FullName 'ZeroTrustAssessment.Export.SignInLog.MaxSizeBytes' -Fallback 1073741824 if (Get-ZtConfig -ExportPath $ExportPath -Property $Name) { Write-PSFMessage "Skipping '{0}' since it was downloaded previously" -StringValues $Name -Target $Name -Tag Export, redundant, skip return } #region Utility Functions function Export-Page { [CmdletBinding()] param ( [int] $PageIndex, [string] $Path, $Results, [string[]] $RelatedPropertyNames, [string] $Name, [string] $Uri ) Write-PSFMessage "Exporting $Name page $PageIndex" $newResults = $Results if ($RelatedPropertyNames) { $items = $Results.Value foreach ($propertyName in $RelatedPropertyNames) { Add-GraphProperty -Results $items -PropertyName $propertyName -Name $Name -Uri $Uri } $newResults = @{ value = $items } } $filePath = Join-Path -Path $Path -ChildPath "$Name-$PageIndex.json" $newResults | Export-PSFJson -Path $filePath -Depth 100 -Encoding UTF8NoBom } function Add-GraphProperty { [CmdletBinding()] param ( $Results, [string] $PropertyName, [string] $Name, [string] $Uri ) Write-PSFMessage -Message "Adding {0} to {1}" -StringValues $PropertyName, $Name -Tag Graph $data = Invoke-ZtGraphBatchRequest -Path "$Uri/{0}/$PropertyName" -ArgumentList $Results -Properties id -Matched -ErrorAction SilentlyContinue -ErrorVariable failed # Since the argument property is the original hashtable provided, we can update the hashtable as it is and thereby update the original object foreach ($pair in $data) { if (-not $Pair.Success) { Write-PSFMessage -Level Warning "Failed to retrieve {0} for {1}" -StringValues $PropertyName, $pair.Argument.id -Target $pair continue } $pair.Argument[$PropertyName] = $($pair.Result) } foreach ($fail in $failed) { $itemID = $fail.TargetObject.url.replace($PropertyName,"").Trim("/").Split("/")[-1] if ($Name -eq "SignIn" -and $fail.Exception.Message -like "*The request was canceled due to the configured HttpClient.Timeout*") { Write-PSFMessage -Level Verbose "Timeout occurred while adding $PropertyName to $Name $itemID - silently continuing" -Tag Graph } else { Write-PSFMessage -Level Warning "Failed to add $PropertyName to $Name $itemID." -Tag Graph -ErrorRecord $fail } } } #endregion Utility Functions $pageIndex = 0 $totalSize = 0 $isSignInLog = $Name -eq 'SignIn' $folderPath = Join-Path -Path $ExportPath -ChildPath $Name Clear-ZtFolder -Path $folderPath $actualUri = $Uri + '?' + $QueryString $startTime = Get-Date $stopTime = $startTime.AddMinutes($MaximumQueryTime) $hasTimeLimit = $MaximumQueryTime -gt 0 do { $results = Invoke-MgGraphRequest -Method GET -Uri $actualUri -OutputType HashTable Export-Page -PageIndex $pageIndex -Path $folderPath -Results $results -RelatedPropertyNames $RelatedPropertyNames -Name $Name -Uri $Uri # Track file size for SignIn logs if ($isSignInLog) { $lastFile = Join-Path -Path $folderPath -ChildPath "$Name-$pageIndex.json" if (Test-Path $lastFile) { $fileSize = (Get-Item $lastFile).Length $totalSize += $fileSize if ($totalSize -gt $maxSizeBytes) { $sizeMB = [math]::Round($totalSize / 1MB, 2) $limitMB = [math]::Round($maxSizeBytes / 1MB, 2) Write-PSFMessage -Level Warning "Sign-in log export reached size limit of $limitMB MB (current: $sizeMB MB). Stopping export and continuing with next task." -Tag Export, SignIn, SizeLimit Write-Host "⚠️ " -NoNewline -ForegroundColor Yellow Write-Host "Sign-in log export reached the 1GB size limit ($sizeMB MB collected). Continuing with remaining exports..." -ForegroundColor Yellow break } } } if (-not $results) { $actualUri = $null } else { $actualUri = $results.'@odata.nextLink' } $pageIndex++ if (-not $actualUri) { break } elseif ($hasTimeLimit -and (Get-Date) -gt $stopTime) { Write-PSFMessage "Maximum time limit reached for $Name" break } } while ($true) Set-ZtConfig -ExportPath $ExportPath -Property $Name -Value $true } |