39

I'm looking for a way to continue a Powershell script from where it left off after calling a reboot in the script. For example, I am building a DC via Powershell automation, and after renaming the PC to TESTDC01, need to reboot, but after the reboot, continue with the script to go on to dcpromo etc.

Is this possible?

Cheers!

PnP
  • 3,133
  • 17
  • 62
  • 95
  • 6
    In the past the way that I have done this in scripts is to set a runonce key in the registry that runs the script with a parameter that it should do the post reboot portion of the script. – EBGreen Mar 01 '13 at 21:05
  • How would I specify what part of the script to continue from? – PnP Mar 01 '13 at 21:08
  • Late to the party but one way is run `script1.ps1` which before rebooting the server adds a RunOnce regkey to run `script2.ps1` but I'd caution to add some sleep to allow the server to fully boot before attempting to run a script. – user4317867 Feb 28 '16 at 21:19

7 Answers7

39

There is a great article on TechNet from the Hey, Scripting Guy series that goes over a situation very similar to what you are describing: Renaming a computer and resuming the script after reboot. The magic is to use the new workflows that are part of version 3:

workflow Rename-And-Reboot {
  param ([string]$Name)
  Rename-Computer -NewName $Name -Force -Passthru
  Restart-Computer -Wait
  Do-MoreStuff
}

Once the workflow has been declared (you don't assign it to a variable), you can call it as though it were a regular cmdlet. The real magic is the -Wait parameter on the Restart-Computer cmdlet.

Rename-And-Reboot PowerShellWorkflows

Source: https://devblogs.microsoft.com/scripting/powershell-workflows-restarting-the-computer/

If PowerShell v3 or later isn't an available choice, you could break your existing script into multiple smaller scripts and have a master script that runs at startup, checks some saved state somewhere (file, registry, etc.), then starts executing a new script to continue on where appropriate. Something like:

$state = Get-MyCoolPersistedState
switch ($state) {
  "Stage1" { . \Path\To\Stage1.ps1 ; break }
  "Stage2" { . \Path\To\Stage2.ps1 ; break }
  "Stage3" { . \Path\To\Stage3.ps1 ; break }
  default { "Uh, something unexpected happened" }
}

Just be sure to remember to set your state appropriately as you move through your smaller scripts.

Brandon Bonds
  • 203
  • 1
  • 9
Goyuix
  • 23,614
  • 14
  • 84
  • 128
  • 5
    When I try to use "Restart-Computer -Wait", I get an exception: "Cannot wait for the local computer to restart. The local computer is ignored when the Wait parameter is specified." – BrainSlugs83 Nov 14 '14 at 19:46
  • 2
    The real trick to the articles' restartability, especially when used for local execution... is that the script creates a scheduled task to resume... you can do the same thing with your own scripts, workflow or not. – Scott Brickey Sep 07 '17 at 14:24
  • 3
    I think `restart-computer -wait` is only for remote computers. – js2010 Nov 02 '19 at 20:36
18

The above answer is true, but it will only apply to remote execution of powershell scripts. According to the windows web portal, the way to have your locally running script resume from where it left off after the local machine restarted is like so:

workflow Resume_Workflow
{
    .....
    Rename-Computer -NewName some_name -Force -Passthru
    Restart-Computer -Wait
    # Do some stuff
    .....
}
# Create the scheduled job properties
$options = New-ScheduledJobOption -RunElevated -ContinueIfGoingOnBattery -StartIfOnBattery
$secpasswd = ConvertTo-SecureString "Aa123456!" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ("WELCOME\Administrator", $secpasswd)
$AtStartup = New-JobTrigger -AtStartup

# Register the scheduled job
Register-ScheduledJob -Name Resume_Workflow_Job -Trigger $AtStartup -ScriptBlock ({[System.Management.Automation.Remoting.PSSessionConfigurationData]::IsServerManager = $true; Import-Module PSWorkflow; Resume-Job -Name new_resume_workflow_job -Wait}) -ScheduledJobOption $options
# Execute the workflow as a new job
Resume_Workflow -AsJob -JobName new_resume_workflow_job

Note that the [System.Management.Automation.Remoting.PSSessionConfigurationData]::IsServerManager flag should be set to true only if the workflow actions are meant to execute locally after the restart.

adaml
  • 341
  • 3
  • 6
  • 6
    What are the credential(s) used for? I tested your script and I can see the job created but it is in suspended state after the reboot: Get-Job | Format-Table -AutoSize – codea Sep 09 '16 at 17:34
  • in scriptblock, why we need `Import-Module PSWorkflow`? – CSJ Oct 10 '18 at 06:35
  • 1
    @CSJ - According to the attached documentation: "Without this module, the Job cmdlets do not recognize workflow jobs". Simply put, the workflow would not be recognized after restart without the import. – adaml Oct 11 '18 at 08:48
4

Check out PS 3.0 with Workflows. I haven't worked with them yet but they are suppose to recover from restarts.

E.V.I.L.
  • 2,120
  • 13
  • 14
3

If this helps anyone, what I do is reboot the server then loop until \\server\c$ goes offline. Next I loop While (-not(Test-path "\\$server\c$")) to confirm the server is again back online and simply continue my script.

This code is working but could definitely be improved. It generates a CSV log of the servers being rebooted. It should also work in PowerShell v2 and newer.

Param([Parameter(Mandatory=$true)][string]$server)
 $ErrorActionPreference = "SilentlyContinue"

Try{
 $LastReboot = Get-EventLog -ComputerName $server -LogName system | Where-Object {$_.EventID -eq '6005'} | Select -ExpandProperty TimeGenerated | select -first 1

 (Invoke-WmiMethod -ComputerName $server -Path "Win32_Service.Name='HealthService'" -Name PauseService).ReturnValue | Out-Null

Restart-Computer -ComputerName $server -Force

#New loop with counter, exit script if server did not reboot.
$max = 20;$i = 0
 DO{
 IF($i -gt $max){
        $hash = @{
             "Server" =  $server
             "Status" = "FailedToReboot!"
             "LastRebootTime" = "$LastReboot"
             "CurrentRebootTime" = "FailedToReboot!"
          }
$newRow = New-Object PsObject -Property $hash
 $rnd = Get-Random -Minimum 5 -Maximum 40
 Start-Sleep -Seconds $rnd
 Export-Csv D:\RebootResults.csv -InputObject $newrow -Append -Force
    "Failed to reboot $server"
    exit}#exit script and log failed to reboot.
    $i++
"Wait for server to reboot"
    Start-Sleep -Seconds 15
}#end DO
While (Test-path "\\$server\c$")

$max = 20;$i = 0
 DO{
 IF($i -gt $max){
        $hash = @{
             "Server" =  $server
             "Status" = "FailedToComeOnline!"
             "LastRebootTime" = "$LastReboot"
             "CurrentRebootTime" = "FailedToReboot!"
          }
$newRow = New-Object PsObject -Property $hash
 $rnd = Get-Random -Minimum 5 -Maximum 40
 Start-Sleep -Seconds $rnd
Export-Csv D:\RebootResults.csv -InputObject $newrow -Append -Force
    "$server did not come online"
    exit}#exit script and log failed to come online.
    $i++
    "Wait for [$server] to come online"
    Start-Sleep -Seconds 15
}#end DO
While (-not(Test-path "\\$server\c$"))

$CurrentReboot = Get-EventLog -ComputerName $server -LogName system | Where-Object {$_.EventID -eq '6005'} | Select -ExpandProperty TimeGenerated | select -first 1
    $hash = @{
             "Server" =  $server
             "Status" = "RebootSuccessful"
             "LastRebootTime" = $LastReboot
             "CurrentRebootTime" = "$CurrentReboot"
              }

$newRow = New-Object PsObject -Property $hash
 $rnd = Get-Random -Minimum 5 -Maximum 40
 Start-Sleep -Seconds $rnd
Export-Csv D:\RebootResults.csv -InputObject $newrow -Append -Force

}#End Try.

Catch{
 $errMsg = $_.Exception
 "Failed with $errMsg"
}
user4317867
  • 2,397
  • 4
  • 31
  • 57
2

Needed to reboot my local computer and continue script after. Tried solution from @adaml but I just couldn't get the scheduled job (that ran after the reboot) to find the suspended workflow-job that should be resumed. Hence, it remained suspended.

Get-Job didn't return the job no matter credentials or elevated. Another strange thing was that if I ran the workflow by marking code in Powershell ISE and ran the section with F8, the job never got suspended... Had to run the whole script with F5 or called it from somewhere else.

To get the resume of the job working, I had to register a scheduled-Task instead of a scheduled-Job:

workflow test-restart {
    Write-Output "Before reboot" | Out-File  C:/Log/t.txt -Append

    Restart-Computer -Wait

    Write-Output "$Now2 After reboot" | Out-File  C:/Log/t.txt -Append
}

$PSPath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
$Args = '-NonInteractive -WindowStyle Hidden -NoLogo -NoProfile -NoExit -Command "& {Import-Module PSWorkflow ; Get-Job | Resume-Job}"'
$Action = New-ScheduledTaskAction -Execute $PSPath -Argument $Args
$Option = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -WakeToRun
$Trigger = New-JobTrigger -AtStartUp -RandomDelay (New-TimeSpan -Minutes 5)
Register-ScheduledTask -TaskName ResumeJob -Action $Action -Trigger $Trigger -Settings $Option -RunLevel Highest


test-restart -AsJob
Andreas
  • 775
  • 2
  • 11
  • 20
  • i tried this snipped but added timestamps to the log. `Write-Output "$(Get-Date -Format "dd.MM.yyyy HH:mm:ss"): Before reboot" | Out-File C:/Temp/t.txt -Append` Before and after have the same timestamp to the second. The last write time one the log file is also the same to the second. – Steffen Bauer Apr 30 '21 at 13:45
1

Do it remotely:

Rename-Computer -ComputerName $computer -NewName "TESTDC01" -DomainCredential $domain\$username -Force -Restart

And continue your script from that 8)

ahmoreish
  • 180
  • 3
  • 8
0

Thinking out loud, for benefit of people doing this in AWS/Your Cloud Provider...

I've got the same issue with AWS instances, that will need renaming to the client standard server name, +domain join, +cert install, +Tentacle install. I am going to have the server startup script put into the user_data field of the instance via Terraform.

I'll set EC2 instance "Renamed" tags in PS on the instance, after each part of the setup, so when the server restarts after rename, the script will look for IF "Renamed = Done" tag value, and skip this part next time the instance boots. Same logic for DomainJoined, TentacleInstalled tags etc.

Code to read the tags is like:

$instanceId = (Invoke-RestMethod -Method Get -Uri http://169.254.169.254/latest/meta-data/instance-id)
$instance   = ((Get-EC2Instance -region $region -Instance $instanceId).RunningInstance)
$myInstance = $instance | Where-Object { $_.InstanceId -eq $instanceId }
$Renamed    = ($myInstance.Tags | Where-Object { $_.Key -eq "Renamed" }).Value
Matthieu
  • 2,736
  • 4
  • 57
  • 87
Faye Smelter
  • 131
  • 9