Private/srgCommand.ps1

<#
    Bausteine fuer den robocopy-Aufruf: Normalisierung der Werte, Argumentliste, Quoting fuer
    cmd / Batch / PowerShell. Kein GUI-Code - wird von New-sqmRobocopyCommand und der GUI benutzt.
#>


function ConvertTo-srgBool($Value) {
    if ($Value -is [bool]) { return $Value }
    if ($null -eq $Value) { return $false }
    "$Value".Trim() -in 'Y', 'J', '1', 'true', '$true', 'on', 'x'
}

function ConvertTo-srgList($Value) {
    # Arrays bleiben Arrays; Zeichenketten werden an ; und Zeilenumbruechen getrennt
    $items = foreach ($v in @($Value)) {
        if ($null -eq $v) { continue }
        foreach ($s in ("$v" -split '[;\r\n]')) { $s.Trim().Trim('"') }
    }
    @($items | Where-Object { $_ })
}

function ConvertTo-srgDirectory([string]$Path) {
    <#
        Abschliessenden Backslash entfernen: "C:\Ziel\" wuerde sonst beim Quoten zu C:\Ziel" (der
        Backslash maskiert das Anfuehrungszeichen) - der klassische robocopy-Fehler "Ungueltiger
        Parameter". Laufwerkswurzeln (D:\) bleiben, sie brauchen nie Anfuehrungszeichen.
    #>

    $p = $Path.Trim().Trim('"')
    if ($p -match '^[A-Za-z]:\\*$') { return $p.Substring(0, 2) + '\' }
    $p.TrimEnd('\')
}

function Get-srgActiveParameter([hashtable]$Parameter) {
    <# Liefert die gesetzten Optionen normalisiert: Name -> $true | 'Wert' | string[] #>
    $out = [ordered]@{}
    foreach ($d in Get-srgParameterDefinition) {
        $key = @($Parameter.Keys | Where-Object { $_ -eq $d.Name })[0]
        if ($null -eq $key) { continue }
        $v = $Parameter[$key]
        switch ($d.Type) {
            'Switch' { if (ConvertTo-srgBool $v) { $out[$d.Name] = $true } }
            'Opt' {
                if ($v -is [bool]) { if ($v) { $out[$d.Name] = $true } }
                elseif ($null -ne $v -and "$v".Trim() -ne '') {
                    $s = "$v".Trim()
                    $out[$d.Name] = if ($s -in '*', '$true', 'true', 'on') { $true } else { $s.ToUpper() }
                }
            }
            'List' { $l = ConvertTo-srgList $v; if ($l.Count) { $out[$d.Name] = $l } }
            'Flags' { if ($null -ne $v -and "$v".Trim() -ne '') { $out[$d.Name] = "$v".Trim().ToUpper() } }
            'Size' { if ($null -ne $v -and "$v".Trim() -ne '') { $out[$d.Name] = "$v".Trim().ToUpper() } }
            default { if ($null -ne $v -and "$v".Trim() -ne '') { $out[$d.Name] = "$v".Trim() } }
        }
    }
    $out
}

function Get-srgArgumentList {
    <#
        Robocopy-Argumente ohne Quoting, in fester Reihenfolge: Quelle, Ziel, Dateien, Optionen.
        Optionen mit Wert als ein Token ('/LOG:C:\x y.log'); das Quoting erledigt Format-srg*Arg.
    #>

    param([hashtable]$Parameter, [string[]]$Extra = @())
    $list = New-Object System.Collections.Generic.List[string]
    $src = [string]$Parameter['Source']
    $dst = [string]$Parameter['Destination']
    if ($src.Trim()) { $list.Add((ConvertTo-srgDirectory $src)) }
    if ($dst.Trim()) { $list.Add((ConvertTo-srgDirectory $dst)) }
    foreach ($f in (ConvertTo-srgList $Parameter['Files'])) { $list.Add($f) }

    $opts = Get-srgActiveParameter $Parameter
    foreach ($name in $opts.Keys) {
        $v = $opts[$name]
        $d = (Get-srgParameterLookup)[$name]
        if ($v -is [bool]) { $list.Add("/$name"); continue }
        if ($d.Type -eq 'List') {
            $list.Add("/$name")
            foreach ($i in $v) { $list.Add($(if ($name -eq 'XD') { ConvertTo-srgDirectory $i } else { $i })) }
            continue
        }
        $list.Add("/${name}:$v")
    }
    foreach ($e in $Extra) { if ($e -and -not ($list -contains $e)) { $list.Add($e) } }
    , $list.ToArray()
}

function Format-srgCmdArg([string]$Token, [switch]$Batch) {
    <# Quoting fuer cmd.exe / CreateProcess: nur wenn noetig, bei /OPT:Wert nur den Wert. #>
    $prefix = ''
    $value = $Token
    if ($Token -match '^(/[A-Za-z0-9+]+:)(.+)$') { $prefix = $Matches[1]; $value = $Matches[2] }
    if ($value -eq '' -or $value -match '[\s&()\[\]{}^=;!'',`|<>"]') {
        # abschliessende Backslashes vor dem schliessenden Anfuehrungszeichen verdoppeln
        $m = [regex]::Match($value, '\\+$')
        if ($m.Success) { $value = $value + $m.Value }
        $value = '"' + $value.Replace('"', '\"') + '"'
    }
    $r = $prefix + $value
    if ($Batch) { $r = $r.Replace('%', '%%') }
    $r
}

function Format-srgPsArg([string]$Token) {
    "'" + $Token.Replace("'", "''") + "'"
}

function Join-srgCmdLine([string[]]$Arguments, [switch]$Batch) {
    (@($Arguments | ForEach-Object { Format-srgCmdArg $_ -Batch:$Batch })) -join ' '
}

function Get-srgExitCodeText([int]$ExitCode) {
    if ($ExitCode -eq 0) { return 'nichts zu tun - Quelle und Ziel sind bereits synchron' }
    if ($ExitCode -lt 0) { return 'robocopy wurde abgebrochen' }
    $parts = foreach ($b in $script:srgExitBits) { if ($ExitCode -band $b.Bit) { $b.Text } }
    $parts -join ', '
}

function Read-srgJobFileLines([string]$Path) {
    <#
        robocopy schreibt .RCJ-Dateien je nach Umgebung in der Konsolen-Codepage (OEM 850, nach
        chcp 65001 UTF-8) oder in der ANSI-Codepage (1252) - ohne Kennung. Mit BOM (/UNICODE) ist es
        eindeutig, sonst gewinnt die Dekodierung, deren Quell-/Zielpfade tatsaechlich existieren.
    #>

    $bytes = [System.IO.File]::ReadAllBytes($Path)
    if (($bytes.Length -ge 2 -and (($bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) -or ($bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF))) -or
        ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF)) {
        return [System.IO.File]::ReadAllLines($Path)
    }
    $ti = [System.Globalization.CultureInfo]::CurrentCulture.TextInfo
    $candidates = New-Object System.Collections.Generic.List[string]
    try { $candidates.Add((New-Object System.Text.UTF8Encoding($false, $true)).GetString($bytes)) } catch { }
    foreach ($cp in $ti.OEMCodePage, $ti.ANSICodePage) {
        try { $candidates.Add([System.Text.Encoding]::GetEncoding($cp).GetString($bytes)) } catch { }
    }
    Initialize-srgHelper
    $pick = $candidates[0]
    if (@($candidates | Select-Object -Unique).Count -gt 1) {
        :outer foreach ($text in $candidates) {
            foreach ($m in [regex]::Matches($text, '(?m)^\t/(?:SD|DD):([^\t\r\n]+)')) {
                $d = $m.Groups[1].Value.Trim()
                if ($d -and [Srg.PathProbe]::DirectoryExists($d, 1500) -eq 1) { $pick = $text; break outer }
            }
        }
    }
    $pick -split '\r?\n'
}