bin/Public/Get-sqmOrphanedFiles.ps1
|
<# .SYNOPSIS Findet MDF/LDF/NDF-Datenbankdateien, die keiner Datenbank zugeordnet sind. .DESCRIPTION Liest alle registrierten Datenbankdateien aus sys.master_files und vergleicht diese mit den tatsaechlich vorhandenen Dateien in den Verzeichnissen. Dateien die auf dem Dateisystem existieren, aber nicht in sys.master_files eingetragen sind, werden als verwaist (Orphaned) gemeldet. Hinweis: Die Verzeichnisse werden von der PowerShell-Session aus durchsucht. Bei Remote-Instanzen muessen die Pfade als UNC-Pfade erreichbar sein oder SearchPath explizit als UNC-Pfad angegeben werden. .PARAMETER SqlInstance SQL Server-Instanz. Standard: lokaler Computername. .PARAMETER SqlCredential PSCredential fuer die Verbindung. .PARAMETER SearchPath Verzeichnisse, die durchsucht werden sollen. Standard: alle eindeutigen Verzeichnisse aus sys.master_files + SQL Server Standardpfade. .PARAMETER FileExtension Dateierweiterungen die gesucht werden. Standard: .mdf, .ldf, .ndf .PARAMETER Recurse Unterverzeichnisse rekursiv durchsuchen. .PARAMETER EnableException Ausnahmen sofort ausloesen. .EXAMPLE Get-sqmOrphanedFiles -SqlInstance "SQL01" .EXAMPLE Get-sqmOrphanedFiles -SqlInstance "SQL01" -SearchPath "D:\SQLData","E:\SQLLog" -Recurse .NOTES Erfordert: dbatools, Invoke-sqmLogging Benoetigt VIEW SERVER STATE und Dateisystemzugriff auf die Datenbankverzeichnisse. #> function Get-sqmOrphanedFiles { [CmdletBinding()] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $false)] [string]$SqlInstance, [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential]$SqlCredential, [Parameter(Mandatory = $false)] [string[]]$SearchPath, [Parameter(Mandatory = $false)] [string[]]$FileExtension = @('.mdf', '.ldf', '.ndf'), [Parameter(Mandatory = $false)] [switch]$Recurse, [Parameter(Mandatory = $false)] [switch]$EnableException ) begin { $functionName = $MyInvocation.MyCommand.Name if (-not $PSBoundParameters.ContainsKey('SqlInstance') -or [string]::IsNullOrWhiteSpace($SqlInstance)) { $SqlInstance = $env:COMPUTERNAME } if (-not $script:dbatoolsAvailable) { $errMsg = "dbatools-Modul nicht gefunden." Invoke-sqmLogging -Message $errMsg -FunctionName $functionName -Level "ERROR" throw $errMsg } # Warnung bei Remote-Instanz ohne expliziten SearchPath $isRemote = ($SqlInstance -notlike "*$env:COMPUTERNAME*" -and $SqlInstance -ne 'localhost' -and $SqlInstance -ne '.') if ($isRemote -and -not $SearchPath) { Write-Warning "Remote-Instanz erkannt. Dateisystemzugriff erfolgt von diesem Computer aus. Gib SearchPath als UNC-Pfad an falls die Pfade nicht direkt erreichbar sind." } Invoke-sqmLogging -Message "Starte $functionName auf $SqlInstance" -FunctionName $functionName -Level "INFO" } process { try { $connParams = @{ SqlInstance = $SqlInstance } if ($SqlCredential) { $connParams['SqlCredential'] = $SqlCredential } # Alle registrierten Dateien aus sys.master_files $registeredSql = @" SELECT DB_NAME(database_id) AS DatabaseName, name AS LogicalName, physical_name AS PhysicalName, type_desc AS FileType, size * 8 / 1024 AS SizeMB, state_desc AS State FROM sys.master_files ORDER BY database_id, file_id "@ $registeredFiles = Invoke-DbaQuery @connParams -Database master -Query $registeredSql -ErrorAction Stop # Normalisierte Pfad-Menge fuer schnellen Vergleich $registeredPaths = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($f in $registeredFiles) { [void]$registeredPaths.Add($f.PhysicalName) } # Suchpfade aufbauen $dirsToSearch = [System.Collections.Generic.List[string]]::new() if ($SearchPath -and $SearchPath.Count -gt 0) { foreach ($p in $SearchPath) { $dirsToSearch.Add($p) } } else { # Eindeutige Verzeichnisse aus sys.master_files $registeredFiles | ForEach-Object { $dir = [System.IO.Path]::GetDirectoryName($_.PhysicalName) if ($dir -and -not $dirsToSearch.Contains($dir)) { $dirsToSearch.Add($dir) } } # SQL Server Standardpfade aus Registry $regSql = @" DECLARE @DataDir NVARCHAR(512), @LogDir NVARCHAR(512), @BackupDir NVARCHAR(512); EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer', N'DefaultData', @DataDir OUTPUT; EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer', N'DefaultLog', @LogDir OUTPUT; SELECT @DataDir AS DataDir, @LogDir AS LogDir; "@ $regResult = Invoke-DbaQuery @connParams -Database master -Query $regSql -ErrorAction SilentlyContinue if ($regResult) { if ($regResult.DataDir -and -not $dirsToSearch.Contains($regResult.DataDir)) { $dirsToSearch.Add($regResult.DataDir) } if ($regResult.LogDir -and -not $dirsToSearch.Contains($regResult.LogDir)) { $dirsToSearch.Add($regResult.LogDir) } } } Invoke-sqmLogging -Message "$($dirsToSearch.Count) Verzeichnis(se) werden durchsucht." -FunctionName $functionName -Level "INFO" # Dateisystem durchsuchen $orphaned = [System.Collections.Generic.List[PSCustomObject]]::new() $scanned = 0 foreach ($dir in $dirsToSearch) { if (-not (Test-Path $dir -ErrorAction SilentlyContinue)) { Invoke-sqmLogging -Message "Verzeichnis nicht erreichbar: $dir" -FunctionName $functionName -Level "WARNING" continue } $getParams = @{ Path = $dir; ErrorAction = 'SilentlyContinue' } if ($Recurse) { $getParams['Recurse'] = $true } $files = Get-ChildItem @getParams | Where-Object { -not $_.PSIsContainer -and ($FileExtension -contains $_.Extension.ToLower()) } foreach ($file in $files) { $scanned++ if (-not $registeredPaths.Contains($file.FullName)) { $orphaned.Add([PSCustomObject]@{ FilePath = $file.FullName FileName = $file.Name Extension = $file.Extension SizeMB = [math]::Round($file.Length / 1MB, 2) LastModified = $file.LastWriteTime Directory = $file.DirectoryName Status = 'Orphaned' }) } } } Invoke-sqmLogging -Message "${functionName}: $scanned Dateien gescannt, $($orphaned.Count) verwaiste Dateien gefunden." -FunctionName $functionName -Level "INFO" if ($orphaned.Count -eq 0) { Invoke-sqmLogging -Message "Keine verwaisten Datenbankdateien gefunden." -FunctionName $functionName -Level "INFO" } return [PSCustomObject]@{ SqlInstance = $SqlInstance ScannedFiles = $scanned RegisteredFiles = $registeredFiles.Count OrphanedFiles = $orphaned.Count TotalOrphanedMB = [math]::Round(($orphaned | Measure-Object SizeMB -Sum).Sum, 2) Files = $orphaned.ToArray() } } catch { $errMsg = "Fehler in $functionName : $($_.Exception.Message)" Invoke-sqmLogging -Message $errMsg -FunctionName $functionName -Level "ERROR" if ($EnableException) { throw } Write-Error $errMsg } } } |