How to escape schtasks /tr arguments

前端 未结 1 918
天命终不由人
天命终不由人 2021-01-13 11:06

I need to schedule a PowerShell task like following:

powershell.exe -file ..\\Execute\\execute.ps1

And I have tried assigning it to an argu

1条回答
  •  走了就别回头了
    2021-01-13 11:56

    Scheduled tasks created with schtasks.exe execute with the working directory set to $env:windir\system32[1], so unless your script happens to be located in ..\Execute\execute.ps1 relative to there, your command won't work as intended.

    If you don't want to hard-code the script path directly into the command, construct the command dynamically, by resolving the relative path to an absolute one when you assign to $Argument:

    $Argument = 'powershell.exe -file \"{0}\"' -f (Convert-Path ..\Execute\execute.ps1)
    

    Note the - unfortunate - need to escape the embedded " as \", which is longstanding bug that hasn't been fixed for the sake of backward compatibility - see this GitHub docs issue for background.

    Convert-Path resolves a relative path to an absolute one.
    Note that the relative path must refer to an existing file (or directory).

    Similarly, relative paths inside your script will be relative to $env:windir\system32 too; to make them relative to the script's directory, change to your script's directory first by executing Set-Location $PSScriptRoot at the start of your script.


    Optional reading: How to quote commands that run from a scheduled task:

    Note: Virtually the same rules apply as when running a command from the Windows Run dialog (press WinKey+R), which you can use to test-drive a command (the command to pass to schtasks /tr, without outer quoting, not the whole schtasks command line) - though note that the working directory will be the user's home directory, and that you won't be able to use '...'-quoting around the PowerShell CLI's -File argument - see below):

    • cmd.exe is NOT involved during execution, which means:

      • You needn't worry about non-double-quoted use of cmd.exe metacharacters such as &, for instance, so you can use these characters even in single-quoted strings passed to the PowerShell CLI powershell.exe as (part of) the -Command argument(s).

      • Conversely, output redirections (e.g., > c:\path\to\log.txt) are not directly supported.

      • In the context of invoking the PowerShell CLI, this means:

        • With -File, you cannot use them on the command line and must instead perform them from within your script.

        • With -Command, however, you can use them, because it is then PowerShell that applies them (but note that Windows PowerShell's > operator creates UTF-16LE files).

    • (Even though cmd.exe isn't involved) references to environment variables using the same syntax form as in cmd.exe are expanded (e.g., %USERNAME%)

      • Caveat: You cannot escape such references:
      • %% doesn't work - the additional % is simply treated as a literal, and expansion still occurs; e.g., %%OS%% results in %Windows_NT%.
      • ^% (accidentally) prevents expansion, but retains the ^ - the ^ doesn't escape; rather, it "disrupts" the variable name, in which case the token is left as-is; e.g., ^%OS^% results in ^%OS^%, i.e., is retained as-is.

    The above applies to the commands as they must end up defined inside a scheduled task, as you would see or define them interactively in Task Scheduler (taskschd.msc).

    Additionally, for creating a scheduled task from the command line / a PowerShell script / a batch file:

    • you must quote the command as a whole and

    • comply with the syntax rules of the calling shell regarding escaping and up-front string interpolation.

    (You can only get away without quoting if the command consists of only a single word that needs no escaping, such the path to an executable that contains no spaces or special characters and to which no arguments are passed.)

    When calling schtasks.exe[2], quote the /tr argument as a whole as follows:

    • from PowerShell, use "...", if you need to expand (string-interpolate) the command string up front; otherwise, use '...'.
      Important: The need to escape nested " as \" applies in both cases, which in the case of outer "..." quoting means that nested " must be escaped as \`" (sic).

      • Surprisingly, schtasks.exe recognizes embedded '...' quoting and automatically translates it to "..." quoting - that is why your original command, "powershell.exe -file '..\Execute\execute.ps1'", worked, even though in direct invocation the PowerShell CLI does not support the use of '...' in combination with -File.
    • from cmd.exe (whether directly or from a batch file), you must use "...".

    PowerShell examples:

    • The following PowerShell commands create and execute two run-once scheduled tasks, named test1 and test2, that run when the next calendar minute starts, in the context of the calling user, visibly. (You'll have to remove these tasks manually afterwards.)

    • You may have to wait for up to 1 minute to see the invocation kick in, at which point a new console window pops up for each task.

    # Create sample script test.ps1 in the current dir. that
    # echoes its arguments and then waits for a keypress.
    '"Hi, $Args."; Read-Host "Press ENTER to exit"' > test.ps1
    
    # Find the start of the next calendar minute.
    $nextFullMinute = ([datetime]::Now.AddMinutes(1).TimeOfDay.ToString('hh\:mm'))
    
    # -File example:
    # Invoke test.ps1 and pass it 'foo' as an argument.
    # Note the escaped embedded "..." quoting around the script path
    # and that with -File you can only pass literal arguments at 
    # invocation time).
    schtasks.exe /create /f /tn test1 /sc once /st $nextFullMinute `
      /tr "powershell -File \`"$PWD/test.ps1\`" foo"                                                                            #`# (dummy comment to fix broken syntax highlighting)
    
    # -Command example:
    # Invoke test.ps1 and pass it $env:USERNAME as an argument.
    # Note the '...' around the script path and the need to invoke it with
    # &, as well as the ` before $env:USERNAME to prevent its premature expansion.
    schtasks.exe /create /f /tn test2 /sc once /st $nextFullMinute `
      /tr "powershell -Command & '$PWD/test.ps1' `$env:USERNAME"
    
    "Tasks will execute at ${nextFullMinute}:00"
    

    [1] Note that the Task Scheduler GUI allows you to configure a working directory, but this feature isn't available via the schtasks.exe utility.

    [2] The same applies to values passed to the -Argument parameter of the New-ScheduledTaskAction PowerShell cmdlet, though note that the executable name/path is specified separately there, via the -Execute parameter.
    By contrast, the Register-ScheduledJob cmdlet for creating scheduled PowerShell jobs accepts a script block as the command to run, which eliminates the quoting headaches.

    0 讨论(0)
提交回复
热议问题