Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 131 additions & 92 deletions Class/ParameterParser.vb
Original file line number Diff line number Diff line change
@@ -1,92 +1,131 @@
' -----------------------------------------------------------------------------
' Copyright (c) svtica. All rights reserved.
' File: ParameterParser.vb
' Author: LiteTask contributors
' Date: 2026-04-25
' Purpose: Parse "key=value" parameter strings used by TaskRunner. Supports
' cmd-style quoting (key="value with spaces") while remaining
' backward-compatible with the original whitespace-delimited form.
' -----------------------------------------------------------------------------
Namespace LiteTask
Public Module ParameterParser

''' <summary>
''' Parses a parameter string of the form `key=value [key2=value2 ...]`.
''' Values may be quoted with double quotes to include spaces:
''' key="value with spaces"
''' Tokens without an `=` are skipped, as are `=value` fragments.
''' Last occurrence wins for duplicate keys.
''' </summary>
Public Function Parse(parameters As String) As Dictionary(Of String, String)
Dim result As New Dictionary(Of String, String)
If String.IsNullOrEmpty(parameters) Then Return result

Dim i As Integer = 0
Dim len As Integer = parameters.Length

While i < len
' Skip leading whitespace
While i < len AndAlso Char.IsWhiteSpace(parameters(i))
i += 1
End While
If i >= len Then Exit While

' Read key: up to '=' or whitespace
Dim keyStart As Integer = i
While i < len AndAlso parameters(i) <> "="c AndAlso Not Char.IsWhiteSpace(parameters(i))
i += 1
End While

' No '=' for this token: skip token entirely (preserves original behavior
' where bare words without '=' were dropped).
If i >= len OrElse parameters(i) <> "="c Then
Continue While
End If

Dim key As String = parameters.Substring(keyStart, i - keyStart)
i += 1 ' consume '='

' Empty key (`=value`) is skipped to match original behavior.
If String.IsNullOrEmpty(key) Then
' Advance past the value so we don't reparse it as a new token
SkipValue(parameters, i)
Continue While
End If

Dim value As String = ReadValue(parameters, i)
result(key) = value
End While

Return result
End Function

Private Function ReadValue(s As String, ByRef i As Integer) As String
Dim len As Integer = s.Length
If i >= len Then Return String.Empty

If s(i) = """"c Then
' Quoted value: read until next '"' or end of string.
i += 1
Dim valueStart As Integer = i
While i < len AndAlso s(i) <> """"c
i += 1
End While
Dim value As String = s.Substring(valueStart, i - valueStart)
If i < len Then i += 1 ' consume closing quote
Return value
End If

' Unquoted value: read until whitespace.
Dim unquotedStart As Integer = i
While i < len AndAlso Not Char.IsWhiteSpace(s(i))
i += 1
End While
Return s.Substring(unquotedStart, i - unquotedStart)
End Function

Private Sub SkipValue(s As String, ByRef i As Integer)
ReadValue(s, i)
End Sub

End Module
End Namespace
' -----------------------------------------------------------------------------
' Copyright (c) svtica. All rights reserved.
' File: ParameterParser.vb
' Author: LiteTask contributors
' Date: 2026-04-25
' Purpose: Parse parameter strings used by TaskRunner. Supports three forms
' that can be mixed in the same string:
' key=value
' key="value with spaces"
' -Name Value (PowerShell-style)
' -Name "Value with spaces" (PowerShell-style)
' -Switch (no value -> stored as Nothing)
' -----------------------------------------------------------------------------
Namespace LiteTask
Public Module ParameterParser

''' <summary>
''' Parses a parameter string. Supports both `key=value` and
''' PowerShell-style `-Name Value` / `-Switch` syntax in the same
''' input. Returns an ordered dictionary mapping each parameter name
''' to its value. A switch parameter (no value provided) is stored
''' as Nothing so callers can distinguish it from an empty string.
''' Tokens without an `=` and that don't start with `-Letter` are
''' skipped to preserve the original lenient behavior.
''' Last occurrence wins for duplicate keys.
''' </summary>
Public Function Parse(parameters As String) As Dictionary(Of String, Object)
Dim result As New Dictionary(Of String, Object)(StringComparer.OrdinalIgnoreCase)
If String.IsNullOrEmpty(parameters) Then Return result

Dim i As Integer = 0
Dim len As Integer = parameters.Length

While i < len
' Skip leading whitespace
While i < len AndAlso Char.IsWhiteSpace(parameters(i))
i += 1
End While
If i >= len Then Exit While

' PowerShell-style: -Name [Value] (a dash followed by a letter)
If parameters(i) = "-"c AndAlso i + 1 < len AndAlso IsNameStart(parameters(i + 1)) Then
i += 1 ' consume '-'
Dim keyStart As Integer = i
While i < len AndAlso Not Char.IsWhiteSpace(parameters(i)) AndAlso parameters(i) <> "="c
i += 1
End While
Dim psKey As String = parameters.Substring(keyStart, i - keyStart)

' Allow `-Name=Value` as a convenience
If i < len AndAlso parameters(i) = "="c Then
i += 1
result(psKey) = ReadValue(parameters, i)
Continue While
End If

' Skip whitespace between name and value
Dim valueScan As Integer = i
While valueScan < len AndAlso Char.IsWhiteSpace(parameters(valueScan))
valueScan += 1
End While

' If next non-whitespace is another -Name, this is a switch.
If valueScan >= len OrElse
(parameters(valueScan) = "-"c AndAlso valueScan + 1 < len AndAlso IsNameStart(parameters(valueScan + 1))) Then
result(psKey) = Nothing
' Don't consume the next token; loop will pick it up.
Else
i = valueScan
result(psKey) = ReadValue(parameters, i)
End If
Continue While
End If

' key=value form
Dim keyStartEq As Integer = i
While i < len AndAlso parameters(i) <> "="c AndAlso Not Char.IsWhiteSpace(parameters(i))
i += 1
End While

If i >= len OrElse parameters(i) <> "="c Then
' Bare token without '=': skip to preserve original lenient behavior.
Continue While
End If

Dim key As String = parameters.Substring(keyStartEq, i - keyStartEq)
i += 1 ' consume '='

If String.IsNullOrEmpty(key) Then
SkipValue(parameters, i)
Continue While
End If

result(key) = ReadValue(parameters, i)
End While

Return result
End Function

Private Function IsNameStart(c As Char) As Boolean
Return Char.IsLetter(c) OrElse c = "_"c
End Function

Private Function ReadValue(s As String, ByRef i As Integer) As String
Dim len As Integer = s.Length
If i >= len Then Return String.Empty

If s(i) = """"c Then
i += 1
Dim valueStart As Integer = i
While i < len AndAlso s(i) <> """"c
i += 1
End While
Dim value As String = s.Substring(valueStart, i - valueStart)
If i < len Then i += 1 ' consume closing quote
Return value
End If

Dim unquotedStart As Integer = i
While i < len AndAlso Not Char.IsWhiteSpace(s(i))
i += 1
End While
Return s.Substring(unquotedStart, i - unquotedStart)
End Function

Private Sub SkipValue(s As String, ByRef i As Integer)
ReadValue(s, i)
End Sub

End Module
End Namespace
63 changes: 42 additions & 21 deletions Class/TaskRunner.vb
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,27 @@ Namespace LiteTask
Try
_logger.LogInfo($"Executing Batch task: {taskAction.Name}")

' Trailing args appended to `cmd.exe /c "target.bat" <args>` so the
' script receives them as %1..%* (works for both code paths).
Dim batchArgs As String = If(String.IsNullOrWhiteSpace(taskAction.Parameters),
String.Empty,
" " & taskAction.Parameters.Trim())

If credential IsNot Nothing Then
' Parse UNC paths from batch content
Dim batchContent = File.ReadAllText(taskAction.Target)
Dim uncPaths = ParseUNCPaths(batchContent)
Dim envPassVar = $"BATCH_PASS_{Guid.NewGuid().ToString("N")}"

' Write the augmented script (net use prelude + original body) to a
' temp .bat so we can invoke it as `cmd.exe /c "tempfile" <args>`.
' The previous implementation streamed the script via stdin to
' `cmd /c -`, which cannot receive %1..%* — task parameters were
' silently dropped.
Dim tempDir = Path.Combine(Application.StartupPath, "LiteTaskData", "temp")
Directory.CreateDirectory(tempDir)
Dim tempBatFile = Path.Combine(tempDir, $"batch_{Guid.NewGuid():N}.bat")

Try
' Set up credentials
Using securePass As New SecureString()
Expand Down Expand Up @@ -135,43 +150,42 @@ Namespace LiteTask
modifiedContent.AppendLine($"net use {uncPath} /delete")
Next

' Execute using PsExec
Using ms As New MemoryStream()
Using writer As New StreamWriter(ms)
Await writer.WriteAsync(modifiedContent.ToString())
Await writer.FlushAsync()
ms.Position = 0
Await File.WriteAllTextAsync(tempBatFile, modifiedContent.ToString())

Dim psExecPath = Path.Combine(_toolManager._toolsPath, "PsExec64.exe")
Dim fullCommand = $"{baseArgs} -p ""%{envPassVar}%"" cmd.exe /c -"
Dim psExecPath = Path.Combine(_toolManager._toolsPath, "PsExec64.exe")
Dim fullCommand = $"{baseArgs} -p ""%{envPassVar}%"" cmd.exe /c ""{tempBatFile}""{batchArgs}"

Using process As New Process With {
Using process As New Process With {
.StartInfo = New ProcessStartInfo With {
.FileName = psExecPath,
.Arguments = fullCommand,
.UseShellExecute = False,
.RedirectStandardInput = True,
.RedirectStandardOutput = True,
.RedirectStandardError = True,
.CreateNoWindow = True,
.StandardOutputEncoding = Encoding.UTF8,
.StandardErrorEncoding = Encoding.UTF8
}
}
Return Await RunProcess(process)
End Using
End Using
Return Await RunProcess(process)
End Using

Finally
Environment.SetEnvironmentVariable(envPassVar, Nothing, EnvironmentVariableTarget.Process)
If File.Exists(tempBatFile) Then
Try
File.Delete(tempBatFile)
Catch ex As Exception
_logger.LogWarning($"Failed to delete temp batch file {tempBatFile}: {ex.Message}")
End Try
End If
End Try
Else
' Execute without credentials
Using process As New Process With {
.StartInfo = New ProcessStartInfo With {
.FileName = "cmd.exe",
.Arguments = $"/c ""{taskAction.Target}""",
.Arguments = $"/c ""{taskAction.Target}""{batchArgs}",
.UseShellExecute = False,
.RedirectStandardOutput = True,
.RedirectStandardError = True,
Expand Down Expand Up @@ -535,15 +549,18 @@ Namespace LiteTask
Dim sqlContent = File.ReadAllText(taskAction.Target)
Dim sqlInfo = ExtractSqlInfo(sqlContent)
Dim sqlConfig = _xmlManager.GetSqlConfiguration()
' The task's Parameters field uses the same `key=value` / `-Name value`
' grammar as PowerShell tasks. ParseSqlParameters parses T-SQL EXEC
' syntax inside the .sql file content, not the form field.
Dim parameters = If(Not String.IsNullOrEmpty(taskAction.Parameters),
ParseSqlParameters(taskAction.Parameters),
New Dictionary(Of String, String))
ParameterParser.Parse(taskAction.Parameters),
New Dictionary(Of String, Object)(StringComparer.OrdinalIgnoreCase))

Dim server = If(parameters.ContainsKey("server"), parameters("server"),
Dim server = If(parameters.ContainsKey("server"), CStr(parameters("server")),
If(Not String.IsNullOrEmpty(sqlInfo.ServerName), sqlInfo.ServerName,
sqlConfig("DefaultServer")))

Dim database = If(parameters.ContainsKey("database"), parameters("database"),
Dim database = If(parameters.ContainsKey("database"), CStr(parameters("database")),
If(Not String.IsNullOrEmpty(sqlInfo.DatabaseName), sqlInfo.DatabaseName,
sqlConfig("DefaultDatabase")))

Expand Down Expand Up @@ -659,7 +676,11 @@ Namespace LiteTask
If Not String.IsNullOrEmpty(taskAction.Parameters) Then
_logger.LogInfo($"Adding parameters: {taskAction.Parameters}")
For Each param In ParseParameters(taskAction.Parameters)
powerShell.AddParameter(param.Key, param.Value)
If param.Value Is Nothing Then
powerShell.AddParameter(param.Key)
Else
powerShell.AddParameter(param.Key, param.Value)
End If
Next
End If

Expand Down Expand Up @@ -802,14 +823,14 @@ Namespace LiteTask



Private Function ParseParameters(parameters As String) As Dictionary(Of String, String)
Private Function ParseParameters(parameters As String) As Dictionary(Of String, Object)
Try
Dim result = ParameterParser.Parse(parameters)
_logger.LogInfo($"Parsed {result.Count} parameters successfully")
Return result
Catch ex As Exception
_logger.LogError($"Error parsing parameters: {ex.Message}")
Return New Dictionary(Of String, String)
Return New Dictionary(Of String, Object)
End Try
End Function

Expand Down
Loading
Loading