Click here to Skip to main content
15,885,278 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
Hi
I have written a .net application which, up till recently, was installed on a XP PC and run under Windows Scheduler. That worked perfectly.

We have just migrated the app to a Windows 7 PC on which runs perfectly when run as astandard app. Click it and it runs.

However when I try and run it via teh Win 7 Scheduler, it does not work correctly.

In part, the code it uses 'System.Diagnostics.Process' to start ffmpeg.exe as an external process.
This is where Scheduler is misbehaving and not letting the external process run.
As I said, it runs perfectly on Win 7, just not under Scheduler.

I guess this is a permissions/security thing but what to do about it?
The schedlue settings have the task runningwith teh highest priveledges.

Anyone have any experience with this at all?

Regards
Malcom
Posted
Comments
Sushil Mate 13-Sep-12 3:15am    
How about setting the permission for your applications & ffmpeg.exe to everyone user.
Properties -> Security -> Add, There you can add Everyone user & assign all the rights to him(read/write/execute).

restarts the system & try to run under scheduler.
malcomm 16-Sep-12 19:45pm    
No luck with that one. Behaves thesame.
Thanks anyway...

Seems like you need to run the process as the logged in user. This code should do it (drop it in a new module):

VB
Imports System.Runtime.InteropServices

Module modProcessAsUser

    <structlayout(layoutkind.sequential)> _
    Private Structure STARTUPINFO
        Public cb As Int32
        Public lpReserved As String
        Public lpDesktop As String
        Public lpTitle As String
        Public dwX As Int32
        Public dwY As Int32
        Public dwXSize As Int32
        Public dwYSize As Int32
        Public dwXCountChars As Int32
        Public dwYCountChars As Int32
        Public dwFillAttribute As Int32
        Public dwFlags As Int32
        Public wShowWindow As Short
        Public cbReserved2 As Short
        Public lpReserved2 As IntPtr
        Public hStdInput As IntPtr
        Public hStdOutput As IntPtr
        Public hStdError As IntPtr
    End Structure

    <structlayout(layoutkind.sequential)> _
    Friend Structure PROCESS_INFORMATION
        Public hProcess As IntPtr
        Public hThread As IntPtr
        Public dwProcessId As Int32
        Public dwThreadId As Int32
    End Structure

    <structlayout(layoutkind.sequential)> _
    Friend Structure SECURITY_ATTRIBUTES
        Public nLength As Int32
        Public lpSecurityDescriptor As IntPtr
        Public bInheritHandle As Boolean
    End Structure

    Public Enum SECURITY_IMPERSONATION_LEVEL
        SecurityAnonymous
        SecurityIdentification
        SecurityImpersonation
        SecurityDelegation
    End Enum

    Public Enum TOKEN_TYPE
        TokenPrimary = 1
        TokenImpersonation
    End Enum

    Private Declare Auto Function CreateProcessAsUser Lib "advapi32" ( _
            ByVal hToken As IntPtr, _
            ByVal strApplicationName As String, _
            ByVal strCommandLine As String, _
            ByRef lpProcessAttributes As SECURITY_ATTRIBUTES, _
            ByRef lpThreadAttributes As SECURITY_ATTRIBUTES, _
            ByVal bInheritHandles As Boolean, _
            ByVal dwCreationFlags As Integer, _
            ByVal lpEnvironment As IntPtr, _
            ByVal lpCurrentDriectory As String, _
            ByRef lpStartupInfo As STARTUPINFO, _
            ByRef lpProcessInformation As PROCESS_INFORMATION) As Boolean

    Declare Function DuplicateTokenEx Lib "advapi32.dll" (ByVal hExistingToken As IntPtr, _
        ByVal dwDesiredAccess As System.UInt32, ByRef lpThreadAttributes As SECURITY_ATTRIBUTES, _
        ByVal ImpersonationLevel As Int32, ByVal dwTokenType As Int32, ByRef phNewToken As IntPtr) As Boolean

    Declare Function CreateEnvironmentBlock Lib "userenv.dll" (ByRef lpEnvironment As IntPtr, _
                                                ByVal hToken As IntPtr, ByVal bInherit As Boolean) As Boolean

    Public Class ProcessAsUser

        Private Const SE_FILE_OBJECT = 1&
        Private Const OWNER_SECURITY_INFORMATION = &H1
        Private Const NAME_SIZE = 64
        Private Const SID_SIZE = 32
        Private Const STARTF_USESHOWWINDOW As Integer = &H1&
        Private Const STARTF_FORCEONFEEDBACK = &H40
        Private Const SW_SHOW = 5
        Private Const CREATE_UNICODE_ENVIRONMENT As Long = &H400&
        Private Const TOKEN_DUPLICATE = &H2
        Private Const TOKEN_ASSIGN_PRIMARY = &H1
        Private Const TOKEN_QUERY = &H8


        Private Declare Function OpenProcessToken Lib "advapi32.dll" ( _
        ByVal ProcessHandle As IntPtr, _
        ByVal DesiredAccess As Integer, _
        ByRef TokenHandle As IntPtr) As Boolean

        Declare Function DestroyEnvironmentBlock Lib "userenv.dll" (ByVal lpEnvironment As IntPtr) As Boolean

        Private Shared Function LaunchProcessAsUser(ByVal cmdLine As String, ByVal token As IntPtr, ByVal envBlock As IntPtr) As Boolean
            Dim result As Boolean = False
            Dim pi As PROCESS_INFORMATION = New PROCESS_INFORMATION
            Dim saProcess As SECURITY_ATTRIBUTES = New SECURITY_ATTRIBUTES
            Dim saThread As SECURITY_ATTRIBUTES = New SECURITY_ATTRIBUTES

            saProcess.nLength = Runtime.InteropServices.Marshal.SizeOf(saProcess)
            saThread.nLength = Runtime.InteropServices.Marshal.SizeOf(saThread)
            Dim si As STARTUPINFO = New STARTUPINFO
            si.cb = Runtime.InteropServices.Marshal.SizeOf(si)
            si.lpDesktop = Nothing
            si.wShowWindow = 1
            si.dwFlags = STARTF_USESHOWWINDOW Or STARTF_FORCEONFEEDBACK
            si.wShowWindow = SW_SHOW
            result = CreateProcessAsUser(token, cmdLine, "", saProcess, saThread, False, CREATE_UNICODE_ENVIRONMENT, envBlock, Nothing, si, pi)
            If result = False Then
                Dim iError As Integer = Runtime.InteropServices.Marshal.GetLastWin32Error
                Dim message As String = String.Format("CreateProcessAsUser Error: {0}", iError)
                Debug.WriteLine(message)
            End If
            Return result
        End Function

        Private Shared Function GetPrimaryToken(ByVal processId As Integer) As IntPtr
            Dim token As IntPtr = IntPtr.Zero
            Dim primaryToken As IntPtr = IntPtr.Zero
            Dim retVal As Boolean = False
            Dim p As Process = Nothing
            Try
                p = Process.GetProcessById(processId)
            Catch generatedExceptionVariable0 As ArgumentException
                Dim details As String = String.Format("ProcessID {0} Not Available", processId)
                Debug.WriteLine(details)
                Return primaryToken
            End Try
            retVal = OpenProcessToken(p.Handle, TOKEN_DUPLICATE, token)
            If retVal = True Then
                Dim sa As SECURITY_ATTRIBUTES = New SECURITY_ATTRIBUTES
                sa.nLength = Runtime.InteropServices.Marshal.SizeOf(sa)
                retVal = DuplicateTokenEx(token, TOKEN_ASSIGN_PRIMARY Or TOKEN_DUPLICATE Or TOKEN_QUERY, sa, CType(SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, Integer), CType(TOKEN_TYPE.TokenPrimary, Integer), primaryToken)
                If retVal = False Then
                    Dim message As String = String.Format("DuplicateTokenEx Error: {0}", Marshal.GetLastWin32Error)
                    Debug.WriteLine(message)
                End If
            Else
                Dim message As String = String.Format("OpenProcessToken Error: {0}", Marshal.GetLastWin32Error)
                Debug.WriteLine(message)
            End If
            Return primaryToken
        End Function

        Private Shared Function GetEnvironmentBlock(ByVal token As IntPtr) As IntPtr
            Dim envBlock As IntPtr = IntPtr.Zero
            Dim retVal As Boolean = CreateEnvironmentBlock(envBlock, token, False)
            If retVal = False Then
                Dim message As String = String.Format("CreateEnvironmentBlock Error: {0}", Marshal.GetLastWin32Error)
                Debug.WriteLine(message)
            End If
            Return envBlock
        End Function

        Public Shared Function Launch(ByVal appCmdLine As String, Optional ByVal processName As String = "explorer") As Boolean
            Dim ret As Boolean = False
            Dim ps As Process() = Process.GetProcessesByName(processName)
            Dim processId As Integer = -1
            If ps.Length > 0 Then
                processId = ps(0).Id
            End If
            If processId > 1 Then
                Dim token As IntPtr = GetPrimaryToken(processId)
                If Not (token.Equals(IntPtr.Zero)) Then
                    Dim envBlock As IntPtr = GetEnvironmentBlock(token)
                    ret = LaunchProcessAsUser(appCmdLine, token, envBlock)
                    If Not (envBlock.Equals(IntPtr.Zero)) Then
                        DestroyEnvironmentBlock(envBlock)
                    End If
                End If
            End If
            Return ret
        End Function
    End Class
End Module


Use the "Launch" method. Hope this helps.

- Pete
 
Share this answer
 
v2
Comments
malcomm 16-Sep-12 19:45pm    
Whew... and thanks

OK, I had a crack at this but now I'm lost.
As I understand it, the 'Launch' method looks for aprocess match in processes that are already running?
How do I tell it to start ffmpeg?

This is tehcode I currently use to run ffmpeg-
targetEXE is something like d:\xxx\ffmpeg.exe
arguments is something like " -vf scale=720:576 -f mov -vcodec dvvideo -r 25 -pix_fmt yuv411p -vtag dvpp -y " + outputFileName

Private Function ExecuteDosCommand(ByVal targetEXE As String, ByVal arguments As String, ByVal fileName As String) As Integer
Dim iCount As Integer = 0
Dim iExitCode As Integer = 0
Using processTranscode As New System.Diagnostics.Process
processTranscode.StartInfo.UseShellExecute = False
processTranscode.StartInfo.RedirectStandardOutput = True
processTranscode.StartInfo.RedirectStandardError = True
processTranscode.StartInfo.CreateNoWindow = True
processTranscode.StartInfo.WindowStyle = ProcessWindowStyle.Normal '.Hidden
processTranscode.StartInfo.FileName = targetEXE
processTranscode.StartInfo.Arguments = arguments
Try
processTranscode.Start()

Dim readLine As String = ""
Dim sErr As System.IO.StreamReader = processTranscode.StandardError()
Do While Not sErr.EndOfStream
readLine = sErr.ReadLine
If Not bMinimised Then
If readLine.Contains("frame") And readLine.Contains("fps") Then
Dim i1 As Integer = readLine.IndexOf("fps")
Dim s As String = readLine.Substring(0, i1)
s = "Transcoding " & fileName & " - " & s
Me.lblStatus.Text = s
Me.Refresh()
Application.DoEvents()
End If
End If
If processTranscode.HasExited Then Exit Do
Loop
sErr.Close()
Catch ex As Exception
Try
processTranscode.Close()
Catch ex1 As Exception
'
End Try
Return 6
End Try
killProc: If Not processTranscode.HasExited Then
processTranscode.Kill()
End If
Application.DoEvents()
iExitCode = processTranscode.ExitCode
processTranscode.Close()
End Using
Return iExitCode
End Function 'ExecuteDosCommand


regards
Malcom
pdoxtader 16-Sep-12 20:15pm    
Did you try putting it all in the application command line? I believe that's the way I set it up. If not, the code can be easily modified to accept a command line... If you haven't tried it, give it a shot ( Ie: "c:\yourApp.exe -commands"). If it doesn't work, let me know and I'll post the modified code.

And yes, it looks for explorer.exe by default, and starts your app as the user that started explorer.exe. You can specify a different application if you like. Use like this:

Launch("c:\yourApp.exe -commands")

Again - if this doesn't work, let me know and I'll modify it to accept a command line for you.

- Pete
malcomm 16-Sep-12 20:43pm    
I placed your code into a module in my existing app.
No, I screwed up and just placed the command variables into the appcmdline and the assumed the process was determined by the process name variable. doh!
Changed that now but I'm still having the same result.

Withing the Launch function, the appCmdLine variable is coming through as
"C:\Users\Malcom\Documents\Visual Studio 2008\Projects\WebCams\TAWN WebCams QLD\WebCam_Archerfield_NNE\bin\Debug\ffmpeg.exe -r 15 -i "D:\fcp_buffer\webcams\kalbar\%04d.jpg" -vf "[in]crop=in_w:in_h-20:0:0,scale=1024:576,drawtext=fontfile=TWCAB___.TTF:text='Archerfield, QLD':x=129:y=54:fontsize=25:fontcolor=white:shadowcolor=0x000000EE:shadowx=1.5:shadowy=1.5, drawtext=fontfile=TWCAB___.TTF:text='Monday':x=129:y=82:fontsize=16:fontcolor=white:shadowcolor=0x000000EE:shadowx=1:shadowy=1[out]" -b 6000k -s 720x576 -vcodec wmv2 -an -y "D:\fcp_buffer\webcams\kalbar\Archerfield_NNE.wmv""

The process name variable is set to "ffmpeg"

Stepping through-
ps.Length = 0
processId = -1
and obviously it returns false.

What else have I misunderstood :)
pdoxtader 16-Sep-12 20:53pm    
Wow... lol. Look at that command line. It's hard to make any sense of... but the 1st thing that comes to mind is that you have quotes in it. How exactly are you passing them? That may be causing you a problem.

But just in case that's not it, I'll post the modified module in a few minutes.

- Pete
malcomm 16-Sep-12 21:04pm    
Yup, hell of a command line but it is passing the app name + vaiables so should be ok.
Exactly the same details as what my ExecuteDosCommand function above handles.
Yes the quotes are part of it as created (in part)via this -
Dim strOptions As String = " -vf " & Chr(34) & "[in]crop=in_w:in_h-20:0:0,scale=1024:576 etc etc
So the quotes appear from ' & Chr(34) & '
Here ya go - tested it just a minute ago. Use like this:

ProcessAsUser.Launch("c:\YourApp.exe", "-Your -commands")

VB
Imports System.Runtime.InteropServices

Module modProcessAsUser

    <structlayout(layoutkind.sequential)> _
    Private Structure STARTUPINFO
        Public cb As Int32
        Public lpReserved As String
        Public lpDesktop As String
        Public lpTitle As String
        Public dwX As Int32
        Public dwY As Int32
        Public dwXSize As Int32
        Public dwYSize As Int32
        Public dwXCountChars As Int32
        Public dwYCountChars As Int32
        Public dwFillAttribute As Int32
        Public dwFlags As Int32
        Public wShowWindow As Short
        Public cbReserved2 As Short
        Public lpReserved2 As IntPtr
        Public hStdInput As IntPtr
        Public hStdOutput As IntPtr
        Public hStdError As IntPtr
    End Structure

    <structlayout(layoutkind.sequential)> _
    Friend Structure PROCESS_INFORMATION
        Public hProcess As IntPtr
        Public hThread As IntPtr
        Public dwProcessId As Int32
        Public dwThreadId As Int32
    End Structure

    <structlayout(layoutkind.sequential)> _
    Friend Structure SECURITY_ATTRIBUTES
        Public nLength As Int32
        Public lpSecurityDescriptor As IntPtr
        Public bInheritHandle As Boolean
    End Structure

    Public Enum SECURITY_IMPERSONATION_LEVEL
        SecurityAnonymous
        SecurityIdentification
        SecurityImpersonation
        SecurityDelegation
    End Enum

    Public Enum TOKEN_TYPE
        TokenPrimary = 1
        TokenImpersonation
    End Enum

    Private Declare Auto Function CreateProcessAsUser Lib "advapi32" ( _
            ByVal hToken As IntPtr, _
            ByVal strApplicationName As String, _
            ByVal strCommandLine As String, _
            ByRef lpProcessAttributes As SECURITY_ATTRIBUTES, _
            ByRef lpThreadAttributes As SECURITY_ATTRIBUTES, _
            ByVal bInheritHandles As Boolean, _
            ByVal dwCreationFlags As Integer, _
            ByVal lpEnvironment As IntPtr, _
            ByVal lpCurrentDriectory As String, _
            ByRef lpStartupInfo As STARTUPINFO, _
            ByRef lpProcessInformation As PROCESS_INFORMATION) As Boolean

    Declare Function DuplicateTokenEx Lib "advapi32.dll" (ByVal hExistingToken As IntPtr, _
        ByVal dwDesiredAccess As System.UInt32, ByRef lpThreadAttributes As SECURITY_ATTRIBUTES, _
        ByVal ImpersonationLevel As Int32, ByVal dwTokenType As Int32, ByRef phNewToken As IntPtr) As Boolean

    Declare Function CreateEnvironmentBlock Lib "userenv.dll" (ByRef lpEnvironment As IntPtr, _
                                                ByVal hToken As IntPtr, ByVal bInherit As Boolean) As Boolean

    Public Class ProcessAsUser

        Private Const SE_FILE_OBJECT = 1&
        Private Const OWNER_SECURITY_INFORMATION = &H1
        Private Const NAME_SIZE = 64
        Private Const SID_SIZE = 32
        Private Const STARTF_USESHOWWINDOW As Integer = &H1&
        Private Const STARTF_FORCEONFEEDBACK = &H40
        Private Const SW_SHOW = 5
        Private Const CREATE_UNICODE_ENVIRONMENT As Long = &H400&
        Private Const TOKEN_DUPLICATE = &H2
        Private Const TOKEN_ASSIGN_PRIMARY = &H1
        Private Const TOKEN_QUERY = &H8


        Private Declare Function OpenProcessToken Lib "advapi32.dll" ( _
        ByVal ProcessHandle As IntPtr, _
        ByVal DesiredAccess As Integer, _
        ByRef TokenHandle As IntPtr) As Boolean

        Declare Function DestroyEnvironmentBlock Lib "userenv.dll" (ByVal lpEnvironment As IntPtr) As Boolean

        Private Shared Function LaunchProcessAsUser(ByVal cmdLine As String, ByVal token As IntPtr, ByVal envBlock As IntPtr, Optional cmds As String = "") As Boolean
            Dim result As Boolean = False
            Dim pi As PROCESS_INFORMATION = New PROCESS_INFORMATION
            Dim saProcess As SECURITY_ATTRIBUTES = New SECURITY_ATTRIBUTES
            Dim saThread As SECURITY_ATTRIBUTES = New SECURITY_ATTRIBUTES

            saProcess.nLength = Runtime.InteropServices.Marshal.SizeOf(saProcess)
            saThread.nLength = Runtime.InteropServices.Marshal.SizeOf(saThread)
            Dim si As STARTUPINFO = New STARTUPINFO
            si.cb = Runtime.InteropServices.Marshal.SizeOf(si)
            si.lpDesktop = Nothing
            si.wShowWindow = 1
            si.dwFlags = STARTF_USESHOWWINDOW Or STARTF_FORCEONFEEDBACK
            si.wShowWindow = SW_SHOW
            result = CreateProcessAsUser(token, cmdLine, " " & cmds, saProcess, saThread, False, CREATE_UNICODE_ENVIRONMENT, envBlock, Nothing, si, pi)
            If result = False Then
                Dim iError As Integer = Runtime.InteropServices.Marshal.GetLastWin32Error
                Dim message As String = String.Format("CreateProcessAsUser Error: {0}", iError)
                Debug.WriteLine(message)
            End If
            Return result
        End Function

        Private Shared Function GetPrimaryToken(ByVal processId As Integer) As IntPtr
            Dim token As IntPtr = IntPtr.Zero
            Dim primaryToken As IntPtr = IntPtr.Zero
            Dim retVal As Boolean = False
            Dim p As Process = Nothing
            Try
                p = Process.GetProcessById(processId)
            Catch generatedExceptionVariable0 As ArgumentException
                Dim details As String = String.Format("ProcessID {0} Not Available", processId)
                Debug.WriteLine(details)
                Return primaryToken
            End Try
            retVal = OpenProcessToken(p.Handle, TOKEN_DUPLICATE, token)
            If retVal = True Then
                Dim sa As SECURITY_ATTRIBUTES = New SECURITY_ATTRIBUTES
                sa.nLength = Runtime.InteropServices.Marshal.SizeOf(sa)
                retVal = DuplicateTokenEx(token, TOKEN_ASSIGN_PRIMARY Or TOKEN_DUPLICATE Or TOKEN_QUERY, sa, CType(SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, Integer), CType(TOKEN_TYPE.TokenPrimary, Integer), primaryToken)
                If retVal = False Then
                    Dim message As String = String.Format("DuplicateTokenEx Error: {0}", Marshal.GetLastWin32Error)
                    Debug.WriteLine(message)
                End If
            Else
                Dim message As String = String.Format("OpenProcessToken Error: {0}", Marshal.GetLastWin32Error)
                Debug.WriteLine(message)
            End If
            Return primaryToken
        End Function

        Private Shared Function GetEnvironmentBlock(ByVal token As IntPtr) As IntPtr
            Dim envBlock As IntPtr = IntPtr.Zero
            Dim retVal As Boolean = CreateEnvironmentBlock(envBlock, token, False)
            If retVal = False Then
                Dim message As String = String.Format("CreateEnvironmentBlock Error: {0}", Marshal.GetLastWin32Error)
                Debug.WriteLine(message)
            End If
            Return envBlock
        End Function

        Public Shared Function Launch(ByVal appCmdLine As String, Optional cmds As String = "", Optional ByVal processName As String = "explorer") As Boolean
            Dim ret As Boolean = False
            Dim ps As Process() = Process.GetProcessesByName(processName)
            Dim processId As Integer = -1
            If ps.Length > 0 Then
                processId = ps(0).Id
            End If
            If processId > 1 Then
                Dim token As IntPtr = GetPrimaryToken(processId)
                If Not (token.Equals(IntPtr.Zero)) Then
                    Dim envBlock As IntPtr = GetEnvironmentBlock(token)
                    ret = LaunchProcessAsUser(appCmdLine, token, envBlock, cmds)
                    If Not (envBlock.Equals(IntPtr.Zero)) Then
                        DestroyEnvironmentBlock(envBlock)
                    End If
                End If
            End If
            Return ret
        End Function
    End Class
End Module


Hope this helps.

- Pete
 
Share this answer
 
v2
Comments
malcomm 16-Sep-12 21:42pm    
Thanks Pete
back soon :)
malcomm 16-Sep-12 22:52pm    
OK, I'm an idiot.

I didn't realise you are using 'explorer' as a way to get the logged on users identity. I had replaced it with ffmpeg.

OK, well unfortunately still no luck.
Within LaunchProcessAsUser, result = CreateProcessAsUser... is returning false

It all runs fine when not under Scheduler control.

Don't give up me now Pete :)
pdoxtader 17-Sep-12 0:19am    
I don't understand what's going wrong. Can you start an application using my code and the scheduler WITHOUT adding your command line?
pdoxtader 17-Sep-12 0:31am    
You know, when I need to add items to the task scheduler, I use this free library. It works great. You can add items to the scheduler to be run as the logged in user, the system account, or you can specify a user. I've used this in the past, and specified command line parameters while creating task scheduler items on windows 7. Have a look... I think this may be your solution. By the way - codeplex.com appears to be down right now... if the link doesn't work, check again later or tomorrow.

Hope this helps,

- Pete

P.S. Here's a codeproject article about the same library. You'll still need codeplex.com to be up to download anything, but at least you can read about it.
malcomm 17-Sep-12 1:24am    
I ran it again with a command of 'ProcessAsUser.Launch("C:\Windows\notepad.exe")'.
Same results with the LaunchProcessAsUser function-
result = CreateProcessAsUser etc is returning false

I'll try the library as well as I have some 70 scheduled items that need to be manually entered if I ever get the roitten thing working. That'll save me a ton of time as I've had no luck creating schedule tasks in code so far.
Thanks
Malcom
Well, despite the excellent advise received, I have failed completely to get the Win7 Scheduler to do the job its made for.
A simple vb.net app that calls upon ffmpeg as a process. I can ffmpeg to fire up but its internal processes are now failing due to the Win7 Schedulers security crap.

In desperation and a lack of time to explore a code solution, I have found a replacement Scheduler which just works and is easy to use. It does not suffer any of the Win7 security crap and therefore saves me having to write a scheduler myself.

SOLWAY'S TASK SCHEDULER
http://www.theabsolute.net/sware/#tasksched

Hope this helps someone.
 
Share this answer
 
Honestly, I think you should save yourself the trouble and use the library. Here's an example using the library pulled from one of my projects. Using the task scheduler library, and the code below you can schedule an application to run, specify a command line, and determine the interval in minutes using a numeric up-down control (nudMinutes):

VB
Dim startTime   As Date     = dtSchedulerTimeOfDay.Value
Dim machineName As String   = Nothing
Dim userName    As String   = Nothing
Dim password    As String   = Nothing
Dim domain      As String   = Nothing
Dim appPath     As String   = System.Reflection.Assembly.GetExecutingAssembly().Location
Dim trigger     As New TimeTrigger()

' ...machineName, userName, password, and domain are set here...

Using ts As New TaskService(machineName, userName, domain, password, False)
   Dim td As TaskDefinition    = ts.NewTask()

   With trigger
      .StartBoundary          = dtSchedulerTimeOfDay.Value.AddMinutes(1)
      .Repetition.Interval    = TimeSpan.FromMinutes(nudMinutes.Value)
      .Repetition.Duration    = TimeSpan.FromMinutes(nudMinutes.Value)
   End With

   With td
      .RegistrationInfo.Description   = "Diamond copy job - " & selectedJobNameForScheduling
      .Principal.LogonType            = TaskLogonType.InteractiveToken
      .Triggers.Add(trigger)
      .Actions.Add(New ExecAction(appPath, "-RunJob=" & """" & selectedJobNameForScheduling & """", Nothing))
   End With

   If cbSchedUser.SelectedItem Is cbSchedUser.Items(0) Then ' Run as system
     ts.RootFolder.RegisterTaskDefinition("Diamond copy job - " & selectedJobNameForScheduling, td, _
        TaskCreation.Create, "SYSTEM", Nothing, TaskLogonType.ServiceAccount)
   ElseIf cbSchedUser.SelectedItem Is cbSchedUser.Items(1) Then ' Run as the currently logged in user
     ts.RootFolder.RegisterTaskDefinition("Diamond copy job - " & selectedJobNameForScheduling, td)
   ElseIf cbSchedUser.SelectedItem Is cbSchedUser.Items(2) Then ' Specify a user
     ts.RootFolder.RegisterTaskDefinition("Diamond copy job - " & selectedJobNameForScheduling, td, _
        TaskCreation.Create, userName, password, TaskLogonType.InteractiveTokenOrPassword)
   End If
End Using


Using this code, you can also specify how it's run - as the system account, as the currently logged in user, or as a specified user.

- Hope this helps...

- Pete
 
Share this answer
 
v3
Comments
malcomm 19-Sep-12 20:40pm    
Hi Pete

Your code has given me some vaulable knowledge but unfortunately has failed to give me a solution to the Win7 Schduler crap.
I've gotten it to point where Win7 scheduler will fire up ffmpeg from within my app but ffmpeg is now failing with its internal processes...

I'm going to replace the Win7 scheduler and see how that goes.

Many thanks for all help. Your ideas have already been incorporated into another project of mine so are greatly appreciated.

regards
Malcom
pdoxtader 19-Sep-12 21:06pm    
Well, I'm glad you found a solution, even if it wasn't the one I provided. I have to say I'm surprised though... I would have thought the security options available in the library I recommended would cover just about any situation.

Yes, some of the code I provided has some other cool possible uses... Glad your getting some use out of it.

- Pete
Hi
I’ve revisited this issue as my current solution of replacing the Windows 7 Scheduler with the Solway one has reached its limits. Solway doesn’t cater for 30 second start times plus it has a few other bugs.

Just to recap, my application is written in VB2008, net 3, running under Win 7 64bit (haven’t tried 32b).
The app grabs an image from the internet, does some scaling and then exports it as WMV and MOV files. To do this it utilises the external FFmpeg.exe which it handles via System.Diagnostics.Process.
The app works perfectly in a standalone mode but fails when run as a task under the Windows 7 Scheduler. It seems that WinSched cannot cope with firing FFmpeg via System.Diagnostics.Process. It opens a CMD window but fails to deal with FFmpeg. Appears to be permissions issues
It works perfectly under WinXP’s scheduler.

I’ve since written my own scheduler which attempts to run the app from another System.Diagnostics.Process but it fails in the same way.


So, for those of you who love a challenge; see if you can get the Win7 Sched to run FFmpeg from within my app.
You can download a fully working cut down version of my app which demonstrates the problem. The download is the entire project folder and includes ffmpeg. Get it here.
You’ll need to create one folder for file output. I’ve used D:\ProcessTest\Buffer but its configurable from the app. The output folder will have the web images saved into it along with the wmv and mov files.

Briefly, the project workflow is –
1/ click start
2/ grab the web image
3/ resize the image
4/ make the base wmv and mov files
5/ ExecuteDosCommand - System.Diagnostics.Process shell
6/ close

Run the app and you’ll see a CMD window pop up then the ffmpeg window. They go away quickly.
Set up a Win Schedule and no ffmpeg data will be shown.

Hopefully someone will take up the challenge and figure out how to make the scheduler work with external apps.

Let me know if you need more info.
 
Share this answer
 
v2
Comments
Mike Meinz 27-Jun-13 19:43pm    
I don't have the time to debug your app for you but I will include source code from my app that works fine. Also, in the future please post follow-ups by using Improve Question or post a new question.


Private Function ConvertVideo() As Boolean
Dim PROGRAMNAME As String = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly.Location), "ffmpeg.exe")
Dim startinfo As New System.Diagnostics.ProcessStartInfo
Dim strStandardError As StreamReader
Dim strProgramOutputLine As String
Dim bStartLogging As Boolean = False
Dim strLastProgramOutputLine As String = ""
Dim strSeekOption As String
Dim strVideoBitRate As String
Dim strAudioBitRate As String

ConvertVideo = True
Try
File.Delete(strOutputFullPathName)
Catch
End Try
Dim p As New Process
Dim bOK As Boolean = False
Try
AddToLog("*** Beginning Video Conversion step")
'
' Build ffmpeg command line parameters
'
If nbrHours.Value + nbrMinutes.Value + nbrSeconds.Value <> 0 Then
' Amount of time to skip at the beginning of the video
strSeekOption = "-ss " & Format(nbrHours.Value, "00") & ":" & Format(nbrMinutes.Value, "00") & ":" & Format(nbrSeconds.Value, "00") & " "
Else
strSeekOption = ""
End If
If intVideoBitRate > 0 Then
strVideoBitRate = "-b:v " & intVideoBitRate.ToString & "k"
Else
strVideoBitRate = ""
End If
If intAudioBitRate > 0 Then
strAudioBitRate = "-b:a " & intAudioBitRate.ToString & "k"
Else
strAudioBitRate = ""
End If
With startinfo
.UseShellExecute = False
.RedirectStandardError = True
.CreateNoWindow = True
.FileName = PROGRAMNAME
.Arguments = "-i " & _
Path.Combine(strSourceDrive, DVDPath, cmbVOBFiles.SelectedItem.ToString) & _
" -f avi -codec:v mpeg4 " & strVideoBitRate & " -g 300 -codec:a libmp3lame " & strAudioBitRate & " " & _
strSeekOption & _
strOutputFullPathName
' " -f avi -codec:v mpeg4 -b:v 4910k -g 300 -codec:a libmp3lame -b:a 128k " & <-- Good Quality
' " -f avi -codec:v mpeg4 -codec:a libmp3lame " & <-- Did not have as good quality
End With
p.StartInfo = startinfo
p.Start()
strStandardError = p.StandardError
While Not strStandardError.EndOfStream
Application.DoEvents()
strProgramOutputLine = strStandardError.ReadLine
Application.DoEvents()
'
' Start including the log at the line after "libpostproc".
If strProgramOutputLine.IndexOf("libpostproc") > -1 AndAlso Not bStartLogging Then
bStartLogging = True
ElseIf bStartLogging Then
If strProgramOutputLine.Length > 5 AndAlso strProgramOutputLine.Substring(0, 5) = "Press" Then
' Do not include this line in the log!
Else
' If a consecutive "frame=" line, then overwrite previous log entry
AddToLog(strProgramOutputLine, _
CBool(strProgramOutputLine.Length > 6 AndAlso _
strLastProgramOutputLine.Length > 6 AndAlso _
strProgramOutputLine.Substring(0, 6) = "frame="))
Mike Meinz 27-Jun-13 19:44pm    

End If
Application.DoEvents()
strLastProgramOutputLine = strProgramOutputLine
End If
End While
p.WaitForExit()
bOK = True
AddToLog("*** Ending Video Conversion step")
'
' Now get the size of the input file and the output file and display them.
'
Dim fiInput As FileInfo = Nothing
Dim fiOutput As FileInfo = Nothing
Try
fiInput = New FileInfo(Path.Combine(strSourceDrive, DVDPath, cmbVOBFiles.SelectedItem.ToString))
Catch
End Try
Try
fiOutput = New FileInfo(strOutputFullPathName)
Catch
End Try
If Not fiInput Is Nothing Then
AddToLog(fiInput.FullName & " (size: " & Format(fiInput.Length, "###,###,###,###,###,###") & ")")
End If
If Not fiOutput Is Nothing Then
Try
AddToLog("Converted file size: " & Format(fiOutput.Length, "###,###,###,###,###,###") & ")")
Catch
AddToLog("ERROR: Video Conversion Failed - Unable to find the converted file.")
ConvertVideo = False
End Try
End If
Catch ex As Exception
AddToLog("ERROR: " & ex.Message & " - ConvertVideo")
ConvertVideo = False
Finally
p.Dispose()
End Try
End Function
Mike Meinz 27-Jun-13 19:52pm    
By the way, the most likley cause of your problem is that one of the filenames passed in the parameter to ffmpeg.exe can't be found. Probably because it lacks a full path or the path is incorrect in some way. My code above captures the output from the execution of ffmpeg.exe. That can be very helpful to solving the issue.
malcomm 1-Jul-13 16:58pm    
Thanks Mike
Not sure if you got the bit about my app working perfectly UNTIL it is run under Win7 Scheduler?
Not really much difference in our code when it all boils down except that I am referencing a font via 'drawtext' which you do not. Good idea about capturing the entire error stream, didn't think of that - I was only capturing the frame rate for progress indication. When I capture the entire stream, it shows a failure loading the font -
[drawtext @ 01A39000] Could not load fontface from file 'TWCAB___.TTF': cannot open resource
Error initializing filter 'drawtext' with args 'fontfile=TWCAB___.TTF:text=Linley Point, NSW:x=790:y=96:fontsize=20:fontcolor=white:shadowcolor=0x000000EE:shadowx=1.5:shadowy=1.5'
Error opening filters!

The font is in the bin folder where it should be and therefore supposedly doesn't need a path specified. Remember it works fine in standalone and this problem only occurs under the Scheduler.
Well you can't just give it a normal path as the colon and forward slash are reserved ffmpeg drawtext commands so you have to change things thus - C:\AppPath\fontname.ttf becomes 'C\:\\AppPath\\fontname.ttf'. Note the single quotes around it in case there are spaces in the path name.

And that is working now in Scheduler.
Hooray!!!


BTW my paths are correct.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

  Print Answers RSS
Top Experts
Last 24hrsThis month


CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900