param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$RemoteUser, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$RemoteHost, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$RemotePort, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$SshExe, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ScpExe ) Add-Type -TypeDefinition @' using System; using System.Runtime.InteropServices; public class ModernFolderPicker { private const uint FOS_PICKFOLDERS = 0x00000020; private const int SIGDN_FILESYSPATH = unchecked((int)0x80028000); public static string Show(string title = "Select Folder") { var dialog = (IFileOpenDialog)new FileOpenDialogClass(); try { uint options; dialog.GetOptions(out options); dialog.SetOptions(options | FOS_PICKFOLDERS); dialog.SetTitle(title); if (dialog.Show(IntPtr.Zero) != 0) return null; // cancelled IShellItem item; dialog.GetResult(out item); string path; item.GetDisplayName(SIGDN_FILESYSPATH, out path); Marshal.ReleaseComObject(item); return path; } finally { Marshal.ReleaseComObject(dialog); } } [ComImport, ClassInterface(ClassInterfaceType.None), Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")] private class FileOpenDialogClass {} [ComImport, Guid("D57C7288-D4AD-4768-BE02-9D969532D960"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IFileOpenDialog { [PreserveSig] int Show(IntPtr hwndOwner); void SetFileTypes(uint cFileTypes, IntPtr rgFilterSpec); void SetFileTypeIndex(uint iFileType); void GetFileTypeIndex(out uint piFileType); void Advise(IntPtr pfde, out uint pdwCookie); void Unadvise(uint dwCookie); void SetOptions(uint fos); void GetOptions(out uint pfos); void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); void SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); void GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); void GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); void SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName); void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); void SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle); void SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText); void SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel); void GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); void AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, int fdap); void SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); void Close(int hr); void SetClientGuid([In] ref Guid guid); void ClearClientData(); void SetFilter(IntPtr pFilter); void GetResults(out IntPtr ppenum); void GetSelectedItems(out IntPtr ppenum); } [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IShellItem { void BindToHandler(IntPtr pbc, [In] ref Guid bhid, [In] ref Guid riid, out IntPtr ppv); void GetParent(out IShellItem ppsi); void GetDisplayName(int sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName); void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs); void Compare(IShellItem psi, uint hint, out int piOrder); } } '@ Add-Type -AssemblyName System.Windows.Forms $remoteRoots = @( '/mnt/nas12/nas2/RUS', '/mnt/da1/foto' ) $encoderDir = Join-Path $PSScriptRoot 'face_encoder_cpu' $outputDir = Join-Path $encoderDir 'output' function ConvertTo-PosixSingleQuoted { param( [Parameter(Mandatory = $true)] [string]$Value ) return "'" + $Value.Replace("'", "'`"'`"'") + "'" } function Normalize-RemoteRelativePath { param( [Parameter(Mandatory = $true)] [string]$InputPath ) $normalized = $InputPath.Trim() $normalized = $normalized -replace '\\', '/' $normalized = $normalized -replace '^/mnt/nas12/nas2/RUS/?', '' $normalized = $normalized -replace '^/mnt/da1/foto/?', '' $normalized = $normalized -replace '/+', '/' $normalized = $normalized.Trim('/') if (-not $normalized) { throw 'Il percorso remoto non puo essere vuoto.' } $segments = $normalized -split '/' foreach ($segment in $segments) { if ([string]::IsNullOrWhiteSpace($segment)) { throw 'Il percorso remoto contiene segmenti vuoti non validi.' } if ($segment -in @('.', '..')) { throw 'Il percorso remoto non puo contenere . o ...' } } return ($segments -join '/') } function Join-RemotePath { param( [Parameter(Mandatory = $true)] [string]$Root, [Parameter(Mandatory = $true)] [string]$RelativePath, [string]$LeafName ) $parts = @($Root.TrimEnd('/')) if ($RelativePath) { $parts += $RelativePath.Trim('/') } if ($LeafName) { $parts += $LeafName } return ($parts -join '/') } function Get-MulticoreSetting { Write-Host '' Write-Host "Seleziona il livello di multicore per l'elaborazione CPU:" Write-Host ' 1 = 1/8 dei core' Write-Host ' 2 = 1/4 dei core' Write-Host ' 3 = 1/2 dei core (predefinito)' Write-Host ' 4 = 3/4 dei core' Write-Host ' 5 = n-2 core' Write-Host '' $multicoreChoice = Read-Host 'Inserisci il livello (1-5) oppure premi Invio per usare il predefinito (3)' if ($multicoreChoice -match '^[1-5]$') { return [int]$multicoreChoice } return -1 } function Show-PklFilePicker { param( [Parameter(Mandatory = $true)] [string]$InitialDirectory ) $dialog = New-Object System.Windows.Forms.OpenFileDialog $dialog.Title = 'Seleziona il file PKL da caricare' $dialog.Filter = 'Pickle files (*.pkl)|*.pkl|Tutti i file (*.*)|*.*' $dialog.CheckFileExists = $true $dialog.Multiselect = $false if (Test-Path -LiteralPath $InitialDirectory) { $dialog.InitialDirectory = (Resolve-Path -LiteralPath $InitialDirectory).Path } if ($dialog.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { return $null } return $dialog.FileName } function Invoke-RemoteShellCommand { param( [Parameter(Mandatory = $true)] [string]$Payload, [switch]$AllocateTty, [switch]$Quiet ) $remoteCommand = 'sh -c ' + (ConvertTo-PosixSingleQuoted $Payload) $sshArgs = [System.Collections.Generic.List[string]]::new() if ($AllocateTty) { $sshArgs.Add('-tt') } $sshArgs.Add('-p') $sshArgs.Add($remotePort) $sshArgs.Add('-o') $sshArgs.Add('PreferredAuthentications=password') $sshArgs.Add('-o') $sshArgs.Add('PubkeyAuthentication=no') $sshArgs.Add('-o') $sshArgs.Add('StrictHostKeyChecking=accept-new') $sshArgs.Add("$remoteUser@$remoteHost") $sshArgs.Add($remoteCommand) if ($Quiet) { & $sshExe @sshArgs | Out-Null } else { & $sshExe @sshArgs } return $LASTEXITCODE } function Test-RemoteFileExists { param( [Parameter(Mandatory = $true)] [string]$RemotePath ) $payload = 'test -e ' + (ConvertTo-PosixSingleQuoted $RemotePath) $exitCode = Invoke-RemoteShellCommand -Payload $payload -Quiet if ($exitCode -eq 0) { return $true } if ($exitCode -eq 1) { return $false } throw "Impossibile verificare l'esistenza del file remoto: $RemotePath" } function Test-RemoteDirectoryExists { param( [Parameter(Mandatory = $true)] [string]$RemotePath ) $payload = 'test -d ' + (ConvertTo-PosixSingleQuoted $RemotePath) $exitCode = Invoke-RemoteShellCommand -Payload $payload -Quiet if ($exitCode -eq 0) { return $true } if ($exitCode -eq 1) { return $false } throw "Impossibile verificare il percorso remoto: $RemotePath" } function Ensure-RemoteDirectory { param( [Parameter(Mandatory = $true)] [string]$RemoteDirectory ) $payload = 'mkdir -p ' + (ConvertTo-PosixSingleQuoted $RemoteDirectory) $exitCode = Invoke-RemoteShellCommand -Payload $payload if ($exitCode -ne 0) { throw "Creazione cartella remota non riuscita: $RemoteDirectory" } } function Upload-FileToRemoteTarget { param( [Parameter(Mandatory = $true)] [string]$LocalFile, [Parameter(Mandatory = $true)] [string]$RemoteFile, [bool]$Overwrite ) $remoteDirectory = Split-Path -Path $RemoteFile -Parent if ([string]::IsNullOrWhiteSpace($remoteDirectory) -or $remoteDirectory -eq '/') { throw 'Il percorso remoto destinazione non e valido.' } if (Test-RemoteDirectoryExists -RemotePath $RemoteFile) { throw "La destinazione remota e una cartella, non un file: $RemoteFile" } Ensure-RemoteDirectory -RemoteDirectory $remoteDirectory if ((-not $Overwrite) -and (Test-RemoteFileExists -RemotePath $RemoteFile)) { Write-Host "Salto upload per file esistente: $RemoteFile" return } $scpArgs = @( '-P', $remotePort, '-o', 'PreferredAuthentications=password', '-o', 'PubkeyAuthentication=no', '-o', 'StrictHostKeyChecking=accept-new', $LocalFile, ("{0}@{1}:{2}" -f $remoteUser, $remoteHost, (ConvertTo-PosixSingleQuoted $RemoteFile)) ) & $scpExe @scpArgs if ($LASTEXITCODE -ne 0) { throw "Caricamento non riuscito verso $RemoteFile" } } function Invoke-FaceEncoding { $multicore = Get-MulticoreSetting $inputPath = [ModernFolderPicker]::Show('Select the folder containing images to encode') if (-not $inputPath) { Write-Host 'Nessuna cartella selezionata. Uscita.' return 0 } $inputFolder = Get-Item -LiteralPath $inputPath -ErrorAction Stop $raceName = $inputFolder.Name $safeRaceName = ($raceName -replace '[<>:"/\\|?*]', ' ').Trim() $safeRaceName = $safeRaceName -replace '\s+', '_' if (-not $safeRaceName) { $safeRaceName = 'race' } $timestamp = Get-Date -Format 'yyyyMMdd_HHmmss' $outputFile = Join-Path $outputDir ("face_encodings_{0}_{1}.pkl" -f $timestamp, $safeRaceName) $logFile = Join-Path $outputDir ("encoder_log_{0}_{1}.txt" -f $timestamp, $safeRaceName) New-Item -ItemType Directory -Path $outputDir -Force | Out-Null $encoderExe = Join-Path $encoderDir 'face_encoder_cpu.exe' $encoderArgs = [System.Collections.Generic.List[string]]::new() $encoderArgs.Add('-i') $encoderArgs.Add($inputFolder.FullName) $encoderArgs.Add('-r') $encoderArgs.Add('-o') $encoderArgs.Add($outputFile) $encoderArgs.Add('-l') $encoderArgs.Add($logFile) if ($multicore -ge 0) { $encoderArgs.Add('-m') $encoderArgs.Add([string]$multicore) } Write-Host "Input folder : $($inputFolder.FullName)" Write-Host "Race name : $raceName" Write-Host "Multicore : $(if ($multicore -ge 0) { $multicore } else { 'default (3)' })" Write-Host "Output file : $outputFile" Write-Host "Log file : $logFile" Write-Host "Command : $encoderExe $encoderArgs" Write-Host '' & $encoderExe @encoderArgs $encoderExitCode = $LASTEXITCODE if ($encoderExitCode -eq 0 -and (Test-Path -LiteralPath $outputFile)) { Start-Process explorer.exe "/select,`"$outputFile`"" } elseif ($encoderExitCode -eq 0) { Write-Warning "Encoding completed, but the expected output file was not found: $outputFile" } return $encoderExitCode } function Invoke-PklUpload { New-Item -ItemType Directory -Path $outputDir -Force | Out-Null $selectedFile = Show-PklFilePicker -InitialDirectory $outputDir if (-not $selectedFile) { Write-Host 'Nessun file selezionato. Uscita.' return 0 } $fileInfo = Get-Item -LiteralPath $selectedFile -ErrorAction Stop $relativeInput = Read-Host 'Inserisci il percorso relativo sotto RUS/foto (esempio: 2026/04.APRILE/ISOLOTTO)' $relativePath = Normalize-RemoteRelativePath -InputPath $relativeInput $targets = foreach ($root in $remoteRoots) { [pscustomobject]@{ Root = $root RemoteFile = Join-RemotePath -Root $root -RelativePath $relativePath -LeafName $fileInfo.Name } } Write-Host '' Write-Host 'Destinazioni remote:' foreach ($target in $targets) { Write-Host (" - {0}" -f $target.RemoteFile) } Write-Host '' $existingTargets = @($targets | Where-Object { Test-RemoteFileExists -RemotePath $_.RemoteFile }) $overwrite = $false if ($existingTargets.Count -gt 0) { Write-Host 'Il file esiste gia nelle seguenti destinazioni:' foreach ($target in $existingTargets) { Write-Host (" - {0}" -f $target.RemoteFile) } $choice = Read-Host 'Vuoi sovrascrivere i file esistenti? (s/N)' if ($choice -match '^(s|si|y|yes)$') { $overwrite = $true } } foreach ($target in $targets) { Upload-FileToRemoteTarget -LocalFile $fileInfo.FullName -RemoteFile $target.RemoteFile -Overwrite:$overwrite } Write-Host '' Write-Host 'Caricamento completato.' return 0 } Write-Host '' Write-Host 'Seleziona un''opzione:' Write-Host ' 1. Elaborazione riconoscimento facciale' Write-Host ' 2. Caricamento' Write-Host '' $mode = $null while ($mode -notin @('1', '2')) { $mode = Read-Host 'Inserisci 1 o 2' } try { if ($mode -eq '1') { $exitCode = Invoke-FaceEncoding } else { $exitCode = Invoke-PklUpload } exit $exitCode } catch { Write-Error $_ exit 1 }