tech·nic·al·ly agile

Updating the Command Line Parser

Discover how to enhance your command line applications with a versatile parser in VB.NET. Streamline multiple commands and improve your coding efficiency!

Published on
6 minute read
Image
https://nkdagility.com/resources/ZMPPpD1TIuP

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.

Updating the Command Line Parser  

Using this model I can create a simple command…

 1Imports Hinshlabs.CommandLineParser
 2Imports System.IO
 3Imports System.Collections.ObjectModel
 4Imports System.Net
 5
 6Public Class Demo1Command
 7    Inherits CommandBase(Of Demo1CommandLine)
 8
 9    Private m_PortalLocation As Uri
10
11    Public Overrides ReadOnly Property Description() As String
12        Get
13            Return "demo 1 command demonstrates a sinle nested command"
14        End Get
15    End Property
16
17    Public Overrides ReadOnly Property Name() As String
18        Get
19            Return "Demo1"
20        End Get
21    End Property
22
23    Protected Overrides Function ValidateCommand() As Boolean
24        Return True
25    End Function
26
27    Public Overrides ReadOnly Property Title() As String
28        Get
29            Return "demo 1"
30        End Get
31    End Property
32
33    Public Overrides ReadOnly Property Synopsis() As String
34        Get
35            Return "demo 1 command"
36        End Get
37    End Property
38
39    Public Overrides ReadOnly Property Switches() As ReadOnlyCollection(Of SwitchInfo)
40        Get
41            Return CommandLine.Switches
42        End Get
43    End Property
44
45    Public Overrides ReadOnly Property Qualifications() As String
46        Get
47            Return String.Empty
48        End Get
49    End Property
50
51    Protected Overrides Function RunCommand() As Integer
52        Try
53            CommandOut.Warning("running Demo1")
54            Return -1
55        Catch ex As Exception
56            CommandOut.Error("Failed: {0}", ex.ToString)
57            Return -1
58        End Try
59    End Function
60
61End Class

Or something more substantial:

 1Protected Overrides Function RunCommand() As Integer
 2    Try
 3        Dim x As New Proxies.MyApp.Configuration.ConfigurationServiceClient("BasicHttpBinding_IConfigurationService", m_PortalLocation.ToString)
 4        x.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Delegation
 5        Select Case CommandLine.Action
 6            Case QuiesceAction.Offline
 7                x.QuiesceSource(CommandLine.Source, CommandLine.Message, New TimeSpan(0))
 8            Case QuiesceAction.Online
 9                x.RestoreSource(CommandLine.Source)
10        End Select
11        CommandOut.Info("Source {0} has been made {1}", CommandLine.Source, CommandLine.Action.ToString)
12        Return 0
13
14    Catch ex As EndpointNotFoundException
15        CommandOut.Error("Unable to locate site. Check the value you selected for /Portal:{0}", CommandLine.Portal)
16        Return -1
17    Catch ex As Exception
18        CommandOut.Error("Failed: {0}", ex.ToString)
19        Return -1
20    End Try
21End 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.

 1''' <summary>
 2''' Created a command line object using the Environment.CommandLine information
 3''' </summary>
 4''' <typeparam name="TCommandLine">The concrete type of object to create</typeparam>
 5''' <returns>An instance of the object</returns>
 6''' <remarks></remarks>
 7Public Shared Function CreateCommandLine(Of TCommandLine As {New, CommandLineBase})() As TCommandLine
 8    Return CreateCommandLine(Of TCommandLine)(Environment.CommandLine)
 9End Function
10
11''' <summary>
12''' Created a command line object using the Environment.CommandLine information
13''' </summary>
14''' <typeparam name="TCommandLine">The concrete type of object to create</typeparam>
15''' <param name="CommandLine">The command line arguments to parse</param>
16''' <returns></returns>
17''' <remarks></remarks>
18Public Shared Function CreateCommandLine(Of TCommandLine As {New, CommandLineBase})(ByVal CommandLine As String) As TCommandLine
19    Dim instance As New TCommandLine
20    Dim parser As New Parser(CommandLine, instance)
21    parser.Parse()
22    instance.Parser = parser
23    Return instance
24End Function

This parser then populates the CommandLine object with values from the CommandLine passed in. For example:

 1Imports Hinshlabs.CommandLineParser
 2Imports System.Collections.ObjectModel
 3
 4Public Class Demo3CommandLine
 5    Inherits CommandLineBase
 6
 7    Private m_value1 As String
 8    Private m_value2 As Value2Values = Value2Values.Value1
 9
10    <CommandLineSwitch("Value1", "Adds a string value named value1"), CommandLineAlias("v1")> _
11    Public Property Value1() As String
12        Get
13            Return Me.m_value1
14        End Get
15        Set(ByVal value As String)
16            Me.m_value1 = value
17        End Set
18    End Property
19
20    <CommandLineSwitch("Value2", "Adds and enum value called value2"), CommandLineAlias("v2")> _
21    Public Property Value2() As Value2Values
22        Get
23            Return Me.m_value2
24        End Get
25        Set(ByVal value As Value2Values)
26            Me.m_value2 = value
27        End Set
28    End Property
29
30    Public Enum Value2Values
31        Enum1
32        Enum2
33        Enum3
34    End Enum
35
36End 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:

1New 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:

 1Imports Hinshlabs.CommandLineParser
 2Imports System.IO
 3Imports System.Collections.ObjectModel
 4Imports System.Net
 5
 6Public Class DelegateCommand(Of TCommandLine As {New, CommandLineBase})
 7    Inherits CommandBase(Of TCommandLine)
 8
 9    Private m_Description As String
10    Private m_Title As String
11    Private m_Synopsis As String
12    Private m_Qualifications As String
13    Private m_name As String
14    Private m_RunCommand As Func(Of Integer)
15
16    Public Overrides ReadOnly Property Description() As String
17        Get
18            Return m_Description
19        End Get
20    End Property
21
22    Public Overrides ReadOnly Property Name() As String
23        Get
24            Return m_name
25        End Get
26    End Property
27
28    Protected Overrides Function RunCommand() As Integer
29        Try
30            Return m_RunCommand.Invoke
31        Catch ex As Exception
32            CommandOut.Error("Failed: {0}", ex.ToString)
33            Return -1
34        End Try
35    End Function
36
37    Protected Overrides Function ValidateCommand() As Boolean
38        Return True
39    End Function
40
41    Public Overrides ReadOnly Property Title() As String
42        Get
43            Return m_title
44        End Get
45    End Property
46
47    Public Overrides ReadOnly Property Synopsis() As String
48        Get
49            Return Synopsis
50        End Get
51    End Property
52
53    Public Overrides ReadOnly Property Switches() As ReadOnlyCollection(Of SwitchInfo)
54        Get
55            Return CommandLine.Switches
56        End Get
57    End Property
58
59    Public Overrides ReadOnly Property Qualifications() As String
60        Get
61            Return String.Empty
62        End Get
63    End Property
64
65    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)
66        m_name = name
67        m_RunCommand = runCommand
68        m_Title = title
69        m_Qualifications = qualifications
70        m_Synopsis = synopsis
71        m_Description = description
72    End Sub
73
74End 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…

Updating the Command Line Parser

With the values coming from the relevant places:

Updating the Command Line Parser

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.

Get Command Line Parser v2.0 

Technorati Tags: .NET  CodeProject 

Software Development
Comments

Related blog posts

No related videos found.

Connect with Martin Hinshelwood

If you've made it this far, it's worth connecting with our principal consultant and coach, Martin Hinshelwood, for a 30-minute 'ask me anything' call.

Our Happy Clients​

We partner with businesses across diverse industries, including finance, insurance, healthcare, pharmaceuticals, technology, engineering, transportation, hospitality, entertainment, legal, government, and military sectors.​

Akaditi Logo
Hubtel Ghana Logo
Big Data for Humans Logo
Philips Logo
Flowmaster (a Mentor Graphics Company) Logo
Lockheed Martin Logo
Ericson Logo
Deliotte Logo
Kongsberg Maritime Logo
Trayport Logo
Capita Secure Information Solutions Ltd Logo
Illumina Logo
YearUp.org Logo
Xceptor - Process and Data Automation Logo

NIT A/S

Workday Logo
Microsoft Logo
Milliman Logo
Royal Air Force Logo
Department of Work and Pensions (UK) Logo
Nottingham County Council Logo
New Hampshire Supreme Court Logo
Ghana Police Service Logo
Washington Department of Enterprise Services Logo
Deliotte Logo
Xceptor - Process and Data Automation Logo
MacDonald Humfrey (Automation) Ltd. Logo
Hubtel Ghana Logo

NIT A/S

Higher Education Statistics Agency Logo