Toggle Renaming a File without checking if it exists in VB.Net - vb.net

Non programmer trying to toggle renaming a file from a vb.net form without checking if it exists. Have done this with simple batch file. Would like to to know if it is possible in vb.net without needing to check if file exists. Also do not want to use toggle control, want to use button control as UI preference.
Code used in batch file:
set ADDON_PATH="P:\MyPath\MySubFolder\"
set ADDON_NAME="myfile.dll"
set DISABLE_NAME="myfile.bak"
cd /d %ADDON_PATH%
Ren %ADDON_NAME% %DISABLE_NAME% 2> nul || ren %DISABLE_NAME% %ADDON_NAME% 2> nul
Partial code here for vb.net using button, not toggle button
Private Sub PlayVanilla(sender As Object, e As EventArgs) Handles Button11.Click
If Button11.Text = "Vanilla Mode" Then
Button11.Text = "Modded Game"
Button11.ForeColor = Color.Green
My.Computer.FileSystem.RenameFile("P:\MyPath\MySubFolder\myfile.dll", "myfile.bak")
Else
Button11.Text = "Vanilla Mode"
Button11.ForeColor = Color.Red
My.Computer.FileSystem.RenameFile("P:\MyPath\MySubFolder\myfile.bak", "myfile.dll")
End If
End Sub
I haven't tried the code above because obviously the file could be in either state, myfile.bak or myfile.dll. So the question is, do i need to check if file exists or can I use a construct as I did in the batch file. One final note, this file always exists and I'm the only user so 0% risk of it not being there. The batch file works simply and perfectly but I don't want to run the batch file from VB form, looking for VB solution. Thank you.

Unable to find solution matching the batch file example, so will test for file exists. In passing, this isn't an enterprise solution -probability of the file being locked by another process is exactly zero. Additionally, there is 100% probability the file exists in one of two name states. However I understand and appreciate the robustness comments and accept them. Thanks.
Imports System.IO
If Directory.Exists("P:\MyPath\Myfolder\Myfile.dll") Then ...

Related

Can't delete .xlsx file - Next Step?

I'm getting an IOException (file in use by another process) error when trying to delete an .xlsx file. I moved the delete code to the front of Form_Load() just to make absolutely certain that nothing else I was doing was trying to open or read it.
Process Explorer can't find any references to the file to see what else might be trying to use it.
The file properties indicate that the user and system have full control.
I don't know what to look at next.
Private Sub PreEdit2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
My.Computer.FileSystem.DeleteFile("\\HOSTNAME\Folder\Import Data\Other Import Files\" & "9710.xlsx")
I tried a local reference to the file just for good measure.
My.Computer.FileSystem.DeleteFile("C:\Folder\Import Data\Other Import Files\" & "9710.xlsx")
Really stumped here. Any suggestions sincerely appreciated.
EDIT:
Modified my program to delete all kinds of other files in the same directory. They delete with no problem.
Created an empty test program with one line that tries to delete the file
and it gets the same IOException.
So, my program isn't doing it. The error says something else is using the file, no monitoring program I have used shows the any process involved with the file, and File Explorer can delete it? Strange things are afoot at the Circle K.
EDIT 2:
I have moved the file and tried deleting it in lots of different directories. I can delete it everywhere else I try it. The only place I don't seem to be able to delete it (with my new single purpose test program) is in the directory it was originally copied to. It should be remembered that all of the other files in that directory I can use the program to delete with no problem.

Problem handling errors with FileSystemWatcher

To start with it has been many years since I have done much programming, probably about 15 years, in fact VB 6 was still being taught then so I'm not up to date on anything and can get lost in what I'm reading but I am trying. I'm using Visual Studio 2019 and trying to create a VB Windows Forms App that uses the FileSystemWatcher. Unfortunately the only solutions to my problem that I could find are in C#. I tried to transfer them to my app but couldn't get them to work. Actually Visual Studio wasn't happy with what I put in and wouldn't run at all, probably due to incorrect syntax.
This part of my little app is supposed to copy a file that another program creates, while the other program is running, and place the copy in another folder. It seemed simple, detect that the file had been changed and then copy the changed file. Well I came across a few problems.
The first problem is that if I create an empty file the code works most times with no problems but occassionally 2 copies of the file are created. I could live with that but.
If I replace the empty file with a larger file, the file that it is meant to copy, then it can make 3 or 4 copies and most times produces a messagebox telling me that it can't access the file because it is in use. The main program is also minimised to display the error message, which I don't want.
Even if my app is the only program running and I replace the watched file manually with the larger file I still get the file in use error, so I am assuming that the event has been triggered twice and one of them is the process that is using the file and causing the error.
If I tell the error message to cancel then my program continues and produces only 1 copy of the file. Which is what I do want.
The second problem is that the file being copied is also accessed by another program for a short period imediately after it has been changed and this also causes an extra few copies of it to be made and sometimes a file in use error message box appears, and again the main program is minimised to display the error message. I originally had other IO.NotifyFilters in place that weren't really necassary and thought that they may have been triggering the errors so I removed them, but it made no difference.
The most common error is that the file is in use but there has also been the odd "Unhandled exception has occured in your application. could not find file.". The only reason that I can think of for this error is that the file may have been renamed to a backup file and a new version created by the main program at the exact time my program tried to access it.
From what I have read the FileSystemWatcher can trigger multiple times and I presume that this is what is causing the duplicate copies from both problems.
I need my app to make only 1 copy without the error messagebox appearing as this program needs to run in the background with no user input. Essentially this part of my app is an external program to backup a file when the file changes because the main program changes this file often but only seems to back up the file every few hours.
The following code is what I have used but nothing that I have tried has caught the error so I removed the error event handler. This code was copied from something else that was similar and in C# then modified for my purpose. I thought that the {} were used for C# not VB but it seems to work and if I take them out it won't.
My code for the FileSystemwatcher is:-
WatchFile = New System.IO.FileSystemWatcher With {
.Path = Path.GetDirectoryName(strArkMapFileNamePath),
.Filter = Path.GetFileName(strArkMapFileNamePath),
.NotifyFilter = IO.NotifyFilters.LastWrite
}
' add the handler to each event
AddHandler WatchFile.Changed, New FileSystemEventHandler(AddressOf OnLastWrite)
'Set this property to true to start watching
WatchFile.EnableRaisingEvents = True
The event handler is:-
Private Sub OnLastWrite(sender As Object, e As FileSystemEventArgs)
'Copy the Save file to a new folder and rename it.
My.Computer.FileSystem.CopyFile(
strArkMapFileNamePath,
strBackupPath & "\" & strArkMapFileName & "_" &
DateTime.Now.ToString("dd.MM.yyyy_hh.mm.ss") & ".ark",
Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs,
Microsoft.VisualBasic.FileIO.UICancelOption.DoNothing)
End Sub
I added an error event handler after AddHandler WatchFile.Changed, New FileSystemEventHandler(AddressOf OnLastWrite) but that did nothing.
I tried to add an on error statement before the end sub and that did nothing either, I presume because the Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs, caught the error first.
I got frustrated and tried to add a statement before the Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs, but it didn't like that and I didn't expect it to work.
So how do I catch the errors before the FileSystemWatcher acts on the error?
I don't know what I am doing wrong so any help would be appreciated.
Also if anybody could offer code for what I need to do can it please be code for a Windows Forms App because I don't seem to have much luck in converting C# or anything else.
Edit
I have replaced the My.Computer.FileSystem.CopyFile with the File.Copy method as suggested and added a few extra bits..
Private Sub OnLastWrite(sender As Object, e As FileSystemEventArgs)
'Verify that source file exists
If File.Exists(strArkMapFileNamePath) Then
'Copy the Save file to a new folder and rename it.
Dim i As Integer
For i = 0 To 3
Try
' Overwrite the destination file if it already exists.
File.Copy(strArkMapFileNamePath, strBackupPath & "\" & strArkMapFileName & "_" & DateTime.Now.ToString("dd.MM.yyyy_hh.mm.ss") & ".ark", True)
i = 3
' Catch exception if the file was already copied.
Catch copyError As IOException
'If copy failed reset counter
i += 1
End Try
Next
End If
End Sub
Although this shouldn't be required because this is done before the FileSystemWatcher is enabled I have added an If statement to check that the files exists before attempting to copy the file.
I'm not entirely happy with the loop but I know under normal conditions that it's not going to be an endless loop.
Is there a way to just catch the specific errors that I would need to deal with or will it be fine the way it is?
I will probably still need to work out how to stop the FileSystemWatcher from sending more than 1 event or somehow make multiple events appear as 1.
The If statement was the last thing that I added and for some reason the program now seems to only make 1 copy and appears to be faster.
The other external program that accesses the file being copied must be slower to react than my program because now it displays a message that it's waiting until my program has finished copying the file.
My program is now performing as it was intended but I believe that it is just a matter of timing rather than the correct code.

Files not opening as read-only in VB.NET when read-only attribute set

I'm having trouble with a bit of code designed to intentionally open files as read-only. My team often needs to be able to peek into each others' files without locking the file owner out, so in a form being designed for document management I want users to be able to open files optionally as read-only.
Coming from VBA I'm still somewhat new to VB.NET and also bitwise operations generally, but I believe the "read-only" interpretation of this code from MS Docs has been correctly implemented:
Dim attributes As FileAttributes
attributes = File.GetAttributes(path)
If Not (attributes And FileAttributes.ReadOnly) = FileAttributes.ReadOnly Then
' Make file readonly.
File.SetAttributes(path, File.GetAttributes(path) Or FileAttributes.ReadOnly)
End If
' Open the file
System.Diagnostics.Process.Start(path)
' Reset the file to read/write.
attributes = RemoveAttribute(attributes, FileAttributes.ReadOnly)
File.SetAttributes(path, attributes)
When I use "GetAttributes" before and after the line to open the file I get a return of 1 or sometimes as 33, which the FileAttributes enumeration documentation suggests is correct for what I'm trying to do. Before and after the attribute change "GetAttributes" returns 128 or in certain cases 32, which also should be correct.
However despite the fact the above code appears correctly implemented and seems to be producing the correct affect in the file's attributes, files opened this way (namely Excel files) open as read-write. I'm also fine with other ways of opening a file read-only provided that it can be used equally well on any document you would commonly encounter in an office setting (Excel, Word, etc.) with its default program. That being said, I've tried several methods and haven't had any success, and this one by far has seemed the cleanest and most promising.
Thanks in advance!
As described in comments, the file attributes are restored to their
previous state right after the Process.Start() command: the
application that opens the file has not been started yet; when it
finally access the file, the read-ony attribute has already been
removed.
A possible solution is to subscribe to the Process.Exited event and restore the original file attributes when the Process termination is notified.
A modified version of your code: the EnableRaisingEvents property causes a process to raise the Process.Exited event. I subscribed to the event using an in-line delegate (a Lambda), but I added an example that uses a standard delegate method using the AddressOf operator (since you said you have to learn about events).
Since we want to run a file and not an executable, we need to also set UseShellExecute = True, so the Shell will find and execute the registered application associated with the file extension.
If UseShellExecute = True is not specified, an exception is raised (the file is not an executable).
The name of the file to execute is assigned to the Process.StartInfo.FileName
When the Process terminates, the Exited event is raised. In the event handler, the file attributes are restored to the previous state.
Private Sub SomeMethod(filePath As String)
' filePath is File's Full path
Dim attributes As FileAttributes = File.GetAttributes(filePath)
File.SetAttributes(filePath, (attributes) Or FileAttributes.ReadOnly)
Dim proc As Process = New Process()
proc.StartInfo.FileName = filePath
proc.StartInfo.UseShellExecute = True
proc.EnableRaisingEvents = True
AddHandler proc.Exited,
Sub()
File.SetAttributes(filePath, attributes)
proc?.Dispose()
End Sub
proc.Start()
End Sub
If you want to use a standard method as the Exited event handler, you have to declare the filePath and attributes variables in a different scope. Neither can be a local variable, they won't be accessible from the method delegate.
If you need to run just one file, these can be instance fields (declared in the scope of the current class).
If you instead can have multiple processes running different files, all waiting for the associated applicationt to terminate, these informations should to be stored in a list of objects, a Dictionary or a similar container.
For example, using a Dictionary, declared as a Field:
(the Dictionary Key is the File path. If a file can be opened multiple times - a .txt file maybe, use a different identifier)
Private myRunningFiles As New Dictionary(Of String, FileAttributes)
' (...)
Private Sub SomeMethod(filePath As String)
Dim attributes As FileAttributes = File.GetAttributes(filePath)
If Not myRunningFiles.ContainsKey(filePath) Then
myRunningFiles.Add(filePath, attributes)
Else
' Notify that the file is already opened
Return
End If
Dim proc As Process = New Process()
' (... same ...)
AddHandler proc.Exited, AddressOf OnProcessExited
End Sub
Protected Sub OnProcessExited(sender As Object, e As EventArgs)
Dim proc = DirectCast(sender, Process)
Dim filePath = proc.StartInfo.FileName
Dim attributes = myRunningFiles(filePath)
File.SetAttributes(filePath, attributes)
myRunningFiles.Remove(filePath)
proc?.Dispose()
End Sub
Thanks to #Jimi I was able to come up with the solution. In my application Excel files are the most important to open in ReadOnly so I'm happy with an Excel-only solution for the time being. While his answer for using and releasing attributes was great it had the problem of not restoring the attributes to their default until the file is closed, which to my understanding would cause others to also open the file ReadOnly while the file is open. The point of this in my application is to "peek" at a file without locking other users on a network out of ReadWrite access, so unfortunately his-well-thought out solution won't work.
I was however able to use the /r switch with a bit of investigating. It was a bit tricky (for someone of my skill level) since some switches need to be placed before the file path and some after. Solution below:
Process.Start("EXCEL.exe", "/r " & Chr(34) & path & Chr(34))

Is there a way to write a command directly by code in the immediate window?

I'm trying to save all lines from the output window into a log-file. That works fine when I enter the command directly into the immediate window. However, I need a way to enter the command line by code.
I searched for a way to enter a command into the immediate window so the log-file I want will be created and filled with data. That's not happening and I don't know if this is even possible. Searching Google didn't help so far.
My command line is:
> Tools.LogCommandWindowOutput C:\Users\user\AppData\test.log
And I try to get it into the immediate window by
Debug.Print("> Tools.LogCommandWindowOutput C:\Users\user\AppData\test.log")
The command line works fine when put directly into the immediate window (either by typing it in or pasting it in). When I try to do that by code, nothing happens. Is there a way to do that or do I have to try another option and if so, what option is it?
EDIT:
I've tried the solution provided by RobertBaron with the following changes:
'Dim dummy = $"C:\\log\\outputfileAddOn_{Date.Now:yyyy_MM_dd_HH_mm_ss}.log"
'cmdWindow.SendInput($"Tools.LogCommandWindowOutput {dummy}", True)
cmdWindow.SendInput("Tools.LogCommandWindowOutput C:\log\outputfile_AddOn.log", True)
(I want a new file to be written every time, so I tried to add the date at the end to have unique file names)
It creates the file but doesn't log anything in it. What do I do wrong?
Another solution I have found was to add a command parameter in project-properties-debug-command parameter:
> C:\log\outputfile.log
This creates the file and inserts all of the data from the output window. The only problem that I have now is that this file will be overwritten every time the program is started. Is there a way I can set the logging from the second, third, … start at the end of the file? (adding /on at the end of the command Parameter didn't help) Or can I provide something like "outputfile_yyyy_MM_dd_HH_mm_ss.log" (for example: outputfile_2019_07_23_15_47_45.log)?
Thank you for your help!
You need to create a Visual Studio Extension project.
Add a new Custom Command item to the VSIX project.
This will create the Command1.vb. This is where you implement the code that you want to execute inside Visual Studio. The code executes whenever you select the entry Invoke Command1 from the Tools menu.
At the bottom of the Command1.vb file, there is the Execute() Sub. It displays a message just to show that the command is working. You can remove this eventually. But for now, just run the project. Another instance of Visual Studio will start. This is where you can test and debug your code. Once the second instance of Visual Studio has started, go to its Tools menu, and select the Invoke Command1 entry. A message box will display. Stop execution.
Now we want to modify the Execute() Sub so that our code gets executed when Invoke Command1 is selected. Here is the Sub that will execute the command that you want. Add it to the Command1 class.
Public Sub ExecCommandWindow()
Dim dte As EnvDTE.DTE = CType(Microsoft.VisualStudio.Shell.Package.GetGlobalService(GetType(Microsoft.VisualStudio.Shell.Interop.SDTE)), EnvDTE.DTE)
Dim cmdWindow As CommandWindow = CType(dte.Windows.Item(EnvDTE.Constants.vsWindowKindCommandWindow).Object, CommandWindow)
'Display some text in the command window
cmdWindow.OutputString("Executing command from the automation OM...")
'Send some command strings to the command window and execute them...
'This command will start logging all input/output in the command window to the specified file
cmdWindow.SendInput("Tools.LogCommandWindowOutput cmdwindow.log", True)
''Open a file in a code editor:
'' 1. We use an alias, 'of', for the File.OpenFile command
'' 2. This command takes quote-delimited parameters (in this case,
'' the name of the editor to load the file in)
'Dim cmd As String = "of "
'cmd = (cmd + """""C:\Contoso\ContosoCommonFramework\Integration.cs""""")
'cmd = (cmd + "/e:""""CSharp Editor""""")
'cmdWindow.SendInput(cmd, True)
'cmdWindow.SendInput("Edit.Find MessageTrxId", True)
'Turn off logging
cmdWindow.SendInput("Tools.LogCommandWindowOutput /off", True)
End Sub
Change the Execute() Sub to call ExecCommandWindow().
You can change the name of the command by changing its title in the Command1 class.
The Visual Studio extension needs to be installed by each user. Just distribute the .vsix file. To install, double-click it.
It's not the best solution, but it works (for now):
adding
>> C:\log\outputfile.log
(with two '>' before the path for the log file) attaches every log at the end of the file. So I get all Information and nothing is being overwritten.
As I want to know if there is a better solution, I will keep this thread open if that is permitted.

Get file from "Windows Explorer - Open With"

I'm not quite sure where to start with this. On right-clicking on a generic file in Windows Explorer (e.g. *.doc for a Word document) one can choose "Open with...". I 'd like to know how the program knows what file has been "passed" (is that the right word?). Is it done via arguments? How can I implement this in my own application?
I tried manually adding a file path to the arguments of one of my applications when it is run, but the path includes spaces (which denotes a new argument). How does Windows get round this/what do I need to do to solve this?
Regards,
Robbie
To retrieve the arguments used from the command line:
Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
Dim sMsg As String = ""
For Each sArg As String In My.Application.CommandLineArgs
sMsg &= sArg & ": "
Next
MsgBox(sMsg)
End Sub
Place the code in the WinForm _Load, Console Main, etc.
If the above was run with: Hello World as the command line Hello: World: would display.
Here is some VB code to open a file:
Case Keys.F4
Process.Start("H:\OIS\PROCEDUR\OIS8ProcedureManual.doc")
In this case Windows looks up .doc in the file types and uses the .doc entry to run Word and pass it the filename.
Process.Start has a second parameter that contains Arguments so you could provide a path to an .exe in the first param and the argument(s) in the second. Actually there are 5 signatures for Process.start. The most powerful ones uses the ProcessStartInfo class to provide you with the most control.
Post the code you wrote for the second group of questions if the above didn't help.