My goal is to set up a service to watch a network folder containing about 200 .exe files. What I'd like is to have the service update a log each time one of the .exes is launched. Basically I'd like to log usage of each application by recording every time one one of them is used.
I've tried using the FileSystemWatcher class to accomplish this, code below, figuring that the LastAccess filter would do the trick, but it seems it won't. When I run this code no event is raised when the applications are opened.
Is there some way of using the FileSysteWatcher class to do this kind of monitoring? Is there any way to do what I'm attempting?
Private Sub StartWatch()
Dim exeWatcher As New FileSystemWatcher
exeWatcher.Path = "<path>"
exeWatcher.Filter = "*.exe"
exeWatcher.IncludeSubdirectories = True
exeWatcher.NotifyFilter = (NotifyFilters.LastAccess Or NotifyFilters.LastWrite Or NotifyFilters.FileName Or NotifyFilters.DirectoryName Or NotifyFilters.Attributes)
AddHandler exeWatcher.Changed, AddressOf ExeChanged
exeWatcher.EnableRaisingEvents = True
End Sub
Private Sub ExeChanged(source As Object, e As FileSystemEventArgs)
Console.WriteLine("File: " & e.FullPath & " " & DateTime.Now.ToString())
End Sub
Take a look at this Stack Overflow answer, which involves monitoring WMI Win32_Process instance creation events (basically, when WMI registers that a new process has been created). This is probably the most effective way outside of a C++ kernel hook to find out when a process has started.
At that point, you just need to use a regular expression to test the file path against to see if it's originating from that folder, and respond appropriately if it is.
The file system watcher cannot be used to accomplish this because it doesn't know why the file is being accessed. It could be accessed to show the properties of the executable or someone copied it to their local hard drive.
If your goal is to see what machines are running your executable, you can use Windows Management Instrumentation (WMI) to remotely query a machine for Win32_Process and determine if your process is running there.
Related
Hi there I have piece of legacy (VS2010) Windows Service code that I have imported into VS2017 and is causing me severe frustration. This code has worked well for about the last 6 years, however when I carry out the install and attempt to start the service the SCM comes back with a timeout error. The OnStart code is as follows:
Protected Overrides Sub OnStart(ByVal args() As String)
'Instaniate the timer for the service
_serviceTimer = New Threading.Timer(New Threading.TimerCallback(AddressOf Tick), Nothing, 60000, 60000)
End Sub
The call back is:
Private Sub Tick(ByVal state As Object)
'Switch off the timer event whilst the code executes
_serviceTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite)
If Not _started Then
Startup()
_started = True
End If
Call ServerProcess()
'Re-enable the timer now that application code has completed
_serviceTimer.Change(_longInterval, _longInterval)
End Sub
I originally had the Startup process in the OnStart method, however removed it as an attempt at resolving this issue, however it has not made any difference. Method Startup is as follows:
Public Sub Startup()
Try
'Source these settings from the local config file
_appDataFolder = Utilities.GetSetting("AppDataRoot")
_configPathMapped = _appDataFolder & Utilities.GetSetting("ConfigPathMapped")
_logPath = _appDataFolder & "\" & utl.GetSetting("LogPath")
'Instaniate the timer for the service - Commented out after moving startup code from OnStart method
' _serviceTimer = New Threading.Timer(New Threading.TimerCallback(AddressOf Tick), Nothing, Timeout.Infinite, Timeout.Infinite)
'Initialise logging architecture
_logger = New aslLog.Logger(_configPathMapped & "nlog.config", _logPath, My.Application.Info.ProductName, My.Application.Info.Version.ToString)
_logger.SendLog("Started PSALERTS Schedule Server Service", NLog.LogLevel.Info, _serviceTimer, _checkInterval, Nothing)
'Determine if the cloned config files exists in the mapped config file folder
'We clone these files to a writable destination to allow us to overcome write restrictions ot the C: drive on the SPEN PTI Desktop model
If Not System.IO.File.Exists(_configPathMapped & "psaservermachine.config") Then
'Clone the app.config file in the config folder as psaservermachine.config
Utilities.CloneFile(_programFileLocation & "PSALERTSScheduleServer.exe.config", _configPathMapped & "psaservermachine.config")
End If
If Not System.IO.File.Exists(_configPathMapped & "nlog.config") Then
'Clone the nlog.config file
Utilities.CloneFile(_programFileLocation & "PSALERTSScheduleServer.exe.config", _configPathMapped & "nlog.config")
End If
'Determine the Oracle TNS Environment
'Check for the existence of the environment variable 'TNS_ADMIN'
If Environment.GetEnvironmentVariable("TNS_ADMIN") IsNot Nothing Then
'If TNS_ADMIN exists then we can continue with the application session
Else
Dim oraTnsPath As String = ""
'If it doesn't exist then we need to determine the Oracle information from the PATH environment variable
oraTnsPath = GetOraTnsPath()
If oraTnsPath <> "" Then
'Then create the TNS_ADMIN environment variable
Environment.SetEnvironmentVariable("TNS_ADMIN", oraTnsPath)
Else
'If no oracle client information exists then raise an error to this effect and exit the app
'informing the user that they need to install the Oracle client in order to use PSALERTS
Beep()
Throw New PSALERTSOracleConfigException(
"PSALERTS Oracle Configuration Error. PSALERTS Did not find a valid Oracle Client." & vbCrLf & vbCrLf &
"Please install a valid Oracle Client and try again." & vbCrLf & vbCrLf &
"If a valid Oracle Client is installed then ensure that the PATH environment variable contains an entry for the Oracle Client." & vbCrLf & vbCrLf &
"For example - TNS_ADMIN=C:\oracle\12.1.0\Client_lite\NETWORK\ADMIN"
)
End If
End If
'Register the application
If Not Registered() Then
'Register the application
Register()
End If
If Registered() Then
'Clean/close any stray Excel processes from previous debug session
If _debugModeOn Then
CleanUpRedundantProcesses("EXCEL", "PSALERTS")
End If
'instantiate fresh excel session
_myXLApp = New Excel.Application
'Get the timer interval settings
_longInterval = CType(utl.GetSettingServerMachine(_configPath, "appSettings", "LongIntervalMillis"), Integer)
_initInterval = CType(utl.GetSettingServerMachine(_configPath, "appSettings", "InitialIntervalMillis"), Integer)
_refreshInterval = CType(utl.GetSettingServerMachine(_configPath, "appSettings", "InitialIntervalMillis"), Integer)
'Re-start the timer with periodic signalling as per the specified check interval
_serviceTimer.Change(_initInterval, _initInterval)
Else
_started = False
End If
Catch ex As Exception
_logger.SendLog("PSALERTS Schedule Server startup failure.", NLog.LogLevel.Error, ex)
Finally
End Try
End Sub
I use a similar technique for a number of similar services and they are running fine. Would appreciate some insight from any Windows Service gurus out there. Oh, I use WiX to carry out the install, again this is a well worn template for a number of similar such applications.
Kind Regards
Paul J.
Core: The very most typical errors:
Config problems: connection strings, faulty paths, etc...
Boot startup problem (good list - from FAQ)
Wrong password / login account when running as a real user with password.
Files missing or runtimes missing.
Permission problems (ACL / NT Privilege missing).
Maybe check this answer before the below.
UPDATE: Maybe have a look at this previous answer. Service startup timing issue. Also check my ad-hoc answer there in the same page.
Debugger: Other than that - nothing like stepping through the code with a debugger. I haven't done that in a long time. Deploy debug binaries and try? Windows 10 now hides messages from services - not sure how that affects debuggers: No more switching to Session 0.
I am not a service guru, but a deployment specialist. I'll just provide some links and see if that helps. Maybe I have not fully understood the whole problem. I tend to focus on the deployment side and not so much development side.
Ideas List / Debugging Check List: These are "ideas lists" for what could be wrong for applications in general - not just services (two first lists are similar - created some time apart):
Crash on launch
Desktop application won't launch
General purpose WiX / MSI links
Yes, these lists are very generic - too large to digest. Just skim the first two I think.
Debugging Tools: Also a reminder of the most useful service debugging tools: Event Viewer, Task Manager, Services.msc, Process Explorer (system internals), The NET command and SC.exe.
Good Service FAQ: https://www.coretechnologies.com/WindowsServices/FAQ.html
Your startup method should fire up a background worker and quickly return to the SCM that it has started. There is a system wide default setting of 30 seconds but honestly a proper service should respond in a few seconds.
Looking though your code, your connection to the database is probably the long pole causing the problem.
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))
I am starting an external application from Visual Basic to perform CPU and memory intensive computations. The code more or less looks like the following:
gApp = New CANoe.ApplicationgApp = New CANoe.Application
While gApp Is Nothing 'wait until CANoe started
Threading.Thread.Sleep(100)
End While
gApp.Open(canoeConfigFile, False, False)
gApp.Configuration.OfflineSetup.Source = dataFile
gMeasurement.Start()
While (gMeasurement.Running)
Threading.Thread.Sleep(1000)
End While
gApp.Quit()
Would it be possible to encapsulate the above code into a process? I am ultematelly looking for something like:
Dim canoeProcess As New Process
canoeProcess.StartInfo.FileName = "C:\Program Files (x86)\Vector CANoe 8.1\Exec32\CANoe32.exe"
canoeProcess.Start()
gApp = canoeProcess.Application
...
' (the code continues as the above)
...
The reason why I want to encapusalte the application into the process is that (i) I want to run muliple instances of the executable with different dataFiles as jobs and (ii) in case the canoe software hangs or does something weird I'd like to be able to kill the process from the VB application.
Is it possible to achieve the reverse? I.e., giving gApp Application object (that is running) to get its Process ID?
Any comments?
I believe the answer is that it is not possible. I am requesting the resources through the Windows COM interface and encapsulating it into a process might be even impossible (maybe a process does not exist at all)
I'm new here and to vb.net and I'm stuck on something that I feel SHOULD be simple to resolve. I setup my program to let the user decide if he or she wants to have the program run at windows start. It actually works fine as it is assigning the registry value to CurrentUser instead of Local Machine because of admin rights needing to be bypassed. However, when I restart my computer the program comes up like normal, but it will not read my access db that is located in the same folder as the program; it tries to read the DB from Windows\System32.
Is there a way to force it to read from the executablepath instead of System32?
Here is my simple code:
Private Sub startup()
If cbStartup.Checked = True Then
My.Computer.Registry.CurrentUser.OpenSubKey("Software").OpenSubKey("Microsoft").OpenSubKey("Windows").OpenSubKey("CurrentVersion").OpenSubKey("Run", True).SetValue("CC_List", System.Windows.Forms.Application.ExecutablePath)
ElseIf cbStartup.Checked = False Then
My.Computer.Registry.CurrentUser.OpenSubKey("Software").OpenSubKey("Microsoft").OpenSubKey("Windows").OpenSubKey("CurrentVersion").OpenSubKey("Run", True).DeleteValue("CC_List", False)
End If
End Sub
So when the O/S starts your program the Current Directory is %windir%\System32.
You need to either adjust all your existing paths to be explicitly relative to Application.ExecutablePath, or put
My.Computer.FileSystem.CurrentDirectory = My.Application.Info.DirectoryPath
at the start of your program (which is the modern version of ChDir ...).
The following code should replace the executable and restart the application, which should work because the content should be replaced but not in the current running instance:
Dim tmppath As String = System.IO.Path.GetTempFileName
Private Sub YesBtn_Click(sender As Object, e As EventArgs) Handles YesBtn.Click
Dim client As New WebClient()
AddHandler client.DownloadProgressChanged, AddressOf client_ProgressChanged
AddHandler client.DownloadFileCompleted, AddressOf client_DownloadFileCompleted
client.DownloadFileAsync(New Uri("https://github.com/Yttrium-tYcLief/Scrotter/raw/master/latest/scrotter.exe"), tmppath)
End Sub
Public Sub client_DownloadFileCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.AsyncCompletedEventArgs)
File.Replace(tmppath, Application.ExecutablePath, Nothing)
Application.Restart()
End Sub
According to MSDN,
Pass Nothing to the destinationBackupFileName parameter if you do not want to create a backup of the file being replaced.
However, what really happens is that it does create a backup (if the .exe is scrotter.exe, then the new backup is scrotter.exe~RF729c1fe9.TMP). Additionally, a new empty folder called "False" is created in the root directory.
All I want is to replace the running executable with my file and not have any backups or extra folders. Any ideas?
Pretty hard to explain this with the posted code, this smells like a some kind of 3rd party utility stepping in and avoiding the problem your code has. It will never work when you pass Nothing for the backup file name. It is required if you want to replace an executable file that's also loaded into memory. The CLR creates a memory mapped file object for the assembly so Windows can page-in the data from the assembly into RAM on demand. With the big advantage that this doesn't take any space in the paging file. That MMF also puts a hard lock on the file so nobody can alter the file content. That would be disastrous.
That's a lock on the file data, not the directory entry for the file. So renaming the file still works. Which is what File.Replace() does when you provide a non-null backup file name, it renames the assembly so you can still create a file with the same name and not get in trouble with the lock. You can delete the backup copy afterwards, assuming that your program still has sufficient rights to actually remove the file when it starts back up. That's unusual with UAC these days. Or just not bother, disk space is cheap and having a backup copy around to deal with accidents is something you can call a feature.
So get ahead and use File.Replace() properly, use the 3rd argument. Don't forget to delete that backup file before you call Replace().
I think the .exe is locked so long as your process runs - which instance runs is of no concern.
To avoid this, I would place the updater in a separate .exe and shut down your main apllication while updating.