Powershell调用计算机到远程我的本地计算机重新启动后幸存下来?

Posted

技术标签:

【中文标题】Powershell调用计算机到远程我的本地计算机重新启动后幸存下来?【英文标题】:Powershell invoke-computer to a remote that survives a reboot of my local computer? 【发布时间】:2021-12-16 10:31:41 【问题描述】:

有没有办法“调用命令”到远程计算机,这样我就可以重新启动计算机并且作业仍将运行,并且我可以随时检查输出日志?

    PS> invoke-command -Computer Remote1 -File "ScriptThatRunsFor7days.ps1"
    PS> restart-computer
    PS> # Hey where's my job on remote computer?  Can i see it running and connect to
        # its output after rebooting my computer?

【问题讨论】:

我已删除您问题的第二部分,以防止因“过于宽泛”或“可能引出基于意见的答案”而关闭。否则这是一个很好的问题,我将在今天晚些时候尝试回答,但我也有兴趣看看是否有人比我使用的繁琐方法有更优雅的答案。 试试*-PSSession cmdlet; Disconnect-PSSession 听起来可以。 我正在做一些测试,但必须运行。我不确定是否可以跨会话或重新启动恢复断开的会话。我知道断开连接的会话会超时,但在创建会话时可以配置。 @BendertheGreatest 我不相信它可以。我认为workflows 是专门为此而设计的。 @SantiagoSquarzon 我没有对工作流程做过任何事情,但怀疑它们在这里可能有用。但是,虽然工作流可以在会话和重新启动后继续存在,但“活动”对象是否也会在恢复执行时保持可操作状态? 【参考方案1】:

只注册一个在远程计算机上运行脚本的计划任务不是更容易吗? 对于日志记录,只需使用脚本顶部的 cmdlet Start-Transcript。 不久前我编写了一个脚本,以便在远程计算机上轻松注册计划任务。也许你可以试试看它是否适合你?

[CmdletBinding()]
param(
    [parameter(Mandatory=$true)]
    [string]
    $PSFilePath,

    [parameter(Mandatory=$true)]
    [string]
    $TaskName,

    [parameter(Mandatory=$true)]
    [string]
    $ComputerName
)

$VerbosePreference="Continue"

New-Variable -Name ScriptDestinationFolder -Value "Windows\PowershellScripts" -Option Constant -Scope Script

New-Variable -Name ScriptSourcePath -Value $PSFilePath -Option Constant -Scope Script
Write-Verbose "Script sourcepath: $ScriptSourcePath"

New-Variable -Name PSTaskName -Value $TaskName -Option Constant -Scope Script
Write-Verbose "TaskName: $TaskName"

$File = Split-Path $ScriptSourcePath -leaf
Write-Verbose "Filename: $File"
New-Variable -Name PSFileName -Value $File -Option Constant -Scope Script
Write-Verbose "PSFileName: $PSFileName"

$ExecutionTime = New-TimeSpan -Hours 8
Write-Verbose "Execution time: $ExecutionTime hours"

Invoke-Command -ComputerName $ComputerName -ScriptBlock 
    $VerbosePreference="Continue"        
    #Removing old Scheduled Task 
    Write-Verbose "Unregistering old scheduled task.."
    Stop-ScheduledTask -TaskName $Using:PSTaskName -ErrorAction SilentlyContinue
    Unregister-ScheduledTask -TaskName $Using:PSTaskName -Confirm:$false -ErrorAction SilentlyContinue


     #Creating destination directory for Powershell script
     $PSFolderPath = "C:" , $Using:ScriptDestinationFolder -join "\"
     Write-Verbose "Creating folder for script file on client: $PSFolderPath"
     New-Item -Path $PSFolderPath -ItemType Directory -Force

     #Scheduled Task definitions
    
     $Trigger = New-ScheduledTaskTrigger -Daily -At "8am" 
     $PSFilePath = "C:", $Using:ScriptDestinationFolder , $Using:PSFileName -join "\"  
     Write-Verbose "Setting path for script file to destination folder on client: $PSFilePath"
     $Action = New-ScheduledTaskAction -Execute PowerShell -Argument "-File $PSFilePath"
     $Principal = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType S4U
     $Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -DontStopOnIdleEnd -ExecutionTimeLimit $Using:ExecutionTime -StartWhenAvailable
     $Task = Register-ScheduledTask -TaskName $Using:PSTaskName -Principal $Principal -Action $Action -Settings $Settings -Trigger $Trigger
     $Task = Get-ScheduledTask -TaskName $Using:PSTaskName   
     $Task.Triggers[0].EndBoundary = [DateTime]::Now.AddDays(90).ToString("yyyyMMdd'T'HH:mm:ssZ") 
     Write-Verbose "Trigger expiration date set to: $Task.Triggers[0].EndBoundary"
     $Task.Settings.DeleteExpiredTaskAfter = 'P1D'
     Write-Verbose "Scheduled task will be deleted after $Task.Settings.DeleteExpiredTaskAfter after expiry."     
     $Task | Set-ScheduledTask -ErrorAction SilentlyContinue

  #End Invoke-Command

#Copy script file from source to the computer
$ScriptDestination = "\" , $ComputerName , "C$",  $ScriptDestinationFolder  -join "\"
Write-Verbose "Script destination is set to: $ScriptDestination"

Write-Verbose "Copying script file: `"$ScriptSourcePath`" to `"$ScriptDestination`""
Copy-Item -Path $ScriptSourcePath -Destination $ScriptDestination -Force

用法:

Create-ScheduledTask-Test.ps1 -ComputerName MyRemoteComputer -PSFilePath "ScriptToRun.ps1" -TaskName DoSomeWork

【讨论】:

【参考方案2】:

有预定工作的东西。我正在使用 pssession 将脚本复制到远程计算机。

$s = new-pssession remote1
copy-item script.ps1 c:\users\admin\documents -tosession $s
invoke-command $s  Register-ScheduledJob test script.ps1 -Runnow 

然后,只有当它开始运行时,它才会在远程计算机上自动显示为常规作业:

invoke-command remote1  get-job | receive-job -keep 

【讨论】:

【参考方案3】:

vonPryz 提供了关键指针:

在 Windows 上,PowerShell 提供断开的远程会话允许您稍后从任何客户端会话重新连接并收集输出,即使在注销或重新启动 - 假设远程计算机上断开的会话没有超时。

请参阅概念性 about_Remote_Disconnected_Sessions 帮助主题。

以下示例脚本演示了该方法:

将其保存到*.ps1 文件并调整$computerName$sessionName 变量值。

脚本假定当前用户身份可以按原样使用远程进入目标计算机;如果不是这种情况,请将-Credential 参数添加到Invoke-CommandGet-PSSession 调用。

调用脚本,并在出现提示时选择何时连接到已创建的断开连接的远程会话 - 包括在注销/重新启动后,在这种情况下,脚本会自动重新调用以连接到断开连接的会话并检索其输出。

请参阅源代码 cmets 了解详细信息,尤其是关于空闲超时的信息。

下面的一个方面是输出缓冲:断开连接的会话长时间运行而没有检索到其输出,可能会积累大量输出。默认情况下,如果输出缓冲区已满,则暂停执行。 OutputBufferingMode 会话选项控制行为 - 请参阅 New-PSSessionOption cmdlet。

解决方案的要点是:

Invoke-Command 调用与-InDisconnectedSession 开关 启动远程计算机上的操作在即时断开会话。也就是说,调用会在操作开始后立即返回但不会从操作返回任何结果(它会返回有关已断开会话的信息)。

稍后的 Receive-PSSession 调用(可能在重新启动后发生)隐式连接到断开的会话并检索操作结果。

$ErrorActionPreference = 'Stop'

# ADAPT THESE VALUES AS NEEDED
$computer = '???'            # The remote target computer's name.
$sessionName = 'ReconnectMe' # A session name of your choice.

# See if the target session already exists.
$havePreviousSession = Get-PSSession -ComputerName $computer -Name $sessionName

if (-not $havePreviousSession) 

  # Create a disconnected session with a distinct custom name 
  # with a command that runs an output loop indefinitely.
  # The command returns instantly and outputs a session-information object 
  # for the disconnected session.
  # Note that [int]::MaxValue is used to get the maximum idle timeout, 
  # but the effective value is capped by the value of the remote machine's 
  # MaxIdleTimeoutMs WSMan configuration item, which defaults to 12 hours.
  Write-Verbose -vb "Creating a disconnected session named $sessionName on computer $computer..."
  $disconnectedSession = 
    Invoke-Command -ComputerName $computer -SessionName $sessionName -InDisconnectedSession -SessionOption @ IdleTimeout=[int]::MaxValue   while ($true)  Write-Host -NoNewLine .; Start-Sleep 1  

  # Prompt the user for when to connect and retrieve the output 
  # from the disconnected session.
  do 
    $response = Read-Host @"
---

Disconnected session $sessionName created on computer $computer.

You can connect to it and retrieve its output from any session on this machine,
even after a reboot.

* If you choose to log off or reboot now, this script re-runs automatically 
  when you log back in, in order to connect to the remote session and collect its output.

* To see open sessions on the target computer on demand, run the following
  (append | Remove-PSSession to remove them):

   Get-PSSession -ComputerName $computer

---

Do you want to (L)og off, (R)eboot, (C)onnect right now, or (Q)uit (submit with ENTER)? [l/r/c/q] 
"@
   while (($response = $response.Trim()) -notin 'l', 'r', 'c', 'q')


  $autoRelaunchCmd =  
   Set-ItemProperty registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce 'ReconnectDemo' "$((Get-Command powershell.exe).Path) -noexit -command `"Set-Location '$PWD'; . '$PSCommandPath'`"" 
  

  switch ($response) 
    'q'  Write-Verbose -vb 'Aborted.'; exit 2 
    'c'  break  # resume below
    'l'  
       Write-Verbose -vb "Logging off..."
       & $autoRelaunchCmd
       logoff.exe
       exit
    
    'r' 
       Write-Verbose -vb "Rebooting..."
       & $autoRelaunchCmd
       Restart-Computer
       exit
      
  



# Getting here means that a remote disconnection session was previously created.
# Reconnect and retrieve its output.

# Implicitly reconnect to the session by name,
# and receive a job object representing the remotely running command.
# Note: Despite what the docs say, -OutTarget Job seems to be the default.
#       Use -Output Host to directly output the results of the running command.
Write-Verbose -vb "Connecting to previously created session $sessionName on computer $computer and receiving its output..."
$job = Receive-PSSession -ComputerName $computer -Name $sessionName -OutTarget Job

# Get the output from the job, timing out after a few seconds.
$job | Wait-Job -Timeout 3
$job | Remove-Job -Force # Forcefully terminate the job with the indefinitely running command.

# Remove the session.
Write-Host
Write-Verbose -Verbose "Removing remote session..."
Get-PSSession -ComputerName $computer -Name $sessionName | Remove-PSSession

【讨论】:

【参考方案4】:
function remote_nohup 
    param(
        [string]$Machine,
        [string]$Cmd
    )       
    
        $job_tstamp     = $(get-date -f MMdd_HHmm_ss)
        $job_name       = "$job_tstamp"
        $job_dir_start  = (Get-Location).Path
        $job_dir_sched  = "$env:userprofile/Documents/jobs"
        $job_file       = "$job_dir_sched/$job_name.run.ps1"
        $job_log        = "$job_dir_sched/$job_name.log"
        $job_computer   = $Machine
        $job_cmd        = $Cmd

        # Create Job File
        $job_ps1 = @(
            "`$ErrorActionPreference  = `"Stop`""
            ""
            "Start-Transcript -path $job_log -append"
            ""
            "try "
            "    write-host 'job_begin:($job_name)'"
            "    "
            "    set-location $job_dir_start -EA 0"
            "    "
            "    write-host 'job_cmd:($job_cmd)'"
            "    $job_cmd | out-host"
            ""
            "    write-host 'job_end:($job_name)'"
            ""
            "catch "
            "    `$msg = `$_"
            "    write-host  `$msg"
            "    write-error `$msg"
            ""
            "finally "
            "    Stop-Transcript"
            ""
            ""
            "Exit-PSSession"
        )
        
        try 
            New-Item -ItemType Directory -Force -EA:0 -Path $job_dir_sched | out-null
            copy-Item $remote_profile_ps1 $job_profile 
            
            write-host "Creating File: $job_file"
            $f1 = open_w $job_file -fatal
            foreach ($line in $job_ps1) 
                $f1.WriteLine($line)
                       
        
        finally 
            $f1.close()
           

        # Create Jobs Dir
        write-host "Creating remote job Directory"
        Invoke-Command -Computer $job_computer -ScriptBlock  
            New-Item -ItemType Directory -Force -EA:0 -Path $using:job_dir_sched | out-null
        
        
        # Copy Job File
        write-host "copy-Item -recurse -ToSession `$s2  $job_file $job_file"
        $s2 = New-PSSession -Computer $job_computer
        copy-Item -ToSession $s2  $job_file $job_file    
        Receive-PSSession -Session $s2
        Remove-PSSession -Session $s2

        # Create Persistent Background Job
        write-host "Submitting job to remote scheduler"
        Invoke-Command -Computer $job_computer -ScriptBlock 
            Register-ScheduledJob -RunNow -Name $using:job_name -File $using:job_file
            Exit-PSSession          
        

        # NOTE: Log file from run is placed on 
        #    remote computer under jobs dir     


function open_w 
    param([string]$path, [switch]$fatal)

    try 
        write-host "path: $path"  
        $pathfix = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path)
        $handle = [System.IO.StreamWriter]::new($pathfix, $false) #false:over-write, true:append
    
    catch 
        if ($fatal) 
            write-host -ForegroundColor Red "EXCEPTION: " + $PSItem.ToString()      
            exit 1
        
        return $null
    
    return $handle


【讨论】:

与您之前的自我回答一样,没有适当的框架和解释,不清楚您的回答如何解决您自己的问题。

以上是关于Powershell调用计算机到远程我的本地计算机重新启动后幸存下来?的主要内容,如果未能解决你的问题,请参考以下文章

远程注册表项提取程序PowerShell脚本

powershell修改远程计算机名

通过 Powershell 远程 TFS 签入

Powershell在远程计算机上使用命令行参数执行远程exe

PS 的小应用

PowerShell 复制项目到远程 - 自定义端口