173 lines
5.9 KiB
PowerShell
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.
|
|
#>
|