PowerShell自更新脚本
我们有一个PowerShell脚本,可以持续监视文件夹中的新JSON文件并将其上载到Azure。我们将此脚本保存在共享文件夹中,以便多人可以同时运行此脚本以实现冗余。每个人的计算机都有一个在登录时运行的计划任务,以便脚本始终运行 我想更新脚本,但随后我不得不要求每个人停止运行脚本并重新启动它。这尤其麻烦,因为我们最终希望以“隐藏”模式运行此脚本,这样就不会有人意外关闭窗口 所以我想知道是否可以创建一个自动更新自身的脚本。我想出了下面的代码,当运行此脚本并保存新版本的脚本时,我希望运行的PowerShell窗口在点击PowerShell自更新脚本,powershell,powershell-4.0,Powershell,Powershell 4.0,我们有一个PowerShell脚本,可以持续监视文件夹中的新JSON文件并将其上载到Azure。我们将此脚本保存在共享文件夹中,以便多人可以同时运行此脚本以实现冗余。每个人的计算机都有一个在登录时运行的计划任务,以便脚本始终运行 我想更新脚本,但随后我不得不要求每个人停止运行脚本并重新启动它。这尤其麻烦,因为我们最终希望以“隐藏”模式运行此脚本,这样就不会有人意外关闭窗口 所以我想知道是否可以创建一个自动更新自身的脚本。我想出了下面的代码,当运行此脚本并保存新版本的脚本时,我希望运行的Power
Exit
命令时关闭,然后重新打开一个新窗口以运行新版本的脚本。然而,这并没有发生
它毫无征兆地继续着。它不会关闭当前窗口,甚至会在屏幕上保留旧版本脚本的输出。就好像PowerShell并没有真正退出,它只是知道发生了什么,并继续使用新版本的脚本。我想知道为什么会这样?我喜欢,只是不明白
#Place at top of script
$lastWriteTimeOfThisScriptWhenItFirstStarted = [datetime](Get-ItemProperty -Path $PSCommandPath -Name LastWriteTime).LastWriteTime
#Continuous loop to keep this script running
While($true) {
Start-Sleep 3 #seconds
#Run this script, change the text below, and save this script
#and the PowerShell window stays open and starts running the new version without a hitch
"Hi"
$lastWriteTimeOfThisScriptNow = [datetime](Get-ItemProperty -Path $PSCommandPath -Name LastWriteTime).LastWriteTime
if($lastWriteTimeOfThisScriptWhenItFirstStarted -ne $lastWriteTimeOfThisScriptNow) {
. $PSCommandPath
Exit
}
}
有趣的旁注
我决定看看如果我的计算机与运行脚本的共享文件夹失去连接会发生什么。它继续运行,但按预期每3秒显示一条错误消息。但是,当网络连接恢复时,它通常会恢复到旧版本的脚本
因此,如果我将脚本中的“Hi”更改为“Hello”,并保存它,“Hello”将按预期显示。如果我拔掉网线一段时间,很快就会收到预期的错误消息。但是,当我插回电缆时,脚本通常会再次开始输出“Hi”,即使新保存的版本中有“Hello”。我想这是脚本在点击
Exit
命令时从未真正退出这一事实的负面影响。$PSCommand
是一个阻塞(同步)调用,这意味着在$PSCommand
自身退出之前,不会执行下一行的Exit
鉴于$PSCommand
这是您的脚本,它永远不会退出(即使它看起来是退出的),永远不会到达Exit
语句(假设脚本的新版本在循环逻辑时保持相同的基本)
虽然这种方法在原则上是有效的,但也有一些警告:
- 您正在使用,这意味着脚本的新内容将加载到当前作用域中(通常,您始终处于相同的过程中,就像调用
*.ps1
文件时一样,无论是使用
还是(隐含的)常规调用操作符,&
)。
当新脚本中的变量/函数/别名替换当前范围中的旧变量/函数/别名时,从新版本脚本中删除的旧定义将保留下来,并可能导致不必要的副作用
- 根据您自己的观察,如果新脚本包含导致其退出的语法错误,您的自我更新机制将中断,因为此时已到达
exit
语句,并且没有任何内容保持运行。
也就是说,您可以将其用作检测调用新版本失败的机制:
- 使用
尝试{.$ProfilePath}捕获{Write Error$}
而不仅仅是$配置文件路径
- 而不是
退出
命令,而是发出警告(或做任何适当的事情来提醒某人失败),然后继续循环(继续
),这意味着旧脚本将保持有效,直到找到有效的新脚本
- 即使使用上述方法,这种方法的基本限制是您可能会超过最大调用递归深度。嵌套的
调用会堆积起来,当达到嵌套限制时,您将无法进行调用
如果你能再做一次,你就会陷入徒劳的重试循环。
这就是说,从Windows PowerShell v5.1开始,该限制似乎约为4900个嵌套调用,因此,如果您从未期望在给定用户会话处于活动状态时(重新启动/注销将重新启动)脚本会如此频繁地更新,那么这可能不成问题
替代方法:
一种更可靠的方法是创建一个单独的看门狗脚本,其唯一目的是监视新版本,杀死运行中的旧脚本并启动新脚本,以及启动新脚本失败时的警报机制。另一种方法是让主脚本具有“阶段”它根据文件夹中最高版本脚本的名称运行命令的位置。我认为mklement0的看门狗是一个很好的主意
但是我指的是做你所做的,但是使用变量作为你的命令,这些变量会被更新为最大数量的脚本名。这样,您只需将10.ps1放入文件夹,它就会忽略9.ps1。脚本中的函数名为mainformation10等
差不多
$command = ((get-childitem c:\path\to\scriptfolder\).basename)[-1]
& "C:\path\to\scruptfolder\\$command"
这些文件必须按字母顺序从最早到最新命名。否则,您必须按日期对对象进行排序
$command = ((get-childitem c:\path\to\scriptfolder\ | sort-object -Property lastwritetime).basename)[-1]
& "C:\path\to\scruptfolder\\$command"
或者。源代码,而不是将其用作命令。然后让后面的代码调用函数,如function$command
,函数将是脚本的名称
我还是更喜欢看门狗的主意
看门狗看起来有点像
While ($true) {
$new = ((get-childitem c:\path\to\scriptfolder\ | sort-object -Property lastwritetime).fullname)[-1]
If ($old -ne $new){
Kill $old
Sleep 10
& $new
}
$old -eq $new
Sleep 600
}
请注意,我不确定脚本是如何运行的,您可能需要根据命令查找powershell实例
$kill = ((WMIC path win32_process get Caption,Processid,Commandline).where({$_.commandline -contains $command})).processid
Kill $kill