Get-Snapshot.ps1
|
# Syskit Discovery - Script Runner # This script runs Syskit Discovery: M365 Security Assessment via the M365Snapshot module # Usage examples: # .\Get-Snapshot.ps1 -TenantId "contoso.onmicrosoft.com" -ClientId "existing-app-id" # .\Get-Snapshot.ps1 -TenantId "contoso.onmicrosoft.com" # .\Get-Snapshot.ps1 -TenantId "contoso.onmicrosoft.com" -RegisterClient # .\Get-Snapshot.ps1 -TenantId "contoso.onmicrosoft.com" -Scopes Applications,Exchange [CmdletBinding(PositionalBinding=$false)] param( [Parameter(Mandatory=$true)] [string]$TenantId, [Parameter(Mandatory=$false)] [string]$ClientId, [Parameter(Mandatory=$false)] [switch]$RegisterClient, [Parameter(Mandatory=$false)] [switch]$GenerateHTMLReport = $true, [Parameter(Mandatory=$false)] [string]$ReportPath, [Parameter(Mandatory=$false)] [ValidateRange(1, [int]::MaxValue)] [int]$MaxUsers = 10000, [Parameter(Mandatory=$false)] [ValidateRange(1, [int]::MaxValue)] [int]$MaxGroups = 10000, [Parameter(Mandatory=$false)] [ValidateRange(1, [int]::MaxValue)] [int]$MaxSites = 1000, [Parameter(Mandatory=$false)] [switch]$LoadAllUsers, [Parameter(Mandatory=$false)] [switch]$LoadAllGroups, [Parameter(Mandatory=$false)] [switch]$LoadAllSites, [Parameter(Mandatory=$false)] [ValidateSet('All', 'Entra', 'Exchange', 'Applications', 'SharePoint')] [string[]]$Scopes = @('All'), [Parameter(Mandatory=$false)] [ValidateRange(1, [int]::MaxValue)] [int]$MaxAppRegistrations = 1000, [Parameter(Mandatory=$false)] [switch]$LoadAllAppRegistrations, [Parameter(Mandatory=$false)] [ValidateRange(1, [int]::MaxValue)] [int]$MaxSharedMailboxes = 100, [Parameter(Mandatory=$false)] [ValidateRange(1, [int]::MaxValue)] [int]$MaxSecurityDistributionGroups = 100, [Parameter(Mandatory=$false)] [string[]]$ConfidentialSensitivityLabels = @(), [Parameter(Mandatory=$false)] [string]$ConfidentialSensitivityLabelsFile, [Parameter(Mandatory=$false)] [switch]$NoRelaunch ) $script:AppName = "Syskit Discovery" $script:AppStorageName = ($script:AppName -replace "[^a-zA-Z0-9]", "").Trim() if ([string]::IsNullOrWhiteSpace($script:AppStorageName)) { $script:AppStorageName = "App" } $global:SyskitDiscoveryTranscriptActive = $false function Start-RunTranscript { param( [string]$StorageName, [switch]$Quiet ) try { $logRoot = Join-Path $env:LOCALAPPDATA $StorageName $logDir = Join-Path $logRoot "Logs" if (-not (Test-Path $logDir)) { New-Item -Path $logDir -ItemType Directory -Force | Out-Null } $timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss") $logPath = Join-Path $logDir ("Snapshot_{0}_{1}.log" -f $timestamp, $PID) Start-Transcript -Path $logPath -Force | Out-Null $global:SyskitDiscoveryTranscriptActive = $true if (-not $Quiet) { Write-Host "[INFO] Run log: $logPath" -ForegroundColor DarkGray } } catch { if (-not $Quiet) { Write-Host "[INFO] Unable to start run transcript logging: $($_.Exception.Message)" -ForegroundColor DarkGray } } } if (-not (Get-EventSubscriber -ErrorAction SilentlyContinue | Where-Object { $_.SourceIdentifier -eq 'SyskitDiscovery.TranscriptStop' })) { Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action { if ($global:SyskitDiscoveryTranscriptActive) { try { Stop-Transcript | Out-Null } catch {} $global:SyskitDiscoveryTranscriptActive = $false } } -SupportEvent -ErrorAction SilentlyContinue | Out-Null } if ($NoRelaunch) { Start-RunTranscript -StorageName $script:AppStorageName } if ([string]::IsNullOrWhiteSpace($ReportPath)) { $ReportPath = "$($script:AppStorageName)_Report.html" } if (-not [string]::IsNullOrWhiteSpace($ConfidentialSensitivityLabelsFile) -and (Test-Path $ConfidentialSensitivityLabelsFile)) { try { $labelsFromFile = Get-Content -Path $ConfidentialSensitivityLabelsFile -Raw | ConvertFrom-Json if ($labelsFromFile -is [System.Array]) { $ConfidentialSensitivityLabels = @($labelsFromFile | ForEach-Object { [string]$_ }) } elseif ($null -ne $labelsFromFile) { $ConfidentialSensitivityLabels = @([string]$labelsFromFile) } } catch { Write-Host "WARNING: Failed to load ConfidentialSensitivityLabels from file '$ConfidentialSensitivityLabelsFile'." -ForegroundColor Yellow } finally { try { Remove-Item -Path $ConfidentialSensitivityLabelsFile -Force -ErrorAction SilentlyContinue } catch { } } } if (-not $NoRelaunch) { $pwshCmd = Get-Command -Name pwsh -ErrorAction SilentlyContinue if ($pwshCmd) { Write-Host "Starting clean PowerShell session for dependency isolation..." -ForegroundColor DarkGray $childArgs = @( "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $PSCommandPath, "-TenantId", $TenantId, "-NoRelaunch" ) if (-not [string]::IsNullOrWhiteSpace($ClientId)) { $childArgs += @("-ClientId", $ClientId) } if ($RegisterClient) { $childArgs += "-RegisterClient" } $childArgs += "-GenerateHTMLReport:$GenerateHTMLReport" if ($PSBoundParameters.ContainsKey("ReportPath") -and -not [string]::IsNullOrWhiteSpace($ReportPath)) { $childArgs += @("-ReportPath", $ReportPath) } if ($PSBoundParameters.ContainsKey("MaxUsers")) { $childArgs += @("-MaxUsers", $MaxUsers) } if ($PSBoundParameters.ContainsKey("MaxGroups")) { $childArgs += @("-MaxGroups", $MaxGroups) } if ($PSBoundParameters.ContainsKey("MaxSites")) { $childArgs += @("-MaxSites", $MaxSites) } if ($LoadAllUsers) { $childArgs += "-LoadAllUsers" } if ($LoadAllGroups) { $childArgs += "-LoadAllGroups" } if ($LoadAllSites) { $childArgs += "-LoadAllSites" } if ($PSBoundParameters.ContainsKey("Scopes")) { foreach ($scopeName in @($Scopes)) { $childArgs += @("-Scopes", $scopeName) } } if ($PSBoundParameters.ContainsKey("MaxAppRegistrations")) { $childArgs += @("-MaxAppRegistrations", $MaxAppRegistrations) } if ($LoadAllAppRegistrations) { $childArgs += "-LoadAllAppRegistrations" } if ($PSBoundParameters.ContainsKey("MaxSharedMailboxes")) { $childArgs += @("-MaxSharedMailboxes", $MaxSharedMailboxes) } if ($PSBoundParameters.ContainsKey("MaxSecurityDistributionGroups")) { $childArgs += @("-MaxSecurityDistributionGroups", $MaxSecurityDistributionGroups) } if ($ConfidentialSensitivityLabels.Count -gt 0) { $nonEmptyLabels = @($ConfidentialSensitivityLabels | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) }) if ($nonEmptyLabels.Count -gt 0) { $labelsFile = Join-Path ([System.IO.Path]::GetTempPath()) ("{0}_{1}.json" -f $script:AppStorageName, ([guid]::NewGuid().ToString("N"))) $nonEmptyLabels | ConvertTo-Json -Depth 3 | Set-Content -Path $labelsFile -Encoding UTF8 $childArgs += @("-ConfidentialSensitivityLabelsFile", $labelsFile) } } & $pwshCmd.Source @childArgs exit $LASTEXITCODE } Start-RunTranscript -StorageName $script:AppStorageName } # Import the module $modulePath = Join-Path $PSScriptRoot "Syskit.Discovery.psd1" if (-not (Test-Path $modulePath)) { Write-Host "ERROR: $($script:AppName) module manifest not found." -ForegroundColor Red Write-Host "Expected: $modulePath" -ForegroundColor Yellow Write-Host "Please ensure Syskit.Discovery.psd1 and M365Snapshot.psm1 are in the same directory." -ForegroundColor Yellow exit 1 } try { Import-Module $modulePath -Force -ErrorAction Stop } catch { Write-Host "ERROR: Failed to import module from '$modulePath'." -ForegroundColor Red Write-Host "Details: $($_.Exception.Message)" -ForegroundColor Yellow Write-Host "Ensure prerequisite modules are installed: MSAL.PS, PnP.PowerShell, Microsoft.Graph.Authentication, Microsoft.Graph.Applications, and Microsoft.Graph.Identity.SignIns." -ForegroundColor Yellow exit 1 } # Call the module function $snapshotParams = @{ TenantId = $TenantId ClientId = $ClientId RegisterClient = $RegisterClient Scopes = $Scopes MaxUsers = $MaxUsers MaxGroups = $MaxGroups MaxSites = $MaxSites MaxAppRegistrations = $MaxAppRegistrations MaxSharedMailboxes = $MaxSharedMailboxes MaxSecurityDistributionGroups = $MaxSecurityDistributionGroups GenerateHTMLReport = $GenerateHTMLReport } if ($ConfidentialSensitivityLabels.Count -gt 0) { $snapshotParams["ConfidentialSensitivityLabels"] = $ConfidentialSensitivityLabels } if ($LoadAllUsers) { $snapshotParams["LoadAllUsers"] = $true } if ($LoadAllGroups) { $snapshotParams["LoadAllGroups"] = $true } if ($LoadAllSites) { $snapshotParams["LoadAllSites"] = $true } if ($LoadAllAppRegistrations) { $snapshotParams["LoadAllAppRegistrations"] = $true } if ($GenerateHTMLReport -and $PSBoundParameters.ContainsKey('ReportPath')) { $snapshotParams["ReportPath"] = $ReportPath } Get-M365Snapshot @snapshotParams |