Private/Test-BlastRadius.ps1
|
function Test-BlastRadius { <# .SYNOPSIS Checks the blast radius of a proposed action on a target server. .DESCRIPTION Evaluates the impact of a remediation action by checking if the target is a Domain Controller, SQL Server, Exchange server, file server, or print server. Checks connected user count and dependent services to determine risk level. Returns a blast radius assessment with level, reason, and affected systems. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$ComputerName, [Parameter()] [string]$Action = 'Unknown action', [Parameter()] [object]$RunbookStep ) $level = 'Low' $reasons = [System.Collections.Generic.List[string]]::new() $affectedSystems = [System.Collections.Generic.List[string]]::new() $requiresApproval = $false # Check blast_radius hint from the runbook step if ($RunbookStep -and $RunbookStep.blast_radius) { switch ($RunbookStep.blast_radius) { 'single_service' { $level = 'Low' } 'single_server' { $level = 'Medium'; $requiresApproval = $true } 'multi_server' { $level = 'High'; $requiresApproval = $true } 'domain_wide' { $level = 'Critical'; $requiresApproval = $true } } } # Check if the step explicitly requires approval if ($RunbookStep -and $RunbookStep.requires_approval -eq $true) { $requiresApproval = $true } # Attempt to detect server role via Active Directory $isDC = $false $serverRoles = [System.Collections.Generic.List[string]]::new() try { if (Get-Command Get-ADComputer -ErrorAction SilentlyContinue) { $adComputer = Get-ADComputer -Identity $ComputerName -Properties OperatingSystem, ServicePrincipalName, Description -ErrorAction Stop # Check for Domain Controller $spns = $adComputer.ServicePrincipalName if ($spns -and ($spns | Where-Object { $_ -like '*GC/*' -or $_ -like '*E3514235-4B06*' })) { $isDC = $true $serverRoles.Add('DomainController') $level = 'Critical' $reasons.Add("$ComputerName is a Domain Controller") $requiresApproval = $true } # Check for Exchange if ($spns -and ($spns | Where-Object { $_ -like '*exchangeMDB/*' -or $_ -like '*SMTP/*' })) { $serverRoles.Add('Exchange') if ($level -ne 'Critical') { $level = 'High' } $reasons.Add("$ComputerName is an Exchange server") $requiresApproval = $true } # Check for SQL Server if ($spns -and ($spns | Where-Object { $_ -like '*MSSQLSvc/*' })) { $serverRoles.Add('SQLServer') if ($level -ne 'Critical') { $level = 'High' } $reasons.Add("$ComputerName is a SQL Server") $requiresApproval = $true } } } catch { Write-Verbose "AD lookup not available or failed for ${ComputerName}: $_" } # Check server role via CIM/WMI try { $os = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $ComputerName -ErrorAction Stop # Domain Controller check via OS caption if ($os.Caption -match 'Domain Controller') { if (-not $isDC) { $isDC = $true $serverRoles.Add('DomainController') $level = 'Critical' $reasons.Add("$ComputerName is a Domain Controller (detected via OS)") $requiresApproval = $true } } # Check for server features via services $services = Get-CimInstance -ClassName Win32_Service -ComputerName $ComputerName -ErrorAction Stop # SQL Server $sqlServices = $services | Where-Object { $_.Name -like 'MSSQL*' -or $_.Name -eq 'SQLAgent' } if ($sqlServices -and 'SQLServer' -notin $serverRoles) { $serverRoles.Add('SQLServer') if ($level -notin @('Critical', 'High')) { $level = 'High' } $reasons.Add("$ComputerName runs SQL Server services") $requiresApproval = $true } # Exchange $exchangeServices = $services | Where-Object { $_.Name -like 'MSExchange*' } if ($exchangeServices -and 'Exchange' -notin $serverRoles) { $serverRoles.Add('Exchange') if ($level -notin @('Critical', 'High')) { $level = 'High' } $reasons.Add("$ComputerName runs Exchange services") $requiresApproval = $true } # File Server (lanmanserver with shares) $fileShares = Get-CimInstance -ClassName Win32_Share -ComputerName $ComputerName -ErrorAction SilentlyContinue | Where-Object { $_.Name -notmatch '^\w\$' -and $_.Name -ne 'IPC$' -and $_.Name -ne 'ADMIN$' } if ($fileShares -and $fileShares.Count -gt 2) { $serverRoles.Add('FileServer') if ($level -eq 'Low') { $level = 'Medium' } $reasons.Add("$ComputerName is a file server with $($fileShares.Count) shares") $affectedSystems.AddRange([string[]]($fileShares | ForEach-Object { "Share: $($_.Name)" })) } # Print Server $printServices = $services | Where-Object { $_.Name -eq 'Spooler' -and $_.State -eq 'Running' } $printers = Get-CimInstance -ClassName Win32_Printer -ComputerName $ComputerName -ErrorAction SilentlyContinue | Where-Object { $_.Shared -eq $true } if ($printServices -and $printers -and $printers.Count -gt 0) { $serverRoles.Add('PrintServer') if ($level -eq 'Low') { $level = 'Medium' } $reasons.Add("$ComputerName is a print server with $($printers.Count) shared printers") } # Check connected users $sessions = Get-CimInstance -ClassName Win32_LogonSession -ComputerName $ComputerName -ErrorAction SilentlyContinue | Where-Object { $_.LogonType -in @(2, 10, 11) } if ($sessions -and $sessions.Count -gt 5) { if ($level -eq 'Low') { $level = 'Medium' } $reasons.Add("$($sessions.Count) active user sessions on $ComputerName") $requiresApproval = $true } } catch { Write-Verbose "CIM/WMI checks not available for ${ComputerName}: $_" if ($reasons.Count -eq 0) { $reasons.Add("Unable to verify server role for $ComputerName (remote access unavailable)") } } # If no specific concerns were found if ($reasons.Count -eq 0) { $reasons.Add("No elevated risk detected for action on $ComputerName") } [PSCustomObject]@{ ComputerName = $ComputerName Action = $Action Level = $level Reasons = $reasons.ToArray() ServerRoles = $serverRoles.ToArray() AffectedSystems = $affectedSystems.ToArray() RequiresApproval = $requiresApproval AssessedAt = (Get-Date).ToString('o') } } |