Drive created by SUBST (Windows 10) Not Reporting Full Path to QueryDosDevice() - vb.net

EDIT - LOOKS LIKE I FOUND THE PROBLEM
Sorry. Apparently it was all my fault. See my self-written answer below for details.
I'm working on a test for a SUBST-ed drive that's included as a part of a larger method that tests a string to ensure that it's (at least, possibly) a valid path. After a bit of validation, the "encapsulating" method converts the string to a UNC or absolute path, depending on the path that's passed in, to return elsewhere.
A couple of examples:
For drive U:\ mapped to \\SERVERNAME\Share, using the \HKCU\Network\ key in the Windows Registry to find network drive mappings on the local computer, U:\PublicFolder\SomeFile.txt becomes \\SERVERNAME\Share\PublicFolder\SomeFile.txt
Alternately, C:\SomeFolder\SomeFile.txt is left unchanged because (as determined within the method) it's an absolute path to a local, physical drive.
So far, most of this appears to be working well and as expected, but I'm encountering an issue with regards to drives created by the SUBST command in Windows 10 (at least - I haven't run any tests under another OS at this time because I don't have another available to me right now).
To be honest, I don't have much experience with the SUBST command and don't use it very often, so, at first, I was having trouble even getting the drive to show up correctly in Windows. After reading through a discussion on the Microsoft Community page (Windows 10 issue "Subst" command doesn't work), I was finally able to get the drive set up "properly" (don't use an elevated command prompt, BTW), but the code I'm using to test for a SUBST-ed drive - converted to VB.NET from this answer - was still not resolving the full path correctly.
Here's the converted code I'm using (I intend to do some "tweaking" later once I have everything working, but this is the current state):
<DllImport("kernel32.dll", SetLastError:=True)>
Private Shared Function QueryDosDevice(ByVal lpDeviceName As String, ByVal lpTargetPath As System.Text.StringBuilder, ByVal ucchMax As Integer) As UInteger
End Function
Private Shared Function IsSubstPath(ByVal pathToTest As String, <Out> ByRef realPath As String) As Boolean
Dim PathInformation As System.Text.StringBuilder = New System.Text.StringBuilder(260)
Dim DriveLetter As String = Nothing
Dim WinApiResult As UInteger = 0
realPath = Nothing
Try
' Get the drive letter of the path
DriveLetter = IO.Path.GetPathRoot(pathToTest).Replace("\\", "")
Catch ex As ArgumentException
Return False
End Try
WinApiResult = QueryDosDevice(DriveLetter, PathInformation, 260)
If WinApiResult = 0 Then
Dim LastWinError As Integer = Marshal.GetLastWin32Error()
Return False
End If
' If the drive is SUBST'ed, the result will be in the format of "\??\C:\realPath\".
If PathInformation.ToString().StartsWith("\??\") Then
Dim RealRoot As String = PathInformation.ToString().Remove(0, 4)
RealRoot += If(PathInformation.ToString().EndsWith("\"), "", "\")
realPath = IO.Path.Combine(RealRoot, pathToTest.Replace(IO.Path.GetPathRoot(pathToTest), ""))
Return True
End If
realPath = pathToTest
Return False
End Function
Which I call like this for a drive I created using SUBST H: D:\substtest:
Dim TestFile As New IO.FileInfo("H:\0984\CPI.TXT")
Dim SubstPath As String = String.Empty
Dim FullPath As String = String.Empty
If IsSubstPath(FullPath, SubstPath) Then
FullPath = SubstPath
End If
My expectation is that the IsSubstPath() method should return D:\substtest\0984\CPI.TXT via the realPath variable. Executing SUBST (without additional parameters) correctly showed the mapping in the command prompt (H:\: => D:\substtest). Checking the TestFile object while debugging shows that it's Exists() property returns True, so the file system does, apparently, know that it's there.
At this point, every time I execute the code, the QueryDosDevice() method call returns a value of 0, although I get varying results from the Marshal.GetLastWin32Error() call as I continue to try to get this working.
My first attempt after getting the SUBST-ed drive "properly" set up on my machine resulted in the Marshal.GetLastWin32Error() returning error code 1008 - ERROR_NO_TOKEN ("An attempt was made to reference a token that does not exist").
Further reading in the linked MS community thread indicated that running SUBST twice - once in a normal command prompt, and again in an elevated command prompt - should make the drive available to either a regular logged on user as well as any elevated user action. I re-ran SUBST in an elevated command prompt and tried again using the same testing code as above. This time, Marshal.GetLastWin32Error() returned error code 6 - ERROR_INVALID_HANDLE ("The handle is invalid.").
Thinking this particular operation might be dependent on the file/path actually existing on the system (as opposed to the .NET IO.FileInfo or IO.DirectoryInfo objects), I manually created the specific subfolder and file to represent what I was testing for in my code (H:\0984\CPI.TXT) and tried it once more (again, using the same code as above):
Once again, the QueryDosDevice() failed to correctly parse the real path (returned 0), but this time the Marshal.GetLastWin32Error() method returned a value of 0 - ERROR_SUCCESS ("The operation completed successfully."). Thinking that, perhaps there was some "flaw" in the code that might unintentionally be skipping a step or something, I checked the PathInformation variable - the Text.StringBuilder object that holds the results of the QueryDosDevice() - in break mode but, alas it's also empty.
NOTE: I also tried using a directory instead of a file, but H:\0984\ resulted in a Marshal.GetLastWin32Error() return value of 0 while H:\0984 resulted in a value of 6. Based on the previous testing, this all makes sense but it nonetheless results in an empty PathInformation variable (failure).
Reading all around the Interwebz, it seems many people are experiencing a variety of issues with SUBST-ed drives under Windows 10, so I'm left wondering at this point if that's the reason for these unexpected results. Has anyone else encountered these issues and, if so, have you been able to resolve them in code?
In case it matters (as I suppose it certainly might), here are some additional details:
I'm using Visual Studio 2017 CE and my project is compiling under .NET Framework 4.7.2
The physical drive to which I'm creating the SUBST path is a RAID 1 pair of drives formatted with NTFS and has plenty of space (178 GB).
NOTE: I have also tried creating a SUBST-ed path to my OS drive (C:\) for the testing, but this gets the same results as above.
If I've left anything out, or if you require further clarification, please let me know in the comments and I'll update the question as needed.

DOUBLE/TRIPLE/QUADRUPLE CHECK CONVERTED CODE
Looks like it all comes down to a single line of code to which I wasn't paying close enough attention (line 14 in the code block above):
DriveLetter = IO.Path.GetPathRoot(PathToTest).Replace("\\", "")
The problem here is that the Path.GetPathRoot() method returns the drive letter in the format H:\ - there's only one backslash (\), so the .Replace() method didn't find anything to replace and was passing an "invalid" parameter value (H:\ instead of H:). The QueryDosDevice() method will apparently fail if the trailing backslash is there, so I made a quick code edit:
DriveLetter = IO.Path.GetPathRoot(PathToTest).Replace("\", "") ' <--Replace ANY/ALL backslashes
I again tested with my SUBST H: D:\substtest drive with an existing file/directory structure as above. This time, the QueryDosDevice() method returned a value of 19 and correctly parsed the real path as D:\substtest\0984\CPI.TXT.
Then, I deleted the subfolder/file I had created for testing and tried again. Again, it correctly returned the real path as D:\substtest\0984\CPI.TXT. So, apparently it all comes down to me overlooking a "typo" introduced during my conversion of the code from C# to VB.NET. My apologies. The full, corrected version of the converted code
<DllImport("kernel32.dll", SetLastError:=True)>
Private Shared Function QueryDosDevice(ByVal lpDeviceName As String, ByVal lpTargetPath As System.Text.StringBuilder, ByVal ucchMax As Integer) As UInteger
End Function
Private Shared Function IsSubstPath(ByVal pathToTest As String, <Out> ByRef realPath As String) As Boolean
Dim PathInformation As System.Text.StringBuilder = New System.Text.StringBuilder(260)
Dim DriveLetter As String = Nothing
Dim WinApiResult As UInteger = 0
realPath = Nothing
Try
' Get the drive letter of the path without the trailing backslash
DriveLetter = IO.Path.GetPathRoot(pathToTest).Replace("\", "")
Catch ex As ArgumentException
Return False
End Try
WinApiResult = QueryDosDevice(DriveLetter, PathInformation, 260)
If WinApiResult = 0 Then
Dim LastWinError As Integer = Marshal.GetLastWin32Error()
Return False
End If
' If the drive is SUBST'ed, the result will be in the format of "\??\C:\realPath\".
If PathInformation.ToString().StartsWith("\??\") Then
Dim RealRoot As String = PathInformation.ToString().Remove(0, 4)
RealRoot += If(PathInformation.ToString().EndsWith("\"), "", "\")
realPath = IO.Path.Combine(RealRoot, pathToTest.Replace(IO.Path.GetPathRoot(pathToTest), ""))
Return True
End If
realPath = pathToTest
Return False
End Function
As I said in the question, I intend to do some "tweaking" of this method, but this does work (now).

Related

VB.NET GetProcessesByName method is missing

I'm building a program in VB whose purpose is to run in the background and automatically update other programs I've created. To do this, it has to check if those programs are still running and, if they are, wait for them to close.
Unfortunately, the program won't compile. The exception states: GetProcessesByName is not a member of String. (The string it refers to is the Process parameter shown in the code below.)
I can't understand why this is happening, because this method has always worked without problems. I'm using Visual Studio 2015. For your reference, here's the code block:
Private Function CheckIfRunning(Process As String) As Boolean
Dim MyProcess() As Process
MyProcess = Process.GetProcessesByName("ProcessName")
If MyProcess.Count > 0 Then
Return True
Else
Return False
End If
End Function
Try using System.Diagnostics.Process.GetProcessesByName("ProcessName")
Since you've declared Process as a string parameter, Process.GetProcessesByName refers to the string instead of the System.Diagnostics method. Alternatively, you can use a different name for the string parameter.

How to use Directory.EnumerateFiles

msdn (https://msdn.microsoft.com/en-us/library/dd383458(v=vs.110).aspx) says:
The EnumerateFiles and GetFiles methods differ as follows: When you use EnumerateFiles, you can start enumerating the collection of names before the whole collection is returned; when you use GetFiles, you must wait for the whole array of names to be returned before you can access the array. Therefore, when you are working with many files and directories, EnumerateFiles can be more efficient.
How can I start using the collection before the whole collection is returned?
The following code gives an elapsed time of more than 3 minutes for a directory with around 45000 files
Dim TIme1, TIme2 As String
TIme1 = TimeString
Dim DirFiles As Generic.List(Of String) = New Generic.List(Of String)(Directory.EnumerateFiles(SourceDirectory))
Dim NumberOfFiles As Integer
NumberOfFiles = DirFiles.Count()
TIme2 = TimeString
MsgBox("Begin time " & TIme1 & "There are " & NumberOfFiles & " Photos in the Directory ." & SourceDirectory & "End Time " & TIme2)
Can I already use entries in Dirfiles before the collection is entirely read? How?
I used to be a professional programmer before Microsoft launched Windows. My experience with windows programming is minimal.
While you can't make good use of count of files returned by EnumerateFiles, you can start working with individual files in the collection without any delay with For Each loop etc. which don't need the count of elements for its working.
So for example you can do:
Dim FileCount As Integer
Dim files = Directory.EnumerateFiles(srcDir)
For Each file in files
'Do something with this file
' e.g.
TextBox1.AppendText(file & vbCrLf)
FileCount += 1
Next
MsgBox ( FileCount.ToString & " files processed.")
So you see how it can be used?
[NB: freehand typed code..might contain typos. It is only meant to explain the concept.]
EnumerateFiles allows you to start processing files before all the files have been found. It appears that you want to know the number of files. You can't know that until all the files have been found, so EnumerateFiles doesn't help you in this case.
The signature for GetFiles is Directory.GetFiles(path As String) As String(). For it to return you results it must hit the hard drive and build the entire array first. If there are 45,000 files then it must build an array of 45,000 elements before it can give you a result.
The signature for EnumerateFiles is Directory.EnumerateFiles(path As String) As IEnumerable(Of String). In this case it doesn't need to hit the hard drive at all to give you a response. So you should be able to get a result almost instantly regardless of the number of files.
Take this test code:
Dim sw = Stopwatch.StartNew()
Dim files = Directory.GetFiles("C:\Windows\System32")
sw.Stop()
Console.WriteLine(sw.Elapsed.TotalMilliseconds)
I get a result of about 6.5 milliseconds to return the files.
But if I change GetFiles to EnumerateFiles I get a result back in 0.07 milliseconds. It's nearly 100 times slower to call GetFiles for this folder!
This is because EnumerateFiles return an IEnumerable<string>. The interface for IEnumerable(Of T) is:
Public Interface IEnumerable(Of Out T)
Inherits IEnumerable
Function GetEnumerator() As IEnumerator(Of T)
End Interface
Whenever we call foreach or .Count() or .ToArray() on an enumerable under the hood we are calling GetEnumerator() which in turn returns another object of type IEnumerator(Of T) with this signature:
Public Interface IEnumerator(Of Out T)
Inherits IDisposable
Inherits IEnumerator
ReadOnly Property Current As T
Function MoveNext() As Boolean
Sub Reset()
End Interface
It's this enumerator that actually does the hard work of returning all of the files. As soon as the first call to MoveNext is made the first file name is immediately available in Current. Then MoveNext is called in a loop until it returns a false and you then know the loop is over. Meanwhile you can collect all of the files from the Current property.
So, in your code, if you were performing some action over each and every file returned then EnumerateFiles would be the way to go.
But since you are doing New Generic.List(Of String)(Directory.EnumerateFiles(SourceDirectory)) you are forcing the iteration of the entire enumerable immediately. Any advantage of using EnumerateFiles is immediately lost.
GetFiles method will materialize the entire list of files that are in a directory. The preferred method to call now is Directory.EnumerateFiles as it will stream the files back (through a yield-like mechanism) as the underlying call to the OS yields the results back.
Solutions using the GetFiles/GetDirectories are kind of slow since the objects need to be created. Using the enumeration on the other hand doesnt do this, it doesn't create any temporary objects.
Either way in the end theres still iteration happening...
Example file count...
Directory.EnumerateFiles(directory, filetype, SearchOption.AllDirectories).Count()
I now use the following before enumeratefiles is started
Public Function FileCount(PathName As String) As Long
Dim fso As Scripting.FileSystemObject
Dim fld As Scripting.Folder
fso = CreateObject("Scripting.FileSystemObject")
If fso.FolderExists(PathName) Then
fld = fso.GetFolder(PathName)
FileCount = fld.Files.Count
End If
End Function
This needs Microsoft Scripting Runtime (set a reference to the VB script run-time library in your Project)

Visual Basic.NET - Add two numbers (I/O from file)

Following code should sum two numbers from file "input.txt" and write the sum to "output.txt". Compilation is succesfull, but "output.txt" is still empty after running program. What am I doing wrong?
Imports System.IO
Public Class test
Public Shared Sub Main()
Dim scan as StreamReader = new StreamReader("input.txt")
Dim writer as StreamWriter = new StreamWriter("output.txt", True)
Dim input as String
input = scan.ReadLine()
Dim ab() as String = Split(input)
Dim res as Integer = Val(ab(0))+Val(ab(1))
writer.writeLine(res)
writer.close()
End sub
End class
Your code works properly for me, so as long as your input file is formatted properly (i.e. a single line with two numbers separated by spaces, like "1 2") and you have the necessary OS permissions to read and write to those files, then it should work for you too. However, it's worth mentioning that there are several issues with your code that would be good to correct, since the fly in the face of typical best-practices.
First, you should, as much as possible, turn Option Strict On. I know that you have it Off because your code won't compile with it On. The following line is technically misleading, and therefore fails with Option Strict On:
Dim res As Integer = Val(ab(0)) + Val(ab(1))
The reason if fails is because the Val function returns a Double, not an integer, so, technically, depending on the contents of the file, the result could be fractional or could be too large to fit in an Integer. With Option Strict Off, the compiler is essentially automatically fixing your code for you, like this:
Dim res As Integer = CInt(Val(ab(0)) + Val(ab(1)))
In order to set the res variable equal to the result of the calculation, the more capable Double value must be converted down to an Integer. When you are forced to put the CInt in the code yourself, you are fully aware that the conversion is taking place and what the consequences of it might be. When you have Option Strict Off and it inserts the conversion behind-the-scenes, then you may very well miss a potential bug.
Secondly, the Val function is old-school VB6 syntax. While it technically works fine, it's provided mainly for backwards compatibility. The new .NET equivalent would be to use Integer.Parse, Integer.TryParse or Convert.ToInt32.
Thirdly, you never close the scan stream reader. You could just add scan.Close() to the end of your method, but is better, when possible, to create Using blocks for any disposable object, like this:
Using scan As StreamReader = New StreamReader("test.txt")
Using writer As StreamWriter = New StreamWriter("output.txt", True)
Dim input As String
input = scan.ReadLine()
Dim ab() As String = Split(input)
Dim res As Integer = Integer.Parse(ab(0)) + Integer.Parse(ab(1))
writer.WriteLine(res)
End Using
End Using
Lastly, as Hans pointed out, it's not good to rely on the current directory. It's always best to specify full paths for your files. There are different methods in the framework for getting various folder paths, such as the user's desktop folder, or the download folder, or the temp folder, or the application folder, or the current application's folder, or the folder of the current running assembly. You can use any such method to get your desired folder path, and then use Path.Combine to add the file name to get the full file path. For instance:
Dim desktopFolderPath As String = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)
Dim inputFilePath As String = Path.Combine(desktopFolderPath, "input.txt")
Dim outputFilePath As String = Path.Combine(desktopFolderPath, "output.txt")

Better socket communication system

Currently, the client is sending messages like this:
Public Function checkMD5(ByVal userID As Integer, ByVal gameID As Integer, ByVal file As String, ByVal fileFull As String) As String
Dim make As New CMakeMSG
Dim md5 As New CMD5
make.append("checkfileMD5")
make.append(userID)
make.append(containerID)
make.append(file)
make.append(md5.GenerateFileHash(fileFull))
Return SocketSendAndReceiveMSG(make.makestring)
End Function
The server may receive something like this:
checkfileMD5-MSGDelimit0-12-MSGDelimit1-54-MSGDelimit2-filename.txt-MSGDelimit3-*md5hash*
Which it then reads out:
Private _message As String
Public Function handleMessage() As String
Dim brokenMessage As New ArrayList
brokenMessage = breakDown() 'Split to ArrayList
If brokenMessage(0) = "checkfileMD5" Then
Try
If brokenMessage.Count > 5 Then
Return "0-structureMessedUp"
End If
Return CompareFileMD5(brokenMessage(1), brokenMessage(2), brokenMessage(3), brokenMessage(4))
Catch ex As Exception
Return "0-structureMessedUp"
End Try
End If
End Function
So what it does is take the received message, and split it to an array using the -MSGDelimit- as a delimiter. So in this case the CompareFileMD5() function would receive 12,54,filename.txt,*md5hash*. And based on that it can return to the client whether or not the MD5 matched.
Sure, it works, but it feels sloppy and code on the server gets really messy.
Here's the less relevant functions from the above code (doubt it matters, but you never know):
Private Function breakDown() As ArrayList
Try
Dim theArray As New ArrayList
Dim copymsg As String = _message
Dim counter As Integer = 0
Do Until Not copymsg.Contains("-MSGDelimit")
Dim found As String
found = copymsg.Substring(0, copymsg.IndexOf("-MSGDelimit" & counter & "-"))
theArray.Add(found)
copymsg = copymsg.Replace(found & "-MSGDelimit" & counter & "-", "")
counter += 1
Loop
theArray.Add(copymsg)
Return theArray
Catch ex As Exception
Module1.msg(ex.Message)
End Try
End Function
Private Function CompareFileMD5(ByVal userID As Integer, ByVal gameID As Integer, ByVal filename As String, ByVal source As String) As String
Try
Dim tryFindFile As String = Module1.filedatabase.findfile(userID, gameID, filename)
If Not tryFindFile = "notFound" Then
Dim fileFull As String = tryFindFile & "\" & filename
Dim md5 As New CMD5
If md5.GenerateFileHash(fileFull) = source Then
Return "Match"
Else
Return "NoMatch"
End If
Else
Return "notFound"
End If
Catch ex As Exception
Module1.msg("0")
Return "0"
End Try
End Function
So, any advice on how to handle it better/cleaner/more professional?
Depending on the application, your current solution may be perfectly fine. There are a couple of things that do stand out a little bit:
The "protocol" is a bit heavy in terms of the amount of data sent. The delimiters between the data pieces adds quite a bit of overhead. In the example, it makes up maybe 50% of the payload. In addition, sending all data as text potentially makes the payload larger than absolutely necessary. All of this, though, is not necessarily a problem. If the traffic between the client and server is relatively light, then the extra data on the wire may not be a problem at all. For a request of this size (with or without the relatively high delimiter overhead), the main cost will be round trip costs and would likely change very little by reducing the size of this packet by half. If, though, there are requests with thousands of pieces of data, then reducing the payload size would help.
The use of the delimiters as shown is potentially ambiguous depending on the data sent. It is unlikely given the length and format of the delimiters, but it's something to keep in mind if there ever exists the possibility of having actual data that "looks" like a delimiter.
Assuming that the example shown is one of many similar protocols, I would be inclined to go a different route. One possibility would be to bundle up the request as a JSON object. There are existing packages available to create and read JSON. One example is Json.NET. JSON has a well-defined structure, it is easy for a human to read and verify, and it can be expanded easily. And depending on the data that you send, it would probably a little more lightweight than the current format. And (maybe the part you are interested in), it would maybe seem more "professional".
A couple of additional things that I would do (personal opinion):
Possibly add a client version to the data being sent so the server will know if it "recognizes" the request. Start the client version at some value (e.g., 1). If there are updates to the protocol format (e.g., different data, different structure), change the version to 2 in that release of the software. Then the server can look at the version number to see if it recognizes it. If it is the first version of the server and sees version 2, then it can return an error indicating the server needs to be updated. This is not necessary if you can guarantee that the client and server releases are always matched (sometimes this is hard in practice).
Use an integer value for the request type instead of a string ('checkFileMD5'). If there are going to be a large number of request types, the server can dispatch the request a little more efficiently (maybe) based on an integer value.

Know if a program launched from command line versus clicking on the .exe in vb.net?

Hi I'm writing a program in vb.net. The program can be started from another program by passing some arguments or it can be lauched by clicking .exe. I'd like to show the user some options depending on where he is coming. Is the below approach correct?
Private Sub Main_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Environment.GetCommandLineArgs(0).ToString = "SomeArgument" Then
'Do some events
Else
'Do some other events
End If
End Sub
Thanks for your help.
It looks like you're on the right track, however you ought to be checking for the argument after the first position. From the GetCommandLineArgs documentation:
The first element in the array
contains the file name of the
executing program. If the file name is
not available, the first element is
equal to String.Empty. The
remaining elements contain any
additional tokens entered on the
command line.
Your code is checking the first element of the array so it would be the program's name. Depending on how many arguments you expect to pass in you should loop through it and determine whether it exists.
For Each arg As String In Environment.GetCommandLineArgs()
If arg = "SomeArg" Then
' do something
End If
Next
' LINQ approach
If Environment.GetCommandLineArgs().Any(Function(arg) arg = "SomeArg") Then
' do something
End If
Also, it's a string array so there's no need to use ToString() on the element.
It is sort of correct. If the user double-clicks the EXE, then Windows will launch your program with no command-line arguments. On the other hand, you can't stop the user launching your app from the command line with no arguments either. Nor can you stop the user manually typing on the command line the same parameters that your calling program passes.
One thing that is wrong with your current code is that the first element (index 0) in the Environment.GetCommandLineArgs() array will actually be your program name (the name of the EXE file). You can get around this by checking for index 1, but if there are no additional arguments then this will throw an IndexOutOfBoundsException. So you need to check the length of the array before indexing into it.
Unless I don't understand what you want, you should iterate over the CommandLine Args collection and only prompt for the ones not provided. This will solve the problem of the user starting your app from the commandline w/out any arguments. This way, you only ever prompt for the arguments that aren't explicitly passed on the commandline.
Like this:
Dim someArgument as String = String.Empty
Dim myArgument as String = String.Empty
For Each arg as String In Environment.GetCommandLineArgs()
If arg.StartsWith("SomeArgument") Then
someArgument = arg
End If
If arg.StartsWith("MyArgument") Then
myArgument = arg
End If
' Continue for each extra argument
Next
If String.IsNullOrEmpty(someArgument) Then
' prompt for someArgument
End If
If String.IsNullOrEmpty(myArgument) Then
' prompt for myArgument
End If
The only tricky part here, is parsing out the value of "arg" in the for loop, since it will be something like "SomeArgument=someValue". My code doesn't split them out, you'll likely want to do that.
Well this is old, but for anyone reading this, I find that the easiest way is to simple check if there is more that 1 argument.
If arg.Count > 1 Then
'Code
End If
The first argument will always be the name of the applocation you are launching.
This should work:
If Console.LargestWindowHeight = 0 Then
'Forms application
Else
'Command line application
End If
You Should add a boolean and do this
Public AllowAppRunV As Boolean = Nothing
Public Sub CheckForCommandLine()
For Each arg As String In My.Application.CommandLineArgs
Select Case Trim(LCase(arg))
Case "/allowrun"
MessageBox.Show("allow")
AllowAppRunV = True
Case Else
MessageBox.Show("else")
End Select
Next
If AllowAppRunV = True Then
Else
MessageBox.Show("Close")
End If
End Sub
i know its a very long time but maybe someone will find this useful