简体   繁体   English

使用 -Filepath 参数调用 Powershell ScheduledJob 时 $PSScriptRoot 为空

[英]$PSScriptRoot empty when called with Powershell ScheduledJob using -Filepath parameter

The automatic variable $PSScriptRoot isn't populating when using a Powershell ScheduledJob (not to be confused with a ScheduledTask).使用 Powershell ScheduledJob(不要与 ScheduledTask 混淆)时,不会填充自动变量 $PSScriptRoot。

A ScheduledJob with the -Filepath Parameter specifying a local script:具有指定本地脚本的 -Filepath 参数的 ScheduledJob:

-Filepath "C:\Scriptpath\Script.ps1"

Fails to populate $PSScriptRoot.无法填充 $PSScriptRoot。

Get-Job reveals that it isn't running the script, but is instead outputting the script like it's reading a.txt file. Get-Job 表明它没有运行脚本,而是像读取 .txt 文件一样输出脚本。 false错误的

A ScheduledJob with the -Scriptblock and the & Call Operator specifying a local script:带有 -Scriptblock 和& Call Operator 指定本地脚本的 ScheduledJob:

-Scriptblock {& "C:\Scriptpath\Script.ps1"}

Succeeds to populate $PSScriptRoot.成功填充 $PSScriptRoot。

Script.ps1 can be as simple as Script.ps1 可以很简单

$PSScriptRoot | out-file "C:\Test.txt"

The issue appears to be how the file is being executed by the Job in Task Scheduler, rather than the syntax errors in the.ps1.问题似乎是任务计划程序中的作业如何执行文件,而不是 .ps1 中的语法错误。 Since the same script works if called through the -Scriptblock parameter.因为如果通过 -Scriptblock 参数调用相同的脚本。 In either case, the script is being executed because Out-File will create a blank txt file.在任何一种情况下,脚本都会被执行,因为 Out-File 将创建一个空白的 txt 文件。

Both usages appear to be in accordance with Microsoft's Documentation:这两种用法似乎都符合 Microsoft 的文档:

https://learn.microsoft.com/en-us/powershell/module/psscheduledjob/register-scheduledjob?view=powershell-5.1 https://learn.microsoft.com/en-us/powershell/module/psscheduledjob/register-scheduledjob?view=powershell-5.1

Any guidance on the behavior would be helpful.任何关于行为的指导都会有所帮助。

Edit:编辑:

What I am noticing is that Get-Job, which shows the "command" that the scheduled job is executing.我注意到的是 Get-Job,它显示了计划作业正在执行的“命令”。

If using -Scriptblock.如果使用 -Scriptblock。 Get-Job shows the exact contents of script block, like above. Get-Job 显示脚本块的确切内容,如上所示。

& "C:\Scriptpath\Script.ps1"

If using -Filepath.如果使用 -Filepath。 Get-Job shows the contents of the file. Get-Job 显示文件的内容。

$PSScriptRoot | out-file "C:\Test.txt"

It's like it's copying the contents of the file and then invoking them.这就像它复制文件的内容,然后调用它们。 This would explain why $PSScriptRoot is failing to be populated.这可以解释为什么 $PSScriptRoot 无法填充。

.... ~test noises~ .... ~测试噪音~

$PWD | Out-File "C:\file.txt"

In the file shows that $PWD happens to be the $Home variable of the user account that the scheduled job is being executed with.在文件中显示 $PWD 恰好是执行计划作业的用户帐户的 $Home 变量。 I think I'm on to something.我想我正在做某事。

It seems like you're describing two problems:您似乎在描述两个问题:

  1. $PSScriptRoot is unpopulated under some circumstance.在某些情况下, $PSScriptRoot未填充。

  2. Set-ScheduledJob with -FilePath parameter displays the contents of the file instead of executing it.带有 -FilePath 参数的 Set-ScheduledJob 显示文件的内容而不是执行它。

For #1: There is an open issue filed for $PSScriptRoot but it's hard to say whether the circumstances match your use-case because... you haven't provided any code.对于#1: $PSScriptRoot 有一个未解决的问题,但很难说情况是否与您的用例相匹配,因为……您没有提供任何代码。 I suggest you add a bit more source code explaining how your code uses $PSScriptRoot我建议你添加更多的源代码来解释你的代码如何使用$PSScriptRoot

For #2: The documentation for the Set-ScheduledJob : -Filepath seems to require a .ps1 file, but your example shows a non-.ps1 file being passed.对于#2: Set-ScheduledJob的文档:-Filepath似乎需要一个 .ps1 文件,但您的示例显示正在传递一个非 .ps1 文件。

-FilePath -文件路径

Specifies a script that the scheduled job runs.指定计划作业运行的脚本。 Enter the path to a .ps1 file on the local computer.输入本地计算机上.ps1 文件的路径。 To specify default values for the script parameters, use the ArgumentList parameter.要为脚本参数指定默认值,请使用 ArgumentList 参数。 Every scheduled job must have either a ScriptBlock or FilePath value.每个计划作业都必须具有 ScriptBlock 或 FilePath 值。

Does the script at 'c:\\Filepath' actually have a .ps1 extension? 'c:\\Filepath' 中的脚本实际上有 .ps1 扩展名吗? If not, give it one and change your -Filepath parameter to match.如果没有,给它一个并更改您的-Filepath参数以匹配。

It may be that one problem is leading to the other, but it's unclear from the current text in question how they are related.一个问题可能会导致另一个问题,但从当前的文本中并不清楚它们是如何相关的。

The documentation is not very clear about it, but as it turns out, both -Filepath and -Scriptblock options will execute script blocks.文档对此不是很清楚,但事实证明, -Filepath-Scriptblock选项都将执行脚本块。 And therefore, the automatic variable $PSScriptRoot won't be properly initialized, since this only happens when the caller is a script.因此,自动变量$PSScriptRoot不会被正确初始化,因为这只发生在调用者是脚本时。 In both cases, the caller is the TaskScheduler which will call Powershell.exe with some extra arguments, including the -Command which in turn, will load a job definition from serialized XML, that will ultimately load the contents of the given file.在这两种情况下,调用者都是 TaskScheduler,它将调用Powershell.exe和一些额外的 arguments,包括-Command ,后者将从序列化的 XML 加载作业定义,最终加载给定文件的内容。

The way it works under the hood is, when using the -Filepath option of the Register-ScheduledJob , the cmdlet creates a somewhat special ScheduledTask that will execute Powershell with the following arguments:它在幕后的工作方式是,当使用Register-ScheduledJob-Filepath选项时,该 cmdlet 创建一个有点特殊的 ScheduledTask,它将执行 Powershell 和以下 arguments:

-NoLogo -NonInteractive -WindowStyle Hidden 
-Command "Import-Module PSScheduledJob; $jobDef = [Microsoft.PowerShell.ScheduledJob.ScheduledJobDefinition]::LoadFromStore('MyScheduledJob', '%LOCALAPPDATA%\Microsoft\Windows\PowerShell\ScheduledJobs'); $jobDef.Run()"

As you can see, there is an ad-hoc command, which loads a specialized module that does the heavylifting for reading/loading and storing "job definitions" in an XML store.如您所见,有一个临时命令,它加载一个专门的模块,该模块执行繁重的工作以在 XML 存储中读取/加载和存储“作业定义”。 If you navigate to that folder, there will be one folder per ScheduledJob, with a ScheduledJobDefinition.xml which contains an XML tag called <InvocationParam_FilePath> whose value is still only the path to the file that contains the script to be executed.如果您导航到该文件夹,每个 ScheduledJob 将有一个文件夹, ScheduledJobDefinition.xml包含一个名为<InvocationParam_FilePath>的 XML 标记,其值仍然只是包含要执行的脚本的文件的路径。 However, when the task is triggered, it will go and extract the contents of that file and run it as a ScriptBlock .但是,当任务被触发时,它将 go 并提取该文件的内容并将其作为 ScriptBlock 运行 This allows you to update the script file and the Task Scheduler will always pick up the most updated version when it runs (that's great,), while you can still trace what was exactly run each time because under the Output directory you can find a Results.xml file that contains a snapshot of the code that was actually executed ( <Status_Command> ).这允许您更新脚本文件,任务计划程序在运行时将始终选择最新版本(这很好,),同时您仍然可以跟踪每次运行的确切内容,因为在Output目录下您可以找到Results.xml文件,其中包含实际执行的代码 ( <Status_Command> ) 的快照。

So, as you already discovered, one simple workaround is to make it so that the script block is only a wrapper to an invocation of a script file, like in your example:因此,正如您已经发现的那样,一个简单的解决方法是使脚本块只是脚本文件调用的包装器,如您的示例所示:

& "C:\Scriptpath\Script.ps1"
# This will effectively auto-populate 
# the $PSScriptRoot variable within the 
# boundaries of that script, making it 
# possible to be used within it.

The second workaround, if you really would like to use -Filepath option, is to use it in tandem with the -ArgumentList option to inject those values when the block gets executed.第二种解决方法,如果你真的想使用-Filepath选项,是将它与-ArgumentList选项一起使用,以便在执行块时注入这些值。 Because I register my scheduled jobs with another powershell script placed in the same location, which I interactively execute, all those auto-variables will be available at registration-time, and the values will be stored in the job definition ( <InvocationParam_ArgList> ) as part of its "execution context" so to speak.因为我使用位于同一位置的另一个 powershell 脚本注册我的计划作业,我以交互方式执行该脚本,所以所有这些自动变量将在注册时可用,并且值将存储在作业定义( <InvocationParam_ArgList> )中作为可以说是其“执行上下文”的一部分。 Now, you just need to have a way to make the script work both when called interactively and when called by the Task Scheduler.现在,您只需要有一种方法可以使脚本在交互调用时和任务计划程序调用时都能正常工作。 Here's an example of a backup script that I needed it to work in such way.这是我需要它以这种方式工作的备份脚本的示例。 This is how the ScheduledJob is registered:这是 ScheduledJob 的注册方式:

Register-ScheduledJob -Name $taskName `
    -FilePath $pathToBackupScript `
    -ArgumentList $PSScriptRoot,$SpecialFolderDropbox `
    -RunEvery (New-TimeSpan -Days 1)

And within the script, I ensure those parameters will always have correct values (with default values, if not provided via an ArgumentList):在脚本中,我确保这些参数始终具有正确的值(如果未通过 ArgumentList 提供,则使用默认值):

# BackupScript.ps1
param (
    [String] $ScriptRoot=$PSScriptRoot, 
    [String] $Dropbox=$SpecialFolderDropbox
)
#...Here goes code that can read from
#   those passed parameters
#   When run interactively, they will already
#   have auto-populated values.

#   If you want to keep using $PSScriptRoot instead 
#   of a proxy variable, you can but some linters
#   will complain/warn about this overwrite.
#   Example: $PSScriptRoot=$PSScriptRoot...
#   Just let it know who's boss :P

The third workaround is to simply go back and use good old Scheduled Tasks (in Windows).第三种解决方法是简单地返回 go 并使用旧的计划任务(在 Windows 中)。 Here you can define the command to be run as you wish.在这里您可以根据需要定义要运行的命令。 Obviously, you will be invoking Powershell.exe...<the usual switches>... -File script.ps1 , and the script will properly populate the $PSScriptRoot .显然,您将调用Powershell.exe...<the usual switches>... -File script.ps1 ,脚本将正确填充$PSScriptRoot Personally, I haven't found any good scenario where I can assert that Scheduled Jobs are the definitive superior choice.就我个人而言,我还没有找到任何可以断言计划作业是绝对最佳选择的好场景。

Regarding $PSScriptRoot not being present for a ScheduledJob.关于$PSScriptRoot不存在于 ScheduledJob。 We found that $PSScriptRoot not only failed for the root file executed by the scheduled job but also those subsequently included.我们发现$PSScriptRoot不仅对计划作业执行的根文件失败,而且对随后包含的根文件也失败。

For example, say the root file contained . "<absolute path>\\Include-SomeOtherFile.ps1"例如,假设根文件包含. "<absolute path>\\Include-SomeOtherFile.ps1" . "<absolute path>\\Include-SomeOtherFile.ps1" and within Include-SomeOtherFile.ps1 we then called . "$PSScriptRoot\\Include-YetAnotherFile.ps1" . "<absolute path>\\Include-SomeOtherFile.ps1"然后在Include-SomeOtherFile.ps1调用. "$PSScriptRoot\\Include-YetAnotherFile.ps1" . "$PSScriptRoot\\Include-YetAnotherFile.ps1" . . "$PSScriptRoot\\Include-YetAnotherFile.ps1" This would fail as $PSScriptRoot is still empty.这将失败,因为$PSScriptRoot仍然是空的。

I didn't want to hardcode everything so instead I do a check within the root file to see if $PSScriptRoot is empty.我不想对所有内容进行硬编码,因此我在根文件中进行了检查,以查看$PSScriptRoot是否为空。 If empty it uses the call operator to start the script again but in a new context where the $PSScriptRoot works:如果为空,它将使用调用运算符再次启动脚本,但在$PSScriptRoot工作的新上下文中:

$ErrorActionPreference = "Stop"
 
if ($PSScriptRoot -eq "")
{   
    # Execute this file again but a new 
    # context where $PSScriptRoot works
    & "<absolute path of this file>.ps1"

    Exit $LASTEXITCODE
}
   
try 
{
    # Rest of code using $PSScriptRoot
}
catch
{
    Exit 1
}

So I still had to use the absolute path of the root file but after that I was able to rely on $PSScriptRoot throughout my included files.所以我仍然必须使用根文件的绝对路径,但之后我可以在我包含的文件中依赖$PSScriptRoot

According to this SO answer , if you want to calculate the absolute path and are happy relying on the name of the scheduled job, you can apparently use the following (untested):根据这个 SO answer ,如果你想计算绝对路径并且很高兴依靠预定作业的名称,你显然可以使用以下(未经测试):

Get-ScheduledJob |? Name -Match 'JOBNAMETAG' |% Command

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM