Okay, this is one that has me stumped beyond belief. I have a program that is to be installed on different machines that is supposed to execute a batch file after it has completed a specific task. The batch file is configured via our webservice by the user, and the application pulls this path string down and then executes the file at that path. Should be pretty simple/straightforward, right?
The code:
Private Function ExecuteBatchFile(batchFilePath As String) As String
Try
Dim psi As New ProcessStartInfo(batchFilePath)
psi.RedirectStandardError = True
psi.RedirectStandardOutput = True
psi.CreateNoWindow = False
psi.UseShellExecute = False
Dim process As Process = process.Start(psi)
process.WaitForExit()
ExecuteBatchFile = process.ExitCode
Catch ex As Exception
StrAppStatus = "Error within execution of batch file: " & ex.Message
LogMe(StrAppStatus)
Return "Fail"
End Try
End Function
Now, for the tricky part. This code works perfectly when running in the dev environment. I have yet to have an issue. HOWEVER, when the application is installed on any computer (including the same one the IDE is installed on), when it gets to the step in it's overall process that calls this function, it does not actually launch the batch file. Keep in mind, it never throws an exception, on either side. It also does not return an exitcode to that string, as i have other logging to track that as well.
There are only two possibilities I can think of in this case. Either A:) there is a permissions issue where the application is not allowed to launch batch files on the computer it is installed on, or B:) this sub is called by a thread that was spun off from the main thread (i.e. using the MyThread = New System.Threading.Thread(AddressOf TheMainLoop) MyThread.Start() command).
Still the fact that it works 100% of the time during the compile and run phase in the IDE but not after an install is blowing my mind.
EDIT 1:
Just did a test where I placed a button on the main form itself, and the click event does the same thing as the earlier function, and it works, even after an install. This leads me to believe that it will only work if it is launched using the main thread... What do you all think?
EDIT 2:
The batch file is a very simple test batch that opens a text file, and I know that the batch file and path are both fine, because when compiled it works fine. Also, edit 1 was stating that I created a simple button that executes the same code from above, and it works fine after an install. Since that was done on the main thread, and the code from earlier was done in a separate thread spun off from the original, I was wondering if that was the cause of the issue.
The other crazy thing is that my logging catches if the file launches or not, and it almost acts as if it is launching when I have that original issue. Bah, this whole thing is just nuts.
EDIT 3:
Added the answer.
Thanks to tinstaafl, the answer was to change UseShellExecute to True. I had to remove the RedirectStandardError and RedirectStandardOutput lines, but I wasn't using them anyways. On that note, thank you everyone for your investigations, and if anyone has an idea as to why this happened, I am all ears.
Related
25 year old vb.net code. Same code hasn't changed for years. Suddenly starting at about 2-3 weeks ago any File.Copy() or File.Delete() - these are root .net functions- started locking files. Never releases lock. I have to go on fileshare look for open files and release lock. Every time. All my programs are crashing. Anyone else having problems with .net file commands? Crazy. This is happening on fileshare drives and administrative shares.
I re-wrote one function using cmd.exe running Robocopy instead of File.Copy and that worked. The problem is not permissions. Robocopy works. File.Copy does not. I ran this in debug mode and compiled. Both lock files. Everything was compiled on Microsoft Visual Studio Professional 2019
Here is an example that locks a file. It creates the file writes a line and closes the file then when it gets to x = 2 and tries to delete file- it throws an exception saying file is locked. I have to go in and close the open file on the fileserver. If I switch the location to a local drive location like c:\temp- this code works perfectly. No locks, no errors.
Imports System.IO
Try
Dim StrWriter As StreamWriter
Dim x As Integer
For x = 1 To 5
If File.Exists("\\fs1\public1\LogFile.txt") Then
File.Delete("\\fs1\public1\\LogFile.txt")
End If
StrWriter = File.CreateText("\\fs1\public1\\LogFile.txt")
StrWriter.WriteLine("Hello")
StrWriter.Close()
Next
Catch ex As Exception
MsgBox(ex.Message)
End Try
In case anyone comes across this issue- it's not anything to do with programming. It is from anti-virus software. My AV software was randomly locking files on the fileshare that my program was copying or trying to delete.
The File.CreateText() documentation includes this info:
Creates or opens a file for writing UTF-8 encoded text. If the file already exists, its contents are overwritten.
Therefore the Exists() and Delete() code are not necessary at all. If the file already exists, we can call CreateText() with no other checks and any existing file is replaced (deleted) automatically. Everything can reduce to this:
Try
Using writer As StreamWriter = File.CreateText(#"\\fs1\public1\LogFile.txt")
writer.WriteLine("Hello")
End Using
Catch ex As Exception
MsgBox(ex.Message)
End Try
This will also likely (no guarantees) fix the issue in the question. The reason is the most likely source was an exception or other failure to reach or fully execute the StrWriter.Close() line, such that the stream was left open. The Using block makes stronger guarantees about disposing the stream. You should also track down other code which writes to this log file and update it to also use Using block.
It's also possible the issue is external to the code: whoever is responsible for monitoring this log file is using a new tool that locks the file while they have it open, and they are leaving this tool running in the background. Or maybe it's the same tool, but the behavior changed, where now they leave it running when before they would have closed it.
One other thing to mention is it's now better practice to use a fully-qualified name for the server. Instead of just fs1, use something like fs1.myADdomain.example. This helps speed name resolution, makes it less likely for authentication to fall back to older/weaker mechanisms, and is one part of allowing better-encrypted traffic between the client and server.
My problem :
I made a program and i have this program for example now under this directory : "C:\Program Files (x86)\AppName", now in the program i'm saving some images in my program directory, but since this is in the C drive, it's giving me a access denied error, now i made the program to always run in administrative mode and that worked just fine, but also there is in my program a feature to auto-start my program when the system starts up using a registry key, but when the "always run in administrative mode" is ON, the program will not start even though there is a start-up key in the registry, and when the "always run in administrative mode" is OFF, it will start.
What I have tried so far :
I tried making an another executable file that will start when the system starts up and then it will run my main program and close it self and i knew that will work but now every time i start my computer, the User Access Control prompt ask me if i trust this program even and that is not very user-friendly.
So i searched and i found that i can create scheduled task but i didn't find a lot of examples and but i tried creating one manually and it did work as wanted!
But here is a code which didn't work :
Imports Microsoft.Win32.TaskScheduler
Using ts As New TaskService("\\RemoteServer")
'Create a new task definition and assign properties
Dim td As TaskDefinition = ts.NewTask()
td.RegistrationInfo.Description = "Does something"
'Create a trigger that will fire the task at this time every other day
td.Triggers.Add(New DailyTrigger() With {
Key.DaysInterval = 2
})
'Create an action that will launch Notepad whenever the trigger fires
td.Actions.Add(New ExecAction("notepad.exe", "c:\test.log", Nothing))
'Register the task in the root folder
ts.RootFolder.RegisterTaskDefinition("Test", td)
End Using
And the problem with the code is :
It couldn't find the TaskService and it has no use of the TaskScheduler library!
Hope someone have some previous experience with that who can help me!
To answer your question:
To use that code you've got to download the Managed Task Scheduler Wrapper first. Then to make it run with administrative privileges you've got to set the RunLevel to TaskRunLevel.Highest on your TaskDefinition:
td.Principal.RunLevel = TaskRunLevel.Highest
However like Plutonix says you shouldn't be writing files to the directory of your program (as that's usually located in the restricted %ProgramFiles% folder).
Instead, use the %ProgramData% directory. You can get the path to it from your code via Environment.GetFolderPath():
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)
'Example:
Dim ConfigPath As String = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "AppName")
Dim ImagePath As String = Path.Combine(ConfigPath, "MyImage.png")
If Directory.Exists(ConfigPath) = False Then Directory.CreateDirectory(ConfigPath)
i have the following problem. i am working on an auto update in my application which means i want to re-download the application. i am using DownloadFile method for this but the problem is of course i cannot download and exchange the file with my current application as long as the application is running and i can't run code after it's closed either.
this is what i am basically trying to do for ppl still didn't get my problem:
Private Sub closeApp()
Application.Exit()
' DOWNLOAD FILE AFTER PROGRAM CLOSED
End Sub
The way this is usually done is to have 2 separate programs. One is the program its self, the other is the updater. When you exit the program you spin up a copy of the updater and exit the actual program. the updater program is then free to download and overwrite the main application because the file isn't in use any more.
This can also be reversed, with the updater, sometimes called the "launcher" when used this way, acting as a stub that checks for updates to the main application when it starts. Once it sees that no update is needed, or after it updates the main program, it goes on to launch the main program and exits its self.
I have wrote a win forms application in VB that requires some external DLLs, It installs and runs flawlessly on several machines but it does not work on one laptop! I even put Filestreams to write exceptions to a text file. While it writes intended header text to the debug file on other computers, it does not write anything to the debug file on the aforementioned laptop. My guess is that it does not even reach Form1_Load() method.
This is the structure of the program:
Import namespaces from external programs
Public Class MyApp
''Varibale declarations:
Structures and classes based on DLLs
Other declarations based on .Net Objects
Stream writer declaration and initialization for the debug file
Private Sub Form1_Load(...)
Try
Write Start and time to the debug file
execute commands
catch
write debug info
end Try
End Sub
Other events and subs
End Class
IS there a way to see what happens between the initialization of the debug file and execution of Form1_Load() ??
As I said it works perfectly on all the machines that use this except one!!
What are the things that I need to check on that machine?
Chances are that the user of the laptop is not in the Administrators group and you're trying to write to the application program folder.
Have you tried to add a placeholder line in the debug file immediately after initialization, to check that the file is actually write-able?
Is the Form's constructor reachable? If you move the "Write Start and time to the debug file" code to the constructor, does it write anything?
Our Winform app consists of a Main.exe and a Launcher.exe. The purpose of the Launcher app is to check for updated versions of the Main.exe.
If it finds no updates it launches Main.exe using System.Diagnostics.Process.Start
If it finds an update, it copies the new Main.exe into position and launches Main.exe the same way (in this case it will be a fresh copy of Main.exe).
Here's the Process.Start code:
Dim p As New ProcessStartInfo
p.FileName = "Main.exe"
p.WindowStyle = ProcessWindowStyle.Normal
Process.Start(p)
This code executes whether or not there's been an update and always successfully fires up Main.exe.
However our Main.exe, if it's being run for the first time (ie. after an update), will error on any line which references the My namespace, such as My.Settings or My.Computer.FileSystem
For example this line will cause an error:
Msgbox(My.Computer.FileSystem.SpecialDirectories.Desktop)
Here's the error:
System.IO.DirectoryNotFoundException:
Could not find special directory
'Desktop'. at
Microsoft.VisualBasic.FileIO.SpecialDirectories.GetDirectoryPath(String
Directory, String DirectoryNameResID)
at
Microsoft.VisualBasic.FileIO.SpecialDirectories.get_Desktop()
at
Microsoft.VisualBasic.MyServices.SpecialDirectoriesProxy.get_Desktop()
But while this example refers to Desktop remember it can't find any of the SpecialDirectories. It's not restricted to Desktop.
But only the first time it's run (ie. immediately after an update). Thereafter it will run fine.
If the process failed more spectacularly, to do with file system issues, locks or threads, it would be more understandable. But why just this "minor" problem with the My namespace?
I realise now it was nothing to do with the copied file and everything to do with a badly implemented System.Security.Principal.WindowsImpersonationContext.
Amazing how these things become clear 5 minutes after posting.