Run process from a windows service as the current user with administrative privilege - vb.net

I'm very new in .net. I'm trying to build a windows service to monitor an windows form application so that it starts and keeps running from startup.
The app will also monitor the windows service back and forth to check if its not stopped and it will try to start the service if stopped. I was following this stack post (written in c#, I converted it to vb.net. Pastebin) to run the app as current user from windows service and it is successfully running as expected.
But the problem is that, this process is starting without administrative privilege for which service start trigger is not working when the app monitors the service and finds it stopped.
When I manually run the Application as Run As Administrator it successfully triggers the service if its found stopped. Please suggest how can I run the process as current user with administrative privilege from windows service.
Here is my Service Class
Public Class myService
Dim ApplicationLauncher As New ApplicationLauncher
Private aTimer As System.Timers.Timer
Dim exePath As String = "path_to_exe"
Protected Overrides Sub OnStart(ByVal args() As String)
SetTimer()
If Not String.IsNullOrEmpty(GetPCUser()) Then
If Not IsProcessRunning("App_exePath") Then
ApplicationLauncher.CreateProcessInConsoleSession(exePath, True)
End If
End If
End Sub
Protected Overrides Sub OnStop()
End Sub
Private Sub SetTimer()
aTimer = New System.Timers.Timer(1000)
AddHandler aTimer.Elapsed, AddressOf OnTimedEvent
aTimer.AutoReset = True
aTimer.Enabled = True
End Sub
Private Sub OnTimedEvent(source As Object, e As ElapsedEventArgs)
If Not String.IsNullOrEmpty(GetPCUser()) Then
If Not IsProcessRunning("App_exePath") Then
ApplicationLauncher.CreateProcessInConsoleSession(exePath, True)
End If
End If
End Sub
Private Function GetPCUser()
Dim strCurrentUser As String = ""
Dim moReturn As ManagementObjectCollection
Dim moSearch As ManagementObjectSearcher
Dim mo As ManagementObject
moSearch = New ManagementObjectSearcher("Select * from Win32_Process")
moReturn = moSearch.Get
For Each mo In moReturn
Dim arOwner(2) As String
mo.InvokeMethod("GetOwner", arOwner)
Dim strOut As String
strOut = String.Format("{0} Owner {1} Domain {2}", mo("Name"), arOwner(0), arOwner(1))
If (mo("Name") = "explorer.exe") Then
strCurrentUser = String.Format("{0}", arOwner(0))
End If
Next
Return strCurrentUser
End Function
Public Function IsProcessRunning(name As String) As Boolean
Dim Result As Boolean = False
Dim GetProcess As Process() = Process.GetProcesses()
For Each pr In GetProcess
If pr.ProcessName = name Then
Result = True
End If
Next
Return Result
End Function
End Class
Here is my Windows Form Application Class
Public Class Form
Dim sc As New ServiceController("myService")
Private Timer2 As System.Timers.Timer
Private Sub Form_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
SetTimer2()
Visible = False
ShowInTaskbar = False
'Some other code
End Sub
Public Sub Form_Startup() Handles Me.Load
'Some other code
End Sub
Private Sub SetTimer2()
Timer2 = New System.Timers.Timer(1000)
AddHandler Timer2.Elapsed, AddressOf OnTimedEvent2
Timer2.AutoReset = True
Timer2.Enabled = True
End Sub
Private Sub OnTimedEvent2(source As Object, e As ElapsedEventArgs)
sc.Refresh()
If sc.Status.Equals(ServiceControllerStatus.Stopped) Or sc.Status.Equals(ServiceControllerStatus.StopPending) Then
sc.Start()
End If
End Sub
End Class

You need modify the manifest file for your service starter (Windows Form Application)
To customise the manifest that gets embedded in the program.
Project + Add New Item
Select "Application Manifest File".
Change the <requestedExecutionLevel> element to
E.g
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
This will make make the application show the UAC prompt when they start the program.
Update
As per your comments
You cannot grant elevated privileges without UAC this violate the
basic principle of User Access Control.
There is no way to elevate permissions while avoiding the prompts, by
design. If there was a way to do this, UAC would become useless.
You need to read this question
Start / Stop a Windows Service from a non-Administrator user account
You will need to set the Service permissions to do this

Related

Unable to run my own created exe inside parrent form (vb.net)

I have been able to run an external program using the following code.
Imports System.Runtime.InteropServices
Public Class Form1
<DllImport("user32.dll")> Public Shared Function SetParent(ByVal hwndChild As IntPtr, ByVal hwndNewParent As IntPtr) As Integer
End Function
Private Sub Button1_Click_1(sender As Object, e As EventArgs) Handles Button1.Click
Dim PRO As Process = New Process
PRO.StartInfo.FileName = ("notepad.exe")
PRO.Start()
Do Until PRO.WaitForInputIdle = True
'Nothing
Loop
SetParent(PRO.MainWindowHandle, Me.Handle)
PRO.Dispose()
End Sub
This works fine..... (for notepad that is)
However If I swich notepad for my own vb.net application it fails to launch that aplication inside the form but rather runs it outside of the form. I thought that the application I am trying to launch might of had somthing in it so I created a new application with nothing in it (as bare as I could get it) and run that instead of notepad but it also fails to launch within its "parent" form but rather it also triggers outside of the "parent" form insted?
Could someone please help me fix this?
You just need to wait a tiny bit longer for the MainWindowHandle property to be populated.
Here's a kludge that'll do it:
Private Async Sub Button1_Click_1(sender As Object, e As EventArgs) Handles Button1.Click
Dim PRO As Process = New Process
PRO.StartInfo.FileName = ("C:\Users\mikes\Desktop\temp.exe")
PRO.Start()
Await Task.Run(Sub()
PRO.WaitForInputIdle()
While PRO.MainWindowHandle.Equals(IntPtr.Zero)
Threading.Thread.Sleep(10)
End While
End Sub)
SetParent(PRO.MainWindowHandle, Me.Handle)
End Sub
If you want a ten second fail-safe, and exceptions caught, then you could change it up to:
Private Async Sub Button1_Click_1(sender As Object, e As EventArgs) Handles Button1.Click
Try
Dim PRO As Process = New Process
PRO.StartInfo.FileName = ("C:\Users\mikes\Desktop\temp.exe")
PRO.Start()
Await Task.Run(Sub()
Dim timeout As DateTime = DateTime.Now.AddSeconds(10)
While timeout > DateTime.Now AndAlso PRO.MainWindowHandle.Equals(IntPtr.Zero)
Threading.Thread.Sleep(10)
End While
End Sub)
If (Not PRO.MainWindowHandle.Equals(IntPtr.Zero)) Then
SetParent(PRO.MainWindowHandle, Me.Handle)
Else
MessageBox.Show("Timed out waiting for main window handle.", "Failed to Launch External Application")
End If
Catch ex As Exception
MessageBox.Show(ex.ToString, "Failed to Launch External Application")
End Try
End Sub

Bug on login form while trying to close the app vb.net

So currently I am using a code which provides me the possibility to manager whenever or not the application will logout or keep on track. Basically if the user is not using the program for quite some time it will move to the Login form and of course if the user wants to login back he had to type again.
I got a class where I am able to produce more or less that code:
Public Class TimerWatcher
Private _timer As System.Threading.Timer
Private _enabled As Boolean
Private _lastEvent As DateTime
Public Sub New()
_timer = New System.Threading.Timer(AddressOf watch)
_enabled = False
Timeout = 0
End Sub
Public Event Idle(sender As Form)
Public Property Timeout As Long
Public Property Enabled As Boolean
Get
Return _enabled
End Get
Set(value As Boolean)
If value Then
_lastEvent = DateTime.Now
_timer.Change(0, 1000)
Else
_timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite)
End If
End Set
End Property
Private Sub watch()
If DateTime.Now.Subtract(_lastEvent).TotalMilliseconds > Timeout Then
Enabled = False
' "End" is quite blunt. You may want to raise an event
' so the form can terminate the application gracefully.
RaiseEvent Idle(Login)
End If
End Sub
Public Sub Refresh()
_lastEvent = DateTime.Now
End Sub
End Class
And then on my main form I got this:
Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
watcher.Timeout = 5000
watcher.Enabled = True
End Sub
Private Sub PaginaInicial_MouseMove(sender As Object, e As MouseEventArgs) Handles Me.MouseMove
watcher.Refresh()
End Sub
Private Sub watcher_idle(sender As Form) Handles watcher.Idle
Dim logIN = New Login
If MsgBox("The application will close, are you sure you want to do it?", MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then
logIN.Show()
Else
End If
End Sub
But, because some bug I am not able to return the Login form...And this is what happen to the form after I click yes on the MessageBox. The login form crash or something
Do you have any idea how I can solve this bug?

Showing WinSCP .NET assembly transfer progress on WinForm's progress bar

Have some main form on which I am calling file downloading from FTP. When this operation is raised i want to see new form as ShowDialog and progress bar on it to be shown meantime, then show the progress and close new form and back to main form. My code is working however, when it will process is started my main form freezes and after while new form is appearing and then closing. What I would like to correct is to show this new form to be showed straightaway after process is executed. Can you take a look and tell me whats wrong?
This is out of my main form the download process called:
Dim pro As New FrmProgressBarWinscp(WinScp, myremotePicturePath, ladujZdjeciaPath, True)
FrmProgressBarWinscp is as follows:
Public Class FrmProgressBarWinscp
Property _winScp As WinScpOperation
Property _remotePicture As String
Property _ladujZdjecia As String
Property _removesource As String
Public Sub New()
InitializeComponent()
End Sub
Sub New(winscp As WinScpOperation, remotePicture As String, ladujzdjecia As String, removesource As Boolean)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_winScp = winscp
_remotePicture = remotePicture
_ladujZdjecia = ladujzdjecia
_removesource = removesource
ShowDialog()
End Sub
Sub Run()
Try
Cursor = Cursors.WaitCursor
_winScp.GetFile(_remotePicture, _ladujZdjecia, _removesource)
ProgressBar1.Minimum = 0
ProgressBar1.Maximum = 1
ProgressBar1.Value = 0
Do
ProgressBar1.Value = WinScpOperation._lastProgress
ProgressBar1.Refresh()
Loop Until ProgressBar1.Value = 1
Cursor = Cursors.Default
'Close()
Catch ex As Exception
Finally
If _winScp IsNot Nothing Then
_winScp.SessionDispose()
End If
System.Threading.Thread.Sleep(10000)
Close()
End Try
End Sub
Private Sub FrmProgressBarWinscp_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Run()
End Sub
End Class
Winscp my own class and used methods:
...
Function GetFile(source As String, destination As String, Optional removeSource As Boolean = False)
Dim result As Boolean = True
Try
session.GetFiles(source, destination, removeSource).Check()
Catch ex As Exception
result = False
End Try
Return result
End Function
Private Shared Sub SessionFileTransferProgress(sender As Object, e As FileTransferProgressEventArgs)
'Print transfer progress
_lastProgress = e.FileProgress
End Sub
Public Shared _lastProgress As Integer
...
Further discussion nr 3:
Main form:
Dim tsk As Task(Of Boolean) = Task.Factory.StartNew(Of Boolean)(Function()
Return WinScp.GetFile(myremotePicturePath, ladujZdjeciaPath, True)
End Function)
Dim forma As New FrmProgressBar
forma.ShowDialog()
Progress bar form:
Public Class FrmProgressBar
Public Sub New()
InitializeComponent()
End Sub
Sub Run()
Try
Do
ProgressBar1.Value = WinScpOperation._lastProgress
ProgressBar1.Refresh()
Loop Until ProgressBar1.Value = 1
Cursor = Cursors.Default
Catch ex As Exception
Finally
MsgBox("before sleep")
System.Threading.Thread.Sleep(10000)
MsgBox("after sleep sleep")
Close()
End Try
End Sub
Private Sub FrmProgressBarWinscp_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Run()
End Sub
End Class
Point nr. 4:
Dim tsk As Task(Of Boolean) = Task.Factory.StartNew(Of Boolean)(Function()
Return WinScp.GetFile(myremotePicturePath, ladujZdjeciaPath, True)
End Function)
Dim pic As New Waiting
pic.ShowDialog()
Task.WaitAll(tsk)
pic.Close()
Point 5:
Dim pic As New Waiting
pic.ShowDialog()
Dim tsk As Task = Task.Factory.StartNew(Sub() WinScp.GetFile(myremotePicturePath, ladujZdjeciaPath, pic, True))
Task.WaitAll(tsk)
'pic.Close()
In some other class (maybe didn't mentioned before this method is placed in diffrent class - my custom one)
Public Function GetFile(source As String, destination As String, formclose As InvokeCloseForm, Optional removeSource As Boolean = False) As Boolean
Dim result As Boolean = True
Try
session.GetFiles(source, destination, removeSource).Check()
Catch ex As Exception
result = False
End Try
formclose.RUn()
Return result
End Function
Interface:
Public Interface InvokeCloseForm
Sub RUn()
End Interface
Waiting form :
Public Class Waiting
Implements InvokeCloseForm
Public Sub RUn() Implements InvokeCloseForm.RUn
Me.Close()
End Sub
End Class
The Session.GetFiles method in blocking.
It means it returns only after the transfer finishes.
The solution is to:
Run the WinSCP transfer (the Session.GetFiles) in a separate thread, not to block the GUI thread.
For that see WinForm Application UI Hangs during Long-Running Operation
Handle the Session.FileTransferProgress event.
Though note that the event handler will be called on the background thread, so you cannot update the progress bar directly from the handler. You have to use the Control.Invoke to make sure the progress bar is updated on the GUI thread.
For that see How do I update the GUI from another thread?
A trivial implementation is below.
For a more version of the code, see WinSCP article Displaying FTP/SFTP transfer progress on WinForms ProgressBar.
Public Class ProgressDialog1
Private Sub ProgressDialog1_Load(
sender As Object, e As EventArgs) Handles MyBase.Load
' Run download on a separate thread
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf Download))
End Sub
Private Sub Download(stateInfo As Object)
' Setup session options
Dim mySessionOptions As New SessionOptions
With mySessionOptions
...
End With
Using mySession As Session = New Session
AddHandler mySession.FileTransferProgress,
AddressOf SessionFileTransferProgress
' Connect
mySession.Open(mySessionOptions)
mySession.GetFiles(<Source>, <Destination>).Check()
End Using
' Close form (invoked on GUI thread)
Invoke(New Action(Sub() Close()))
End Sub
Private Sub SessionFileTransferProgress(
sender As Object, e As FileTransferProgressEventArgs)
' Update progress bar (on GUI thread)
ProgressBar1.Invoke(
New Action(Of Double)(AddressOf UpdateProgress), e.OverallProgress)
End Sub
Private Sub UpdateProgress(progress As Double)
ProgressBar1.Value = progress * 100
End Sub
End Class
You may want to disable the progress form (or its parts) during the operation, if you want to prevent the user from doing some operations.
Use the .Enabled property of the form or control(s).
Easier, but hacky and generally not recommendable solution, is to call the Application.DoEvents method from your existing SessionFileTransferProgress handler.
And of course, you have to update the progress bar from the the SessionFileTransferProgress as well.
Private Shared Sub SessionFileTransferProgress(
sender As Object, e As FileTransferProgressEventArgs)
'Print transfer progress
ProgressBar1.Value = e.FileProgress
Application.DoEvents
End Sub
And the progress bar's .Minimum and .Maximum must be set before the Session.GetFiles.
But do not do that! That's a wrong approach.
And still, you need to disable the forms/controls the same way as in the correct solution above.

Constantly monitor if a process is running

I have the following code:
Dim p() As Process
Private Sub CheckIfRunning()
p = Process.GetProcessesByName("skype") 'Process name without the .exe
If p.Count > 0 Then
' Process is running
MessageBox.Show("Yes, Skype is running")
Else
' Process is not running
MessageBox.Show("No, Skype isn't running")
End If
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
CheckIfRunning()
End Sub
And it works GREAT!
But I'm wondering how I would convert this to a monitoring application, to constantly check if the processes is running. Is it as simple as putting the check on a timer every 1 second, or is there a better, more efficient way to go about this.
In the end result, I'd like to have a label that says "Running", or "Not Running" based on the process, but I need something to watch the process constantly.
If you need the app running all the time, then you don't need a Timer at all. Subscribe to the Process.Exited() event to be notified when it closes. For instance, with Notepad:
Public Class Form1
Private P As Process
Private FileName As String = "C:\Windows\Notepad.exe"
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim ps() As Process = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(FileName))
If ps.Length = 0 Then
P = Process.Start(FileName)
P.EnableRaisingEvents = True
AddHandler P.Exited, AddressOf P_Exited
Else
P = ps(0)
P.EnableRaisingEvents = True
AddHandler P.Exited, AddressOf P_Exited
End If
End Sub
Private Sub P_Exited(sender As Object, e As EventArgs)
Console.WriteLine("App Exited # " & DateTime.Now)
Console.WriteLine("Restarting app: " & FileName)
P = Process.Start(FileName)
P.EnableRaisingEvents = True
AddHandler P.Exited, AddressOf P_Exited
End Sub
End Class
That would keep it open all the time, assuming you wanted to open it if it wasn't already running.
If you don't want to open it yourself, and need to detect when it does open, then you could use WMI via the ManagementEventWatcher as in this previous SO question.
I've done something similar to this to monitor an exe that I need to be running all the time, and to restart it if it was down.
Mine was running as a Windows Service - that way it would start when windows booted and id never need to look after it.
Alternatively you could just create it as a console app and put it in your startup folder?
I had:
Sub Main()
Do
Check_server()
Dim t As New TimeSpan(0, 15, 0)
Threading.Thread.Sleep(t)
Loop
End Sub
Public Sub Check_server()
Dim current_pros() As Process = get_pros()
Dim found As Boolean = False
If Now.Hour < "22" Then
For Each pro In current_pros
If pro.ProcessName.ToLower = "Lorraine" Then
found = True
Exit For
Else
found = False
End If
Next
If found Then
Console.WriteLine("Server up")
Else
Console.WriteLine("Server down - restarting")
restart_server()
End If
End If
End Sub
My "server" app was called Lorraine...Also a timer maybe better practice than having the thread sleep..
From my experience, a simple timer works best:
'Timer interval set to 1-5 seconds... no remotely significant CPU hit
Private Sub timerTest_Tick(sender As System.Object, e As System.EventArgs) Handles timerTest.Tick
Dim p() As Process = Process.GetProcessesByName("Skype")
lblStatus.Text = If(p.Length > 0, "Skype is running.", "Skype isn't running.")
End Sub
Your mileage may vary, but I don't like to deal with separate threads unless necessary.

Move file in Windows-Service

This is my first time making a windows service app. I'm trying to move files from one folder to another using a windows service app. It'll do so every 10 seconds.
This is the code I'm using. It works when I use it on a windows form app but doesn't work when I use it on a windows-service app.
The code in the Timer1_Tick works if I use it in OnStart. But doesn't work in the timer.
Protected Overrides Sub OnStart(ByVal args() As String)
Timer1.Enabled = True
End Sub
Protected Overrides Sub OnStop()
Timer1.Enabled = False
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Dim FileToMove As String
Dim MoveLocation As String
Dim count1 As Integer = 0
Dim files() As String = Directory.GetFiles("C:\Documents and Settings\dmmc.operation\Desktop\Q8")
Dim pdfFiles(100) As String
For i = 0 To files.Length - 1
If Path.GetExtension(files(i)) = ".pdf" Then
pdfFiles(count1) = files(i)
count1 += 1
End If
Next
For i = 0 To pdfFiles.Length - 1
If pdfFiles(i) <> "" Then
FileToMove = pdfFiles(i)
MoveLocation = "C:\Documents and Settings\dmmc.operation\Desktop\Output\" + Path.GetFileName(pdfFiles(i))
If File.Exists(FileToMove) = True Then
File.Move(FileToMove, MoveLocation)
End If
End If
Next
End Sub
Windows.Forms.Timer won't work without a Form instantiated. You should be using System.Timers.Timer instead:
Private WithEvents m_timer As System.Timers.Timer
Protected Overrides Sub OnStart(ByVal args() As String)
m_timer = New System.Timers.Timer(1000) ' 1 second
m_timer.Enabled = True
End Sub
Private Sub m_timer_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles m_timer.Elapsed
m_timer.Enabled = False
'Do your stuff here
m_timer.Enabled = True
End Sub
I've also build a service that moves files that are dropped into a folder but I'm using the FileSystemWatcher. The FileSystemWatcher allows me to move the file when a new one is created, create an entry in a SQL Database and send an email notification when it's all done.
This is how I have setup the FileSystemWatcher
Set a folder to watch: watcher.Path = "C:\Folder to Watch"
Set the file type to watch: watcher.Filter = "*.pdf".
The type of event to watch and the Method that gets triggered: watcher.Created += new FileSystemEventHandler(OnChanged);
Lastly, I need to enable the event: watcher.EnableRaisingEvents = true;
Unfortunately, I have instances on a daily basis where the files do not get moved successfully. I get IO exceptions for file in use. The files get copied but the file in the destination folder is 0kb.
I'm trying to troubleshoot it and I've manage to debug remotely but I still haven't figured out what I am doing wrong.
The most common error I get is: Error: System.IO.IOException: The process cannot access the file 'fileName.pdf' because it is being used by another process.
this error does not make sense as the file did not exist prior to my service trying to move it...
Any further help would be appreciated.