Public/ConvertFrom-sqmRobocopyCommand.ps1

function ConvertFrom-sqmRobocopyCommand {
    <#
    .SYNOPSIS
        Zerlegt eine vorhandene robocopy-Befehlszeile oder Auftragsdatei (.RCJ) in den Parameter-Hashtable.
    .DESCRIPTION
        Das Ergebnis passt direkt zu New-sqmRobocopyCommand, Test-sqmRobocopyParameter und der GUI
        (Schaltflaeche "Befehl importieren"). Unbekannte Optionen werden als Warnung gemeldet und
        uebersprungen. Ein vorangestelltes "robocopy" / "robocopy.exe" und Zeilenfortsetzungen
        (^ in cmd, ` in PowerShell) werden ignoriert.
    .EXAMPLE
        ConvertFrom-sqmRobocopyCommand 'robocopy "D:\Daten\" \\nas\backup *.docx /MIR /R:3 /W:5 /XD "~tmp" /LOG:C:\Logs\daten.log'
    .EXAMPLE
        ConvertFrom-sqmRobocopyCommand -JobFile C:\Jobs\Nacht.RCJ
    #>

    [CmdletBinding(DefaultParameterSetName = 'Line')]
    param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'Line')][string]$CommandLine,
        [Parameter(Mandatory, ParameterSetName = 'Job')][string]$JobFile
    )
    $lookup = Get-srgParameterLookup
    $result = @{}
    $files = New-Object System.Collections.Generic.List[string]

    $setOption = {
        param([string]$Token)
        $t = $Token.TrimStart('/', '-')
        $name = $t; $value = $null
        $i = $t.IndexOf(':')
        if ($i -gt 0) { $name = $t.Substring(0, $i); $value = $t.Substring($i + 1).Trim('"') }
        $name = $name -replace '\[.*$', ''      # "/SPARSE[:J/N]" in RCJ-Dateien
        if ($name -eq 'IF') { return 'IF' }
        $def = $lookup[$name]
        if (-not $def) { Write-Warning "Unbekannte Option '$Token' uebersprungen."; return $null }
        $name = $def.Name
        switch ($def.Type) {
            'Switch' { $result[$name] = $true }
            'List' { if (-not $result.ContainsKey($name)) { $result[$name] = @() }; return $name }
            'Opt' { $result[$name] = if ($null -ne $value -and $value -ne '') { $value } else { $true } }
            'Hours' {
                # robocopy speichert in .RCJ "100-500" statt "0100-0500"
                if ($value -match '^(\d{1,4})-(\d{1,4})$') { $value = '{0:0000}-{1:0000}' -f [int]$Matches[1], [int]$Matches[2] }
                $result[$name] = $value
            }
            default { $result[$name] = $value }
        }
        $null
    }

    if ($PSCmdlet.ParameterSetName -eq 'Job') {
        # Format: TAB/OPTION<TAB>:: Kommentar, Listeneintraege mit zwei TABs eingerueckt
        $listName = $null
        $path = (Resolve-Path -LiteralPath $JobFile).ProviderPath
        foreach ($raw in (Read-srgJobFileLines $path)) {
            $line = ($raw -split '\t::', 2)[0]
            if ($line.TrimStart().StartsWith('::') -or -not $line.Trim()) { continue }
            if ($line -match '^\t/\S') {
                # Werte mit Leerzeichen (Pfade) stehen ungequotet bis zum Kommentar
                $full = $line.Trim()
                if ($full -match '^/(SD|DD):(.*)$') {
                    $result[$(if ($Matches[1] -eq 'SD') { 'Source' } else { 'Destination' })] = ConvertTo-srgDirectory $Matches[2].Trim()
                    $listName = $null; continue
                }
                $listName = & $setOption $full
            } elseif ($line -match '^\t\t(.+)$' -and $listName) {
                $item = $Matches[1].Trim()
                if ($listName -eq 'IF') { $files.Add($item) } else { $result[$listName] += $item }
            }
        }
    } else {
        $text = $CommandLine -replace '\s*[\^`]\s*\r?\n\s*', ' ' -replace '\r?\n', ' '
        # Tokenizer im Stil von CommandLineToArgvW (Anfuehrungszeichen mitten im Token erlaubt)
        $tokens = New-Object System.Collections.Generic.List[string]
        $sb = New-Object System.Text.StringBuilder
        $inQ = $false; $any = $false
        # Hochkomma nur am Tokenanfang als PowerShell-Quote werten ('' = Hochkomma) - mitten im Token
        # bleibt es ein normales Zeichen (cmd: C:\O'Brien)
        $inS = $false
        $chars = $text.ToCharArray()
        for ($i = 0; $i -lt $chars.Length; $i++) {
            $ch = $chars[$i]
            if ($inS) {
                if ($ch -eq "'") {
                    if ($i + 1 -lt $chars.Length -and $chars[$i + 1] -eq "'") { [void]$sb.Append("'"); $i++ } else { $inS = $false }
                } else { [void]$sb.Append($ch) }
                continue
            }
            if ($ch -eq "'" -and -not $inQ -and -not $any) { $inS = $true; $any = $true; continue }
            if ($ch -eq '"') { $inQ = -not $inQ; $any = $true; continue }
            if (-not $inQ -and [char]::IsWhiteSpace($ch)) {
                if ($any) { $tokens.Add($sb.ToString()); [void]$sb.Clear(); $any = $false }
                continue
            }
            [void]$sb.Append($ch); $any = $true
        }
        if ($any) { $tokens.Add($sb.ToString()) }
        # PowerShell-Aufruf: & robocopy.exe 'a' 'b'
        $tokens = @($tokens | Where-Object { $_ -ne '&' })
        if ($tokens.Count -and $tokens[0] -match '(^|\\)robocopy(\.exe)?$') { $tokens = @($tokens | Select-Object -Skip 1) }

        $positional = 0
        $listName = $null
        foreach ($t in $tokens) {
            if ($t -match '^/') {
                $listName = & $setOption $t
                continue
            }
            if ($listName) {
                if ($listName -eq 'IF') { $files.Add($t) } else { $result[$listName] += $t }
                continue
            }
            switch ($positional) {
                0 { $result['Source'] = ConvertTo-srgDirectory $t }
                1 { $result['Destination'] = ConvertTo-srgDirectory $t }
                default { $files.Add($t) }
            }
            $positional++
        }
    }

    # robocopy schreibt bei /E auch /S in die Auftragsdatei
    if ($result['E'] -and $result['S']) { [void]$result.Remove('S') }
    $files = @($files | Where-Object { $_ -ne '*.*' })
    if ($files.Count) { $result['Files'] = @($files) }
    $result
}