I had previously created a Command Line Parser from Ray Hayes CodeProject article Automatic Command Line Parsing in C#. I had adapted it to VB.NET and upgraded it to .NET 3.5 but I recently ran into the problem with wanting a single command prompt application to handle multiple processes and multiple parameters. This would allow you to group all of a particular tasks commands into a single application. With the advent of Power Shell, this format is increasingly less relevant, but with the proliferation of Power Shell many people still prefer to use the good old command line.
So, staring from the original Command Line Parser v1.0 code, I wanted to be able to add multiple commands, or even nest commands. The result is a nice simple commanding architecture conducive to creating multiple commands.
Using this model, I can create a simple command…
Imports Hinshlabs.CommandLineParser
Imports System.IO
Imports System.Collections.ObjectModel
Imports System.Net
Public Class Demo1Command
Inherits CommandBase(Of Demo1CommandLine)
Private m_PortalLocation As Uri
Public Overrides ReadOnly Property Description() As String
Get
Return "demo 1 command demonstrates a single nested command"
End Get
End Property
Public Overrides ReadOnly Property Name() As String
Get
Return "Demo1"
End Get
End Property
Protected Overrides Function ValidateCommand() As Boolean
Return True
End Function
Public Overrides ReadOnly Property Title() As String
Get
Return "demo 1"
End Get
End Property
Public Overrides ReadOnly Property Synopsis() As String
Get
Return "demo 1 command"
End Get
End Property
Public Overrides ReadOnly Property Switches() As ReadOnlyCollection(Of SwitchInfo)
Get
Return CommandLine.Switches
End Get
End Property
Public Overrides ReadOnly Property Qualifications() As String
Get
Return String.Empty
End Get
End Property
Protected Overrides Function RunCommand() As Integer
Try
CommandOut.Warning("running Demo1")
Return -1
Catch ex As Exception
CommandOut.Error("Failed: {0}", ex.ToString)
Return -1
End Try
End Function
End Class
Or something more substantial:
Protected Overrides Function RunCommand() As Integer
Try
Dim x As New Proxies.MyApp.Configuration.ConfigurationServiceClient_
("BasicHttpBinding_IConfigurationService", m_PortalLocation.ToString)
x.ClientCredentials.Windows.AllowedImpersonationLevel = _
System.Security.Principal.TokenImpersonationLevel.Delegation
Select Case CommandLine.Action
Case QuiesceAction.Offline
x.QuiesceSource(CommandLine.Source, CommandLine.Message, New TimeSpan(0))
Case QuiesceAction.Online
x.RestoreSource(CommandLine.Source)
End Select
CommandOut.Info("Source {0} has been made {1}", _
CommandLine.Source, CommandLine.Action.ToString)
Return 0
Catch ex As EndpointNotFoundException
CommandOut.Error("Unable to locate site. _
Check the value you selected for /Portal:{0}", CommandLine.Portal)
Return -1
Catch ex As Exception
CommandOut.Error("Failed: {0}", ex.ToString)
Return -1
End Try
End Function
If you are wondering where the variables come from, you can see form Demo1Command that a generic type of Demo1CommandLine is passed in. The application creates an instance of this which wraps the Ray Hayes parser to provide the values from Environment.CommandLine used on the shared methods on the CommandLineBase class.
Public Shared Function CreateCommandLine(Of TCommandLine As _
{New, CommandLineBase})() As TCommandLine
Return CreateCommandLine(Of TCommandLine)(Environment.CommandLine)
End Function
Public Shared Function CreateCommandLine(Of TCommandLine As _
{New, CommandLineBase})(ByVal CommandLine As String) As TCommandLine
Dim instance As New TCommandLine
Dim parser As New Parser(CommandLine, instance)
parser.Parse()
instance.Parser = parser
Return instance
End Function
This parser then populates the CommandLine
object with values from the CommandLine
passed in. For example:
Imports Hinshlabs.CommandLineParser
Imports System.Collections.ObjectModel
Public Class Demo3CommandLine
Inherits CommandLineBase
Private m_value1 As String
Private m_value2 As Value2Values = Value2Values.Value1
<CommandLineSwitch("Value1", "Adds a string value named value1"), _
CommandLineAlias("v1")> _
Public Property Value1() As String
Get
Return Me.m_value1
End Get
Set(ByVal value As String)
Me.m_value1 = value
End Set
End Property
<CommandLineSwitch("Value2", "Adds and enum value called value2"), _
CommandLineAlias("v2")> _
Public Property Value2() As Value2Values
Get
Return Me.m_value2
End Get
Set(ByVal value As Value2Values)
Me.m_value2 = value
End Set
End Property
Public Enum Value2Values
Enum1
Enum2
Enum3
End Enum
End Class
Would allow you to call [consoleApp] Demo3 /v1:”Any value you like” /Value2:Enum3 and have the correct values populated at runtime.
I have also updated with a DelegateCommand
class that would allow you to call a function in the right format from anywhere:
New DelegateCommand(Of Demo3CommandLine)("Demo2", AddressOf OnDemo2Run, "demo 2", _
"no additional information", "demo 2 command", _
"This command shows how to delegate the run method using the delegate command")
The delegate command is really easy in .NET 3.5 with the only change being the addition of a variable declared as a Func
in the class:
Imports Hinshlabs.CommandLineParser
Imports System.IO
Imports System.Collections.ObjectModel
Imports System.Net
Public Class DelegateCommand(Of TCommandLine As {New, CommandLineBase})
Inherits CommandBase(Of TCommandLine)
Private m_Description As String
Private m_Title As String
Private m_Synopsis As String
Private m_Qualifications As String
Private m_name As String
Private m_RunCommand As Func(Of Integer)
Public Overrides ReadOnly Property Description() As String
Get
Return m_Description
End Get
End Property
Public Overrides ReadOnly Property Name() As String
Get
Return m_name
End Get
End Property
Protected Overrides Function RunCommand() As Integer
Try
Return m_RunCommand.Invoke
Catch ex As Exception
CommandOut.Error("Failed: {0}", ex.ToString)
Return -1
End Try
End Function
Protected Overrides Function ValidateCommand() As Boolean
Return True
End Function
Public Overrides ReadOnly Property Title() As String
Get
Return m_title
End Get
End Property
Public Overrides ReadOnly Property Synopsis() As String
Get
Return Synopsis
End Get
End Property
Public Overrides ReadOnly Property Switches() As ReadOnlyCollection(Of SwitchInfo)
Get
Return CommandLine.Switches
End Get
End Property
Public Overrides ReadOnly Property Qualifications() As String
Get
Return String.Empty
End Get
End Property
Public Sub New(ByVal name As String, ByVal runCommand As Func(Of Integer), _
ByVal title As String, ByVal qualifications As String, _
ByVal synopsis As String, ByVal description As String)
m_name = name
m_RunCommand = runCommand
m_Title = title
m_Qualifications = qualifications
m_Synopsis = synopsis
m_Description = description
End Sub
End Class
If you were wondering why there are so many properties, it is to allow the help to be created automatically. For example, if you call the help function on Demo3Command
you will get…
With the values coming from the relevant places:
It will also support inherited CommandLine
objects to minimize duplication.
I hope that if you are building command line apps that you will have a look, just remember not to spend too much effort on cmd, when Power Shell is much more suitable and accessible to non developers.
Download the code here.