[CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [string[]]$Path, [Parameter()] [string]$ExportJsonPath ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' if ([System.Environment]::OSVersion.Platform -ne [System.PlatformID]::Win32NT) { throw 'Dieses Demo-Script kann nur unter Windows ausgefuehrt werden.' } $nativeMethods = @" using System; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; public static class NtfsNativeMethods { [StructLayout(LayoutKind.Sequential)] public struct BY_HANDLE_FILE_INFORMATION { public uint FileAttributes; public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime; public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime; public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime; public uint VolumeSerialNumber; public uint FileSizeHigh; public uint FileSizeLow; public uint NumberOfLinks; public uint FileIndexHigh; public uint FileIndexLow; } public const uint FILE_SHARE_READ = 0x00000001; public const uint FILE_SHARE_WRITE = 0x00000002; public const uint FILE_SHARE_DELETE = 0x00000004; public const uint OPEN_EXISTING = 3; public const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern SafeFileHandle CreateFile( string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetFileInformationByHandle( SafeFileHandle hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation); } "@ if (-not ('NtfsNativeMethods' -as [type])) { Add-Type -TypeDefinition $nativeMethods } function Get-Md5Hex { param( [Parameter(Mandatory = $true)] [string]$Text ) $md5 = [System.Security.Cryptography.MD5]::Create() try { $bytes = [System.Text.Encoding]::UTF8.GetBytes($Text) $hash = $md5.ComputeHash($bytes) return ([System.BitConverter]::ToString($hash)).Replace('-', '').ToLowerInvariant() } finally { $md5.Dispose() } } function Get-NtfsStableFolderId { param( [Parameter(Mandatory = $true)] [string]$LiteralPath ) $item = Get-Item -LiteralPath $LiteralPath -Force if (-not $item.PSIsContainer) { throw "Der Pfad '$LiteralPath' ist kein Ordner." } $handle = [NtfsNativeMethods]::CreateFile( $item.FullName, 0, [NtfsNativeMethods]::FILE_SHARE_READ -bor [NtfsNativeMethods]::FILE_SHARE_WRITE -bor [NtfsNativeMethods]::FILE_SHARE_DELETE, [IntPtr]::Zero, [NtfsNativeMethods]::OPEN_EXISTING, [NtfsNativeMethods]::FILE_FLAG_BACKUP_SEMANTICS, [IntPtr]::Zero) if ($handle.IsInvalid) { $lastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error() throw "CreateFile fehlgeschlagen fuer '$($item.FullName)' mit Win32-Fehler $lastError." } try { $info = New-Object NtfsNativeMethods+BY_HANDLE_FILE_INFORMATION if (-not [NtfsNativeMethods]::GetFileInformationByHandle($handle, [ref]$info)) { $lastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error() throw "GetFileInformationByHandle fehlgeschlagen fuer '$($item.FullName)' mit Win32-Fehler $lastError." } $fileIndex = ([UInt64]$info.FileIndexHigh -shl 32) -bor [UInt64]$info.FileIndexLow $stableAnchor = ('{0:x8}:{1:x16}' -f $info.VolumeSerialNumber, $fileIndex) [pscustomobject]@{ Path = $item.FullName Name = $item.Name VolumeSerialNumberHex = ('0x{0:x8}' -f $info.VolumeSerialNumber) FileIndexHex = ('0x{0:x16}' -f $fileIndex) StableAnchor = $stableAnchor StableDataAreaId = (Get-Md5Hex -Text $stableAnchor) CreatedUtc = $item.CreationTimeUtc.ToString('o') LastWriteUtc = $item.LastWriteTimeUtc.ToString('o') } } finally { $handle.Dispose() } } $results = foreach ($currentPath in $Path) { Get-NtfsStableFolderId -LiteralPath $currentPath } if ($PSBoundParameters.ContainsKey('ExportJsonPath')) { $exportDirectory = Split-Path -Parent $ExportJsonPath if (-not [string]::IsNullOrWhiteSpace($exportDirectory) -and -not (Test-Path -LiteralPath $exportDirectory)) { New-Item -ItemType Directory -Path $exportDirectory | Out-Null } $results | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $ExportJsonPath -Encoding UTF8 } $results <# Demo fuer Rename/Move auf demselben NTFS-Volume: 1. Vorher ausfuehren: .\Get-NtfsStableFolderId.ps1 -Path 'C:\Daten\TeamA' -ExportJsonPath '.\before.json' 2. Ordner umbenennen oder innerhalb desselben Volumes verschieben. 3. Nachher erneut ausfuehren: .\Get-NtfsStableFolderId.ps1 -Path 'C:\Daten\Fachbereich\TeamA_Renamed' -ExportJsonPath '.\after.json' 4. Vergleichen: - `Path` ist anders - `StableAnchor` bleibt gleich - `StableDataAreaId` bleibt gleich Wichtige Einschraenkung: - Rename und Move auf demselben NTFS-Volume behalten die Identitaet. - Copy-and-delete oder Move ueber ein anderes Volume erzeugen in der Regel eine neue Identitaet. - Auf manchen Remote-/DFS-Szenarien kann der Dateiserver diese Metadaten nicht verlaesslich liefern. #>