PowerShell自我更新脚本

时间:2018-08-10 16:21:44

标签: powershell powershell-v4.0

我们有一个PowerShell脚本,可以持续监视文件夹中是否有新的JSON文件并将其上传到Azure。我们将此脚本保存在共享文件夹中,以便多个人可以同时运行此脚本以实现冗余。每个人的计算机都有计划的任务,可以在登录时运行它,以便脚本始终处于运行状态。

我想更新脚本,但是后来我不得不要求每个人停止正在运行的脚本并重新启动它。这特别麻烦,因为我们最终希望以“隐藏”模式运行此脚本,以便没有人意外关闭窗口。

所以我想知道是否可以创建一个自动更新自身的脚本。我想出了下面的代码,当运行此脚本并保存了新版本的脚本时,我希望正在运行的PowerShell窗口在命中Exit命令时关闭,然后重新打开一个新窗口以运行脚本的新版本。但是,那没有发生。

它持续进行,没有出现任何提示。它不会关闭当前窗口,甚至会将旧版本脚本的输出保留在屏幕上。好像PowerShell并不是真正的Exit,它只是弄清楚发生了什么,并继续使用脚本的新版本。我想知道为什么会这样吗?我喜欢它,我只是不理解。

#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”开始按预期出现。如果我拔掉网络电缆一会儿,很快就会收到预期的错误消息。但是,当我重新插入电缆时,即使新保存的版本中包含“ Hello”,脚本也经常会再次开始输出“ Hi”。我猜这是一个负面影响,因为脚本在执行Exit命令时不会真正退出。

2 个答案:

答案 0 :(得分:2)

. $PSCommand blocking (同步)调用,这意味着直到Exit本身退出后,下一行的$PSCommand才会执行。 / p>

假设$PSCommand是您的脚本,从不退出(即使貌似也不会退出), Exit语句永远不会达到(假设脚本的新版本保持相同的基本while循环逻辑)。

虽然这种方法原则上可行,但是有腔室

  • 您正在使用., the "dot-sourcing" operator,这意味着脚本的新内容已加载到 current 范围内(通常,您始终处于相同的过程,就像调用*.ps1或使用(暗示)常规调用运算符.来调用&文件时一样。
    虽然新脚本中的变量/函数/别名随后会在当前范围内替换,但从新版本脚本中删除的旧定义将保留 ,并可能导致不良的副作用。

  • 当您观察自己时,如果新脚本包含导致其退出的语法错误,则您的自我更新机制将会中断,因为Exit语句会已到达,并且什么都没运行
    也就是说,您可以将其用作检测调用新版本失败的机制:

    • 使用try { . $ProfilePath } catch { Write-Error $_ }而不是. $ProfilePath
    • 而不是Exit命令,发出警告(或执行任何适当的操作以警告某人失败),然后保持循环continue),表示旧脚本一直有效,直到找到新的 valid
  • 即使有上述情况,此方法的基本约束还是您可能会超过最大呼叫递归深度。嵌套的.调用会堆积起来,达到嵌套限制时,您不会 能够执行另一个操作,而您陷入了徒劳的重试循环中。
    也就是说,从Windows PowerShell v5.1开始,此限制似乎是大约4900个嵌套调用,因此,如果您从不希望在给定用户会话处于活动状态时重新频繁更新脚本(重新启动/注销将重新开始),这可能不必担心。


替代方法

一种更可靠的方法是创建一个单独的看门狗脚本,其唯一目的是监视新版本,杀死旧的运行脚本并启动新脚本,并具有启动时的警报机制新脚本失败。

答案 1 :(得分:0)

另一种选择是使主脚本具有“阶段”,以便根据文件夹中最高修订脚本的名称运行命令。我认为mklement0的看门狗是一个聪明的主意。

但是我指的是做您要做的事情,但使用变量作为命令,并且这些变量将使用最高编号的脚本名称进行更新。这样,您只需将10.ps1放到该文件夹​​中,它将忽略9.ps1。并且该脚本中的函数将被命名为mainfunction10等。

类似

$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

将替换kill $old

此命令是有根据的猜测,未经测试。

其他技巧可能会将看门狗的主脚本作为作业运行。获取工作ID。然后检查文件更改。如果有新文件出现,看门狗可能会杀死工作ID并重复整个过程

您也可以只结束脚本。每10分钟就有一次Windows作业,只需重新运行脚本即可。这样一来,您只需每十分钟运行一次脚本即可。但是,这对于每个初创公司来说都更加强烈。

您可以使用break而不是退出来终止循环。而且脚本会自然退出

您可以使用测试连接来检查服务器。但是,如果是每3秒一次。如果从很多计算机上ping都很多