Files
LIAM/Sonstiges/Get-NtfsStableFolderId.ps1
2026-03-17 14:46:54 +01:00

173 lines
5.9 KiB
PowerShell

[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.
#>