workflow with multiple reboots

Tagged: 

This topic contains 12 replies, has 4 voices, and was last updated by Profile photo of kyle dee kyle dee 1 week ago.

  • Author
    Posts
  • #72782
    Profile photo of kyle dee
    kyle dee
    Participant

    Hello,
    My goal is to unjoin a domain, reboot, rename the pc, join a domain, reboot again.

    My current workflow can do each piece separately (eg it can unjoin the domain and reboot or it can rename the pc and join a domain then reboot).

    It seems like the scheduled job to resume the workflow is not running properly. After running the workflow it causes a reboot and when i check the jobs it lists my workflow_at_login job as suspended. If I "resume-job -name workflow_at_login" then the rest of the workflow completes properly. I just cant get it to run automatically at startup.

    The output of the workflow is:
    Un-joining domain
    the unjoin domain part of the workflow has run at 4:44:45_PM
    Id : 5
    State : Running
    Command : reboot-workflow
    JobStateInfo : Running
    Name : workflow_at_login
    PSBeginTime : 6/13/2017 4:44:38 PM
    PSEndTime :
    PSComputerName : localhost
    PSSourceJobInstanceId : 8c0417af-60cd-44ee-9acc-58659fcb5bd8

    *************Rename PC and Join Domain Section*****************
    Running re-join and re-name

    Any help would be greatly appreciated!

    workflow reboot-workflow {
    
    [CmdletBinding()]
    
        param
        (
        
        [Parameter(Mandatory=$true,
                       ValueFromPipelineByPropertyName=$true,
                       Position=0)]
        [string]$PCName,
    
        [Parameter(Mandatory=$true,
                       ValueFromPipelineByPropertyName=$true,
                       Position=0)]
        [System.Management.Automation.CredentialAttribute()]$Credentials
        )
        sequence {
           
            #checks if PC is joined to the domain and unjoins it
            if ((gwmi win32_computersystem).partofdomain -eq $true)
            {
                Write-Output "***************Unjoin Domain Section*****************" | Out-File -FilePath "example.txt" -Append -Force
                Write-Output "Un-joining domain" | Out-File -FilePath "example.txt"
                Remove-Computer -UnjoinDomainCredential $credentials -WorkgroupName WBI -PassThru -Force
                $time1 = get-date -Format h:mm:ss_tt
                $first = "the unjoin domain part of the workflow has run at $time1" | Out-File -FilePath "example.txt" -Append -force
                $firstjob = Get-Job | select ID,state,command,jobstateinfo,name,psbegintime,psendtime | Out-File -FilePath "example.txt" -Append -Force
                Checkpoint-Workflow
            
                ######################
                Restart-Computer -Wait
                ######################
    
            }#end if
        
            #renames PC and joins it to the domain
            Write-Output "*************Rename PC and Join Domain Section*****************" | Out-File -FilePath "example.txt" -Append -Force
            Write-Output "Running re-join and re-name" | Out-File -FilePath "example.txt" -Append -Force
            Add-Computer -DomainName Domain -Credential $Credentials -NewName $PCName -ErrorAction Inquire -Verbose -PassThru -Force 
            $time2 = get-date -Format h:mm:ss_tt
            $second = "the join domain and rename part of the workflow has run at $time2. Computer will now reboot" | Out-File -FilePath "example.txt" -Append -Force
            $secondjob = Get-Job | select ID,state,command,jobstateinfo,name,psbegintime,psendtime | Out-File -FilePath "example.txt" -Append -Force
            Checkpoint-Workflow
    
            ######################
            Restart-Computer -Wait
            ######################
    
            #cleanup jobs
            Unregister-ScheduledJob -Name Resume_Workflow
            InlineScript { Import-Module PSWorkflow }
            get-job -Name *workflow* | Remove-Job -Force
    
        }#end sequence
    }#end workflow
    
    #creates shceduled job to run at startup to resume the suspended workflow from above
    Unregister-ScheduledJob -Name Resume_Workflow
    $atstart = New-JobTrigger -AtStartup
    $options = New-ScheduledJobOption -RunElevated -Verbose
    $localUN = "$env:COMPUTERNAME\username"
    $localPwd = ConvertTo-SecureString -String "password" -AsPlainText -Force
    $localCred = New-Object System.Management.Automation.PSCredential($localUN,$localPwd)
    
    Register-ScheduledJob -Name Resume_Workflow -Credential ($localCred) -Trigger $atstart -ScriptBlock{
        Import-Module PSWorkflow
        Get-Job -Name workflow_at_login | Resume-Job -Wait
    }  -ScheduledJobOption $options 
    
    #run workflow as job
    reboot-workflow -AsJob -JobName workflow_at_login
    
    #warn users to wait for reboot
    Write-output "`nWAIT!!! DONT PRESS ANY KEYS ... REBOOT will occur shortly"
    
  • #72820
    Profile photo of Don Jones
    Don Jones
    Keymaster

    Workflow is weird. If you've left it sit in the suspend state for a while and it isn't resuming after some time, then it's "stuck" – and that's difficult to troubleshoot. Workflow isn't PowerShell; it's WIndows Workflow Foundation.

    I'm a little confused at how you're doing this, though. Once a workflow starts, the WWF engine is what keeps it going between reboots. But you've got this job going as well, which would have the effect of restarting the workflow.

    What happens if you just run the workflow, without all the job stuff?

    • #72839
      Profile photo of kyle dee
      kyle dee
      Participant

      Thanks for the update Don,

      Running the workflow without the job only completes the first portion of the workflow (the computer is removed from the domain). My workflow_at_login job is shown as running before the reboot but not shown as suspended when I check after logging back in. If I check my output file it shows that the workflow is getting stopped right at the Add-Computer command (line 40).

      Output:
      Un-joining domain
      the unjoin domain part of the workflow has run at 9:40:59_AM
      Id : 5
      State : Running
      Command : reboot-workflow
      JobStateInfo : Running
      Name : workflow_at_login
      PSBeginTime : 6/14/2017 9:40:53 AM
      PSEndTime :
      PSComputerName : localhost
      PSSourceJobInstanceId : 6faded7c-5900-4942-b4e5-4c075a4d9793
      *************Rename PC and Join Domain Section*****************
      Running re-join and re-name

      Do you know of a different way to perform this sort of rename and reboot task?

      Thanks again for your help.

    • #72856
      Profile photo of Colin Nichols
      Colin Nichols
      Participant

      I'm not sure if this is off-base at all, but would unjoining the domain affect the encryption that PowerShell uses to store the credentials? It sounds like that's probably not the case since you can resume this manually, but just a thought.

    • #72862
      Profile photo of Kiran P
      Kiran P
      Participant

      Hello,

      You can split the actions as below

      Workflow::UnJoinDomain
      Workflow::RestartComputer
      Workflow::RenameComputer
      Workflow::JoinDomain
      Workflow::RestartComputer

      Add all of these to a workflow object (pscustomobject) and save it to disk in the format of Json or Xml.

      Every time the script runs it reads the Json file and create the object with the current workflow status and maintain through out the execution, and ensure each change should be saved to Json file on the disk.

      Maintain proper action change status like beginning of the script, processing and completed, so that when the system comes back from the reboot, it picks up the exact action to be performed.

      For every reboot, create a 1-click .bat (deletes after execution) file to resume the script and add it to RunOnce key in the registry to resume the script after the reboot

        Refer the link below for 1-Click .bat file

      https://stackoverflow.com/questions/20329355/how-to-make-a-batch-file-delete-itself

      Refer the link below to run the script once right after the reboot

    • #72871
      Profile photo of kyle dee
      kyle dee
      Participant

      Hi Kiran, you mentioned you had a bit of code. Would you mind providing that as a starting point?

    • #72980
      Profile photo of Kiran P
      Kiran P
      Participant

      Hi Kyle,

      I can't really squeeze the code from our repository, because its all mess for you to understand, however I tried my level best to elaborate the same scenario with the code below, please follow the same and if you see something odd, please let me know I may help you.

      Thank you.

      Note: I have not executed the code below, this is just an example to match your scenario.

      $ConfigurationFile = Join-Path -Path $env:TEMP -ChildPath "Config_$($env:COMPUTERNAME).json";
      
      if (Test-Path -Path $ConfigurationFile) {
          $Configuration = Get-Content -Path $ConfigurationFile | ConvertFrom-Json;
      } else {
          [hashtable]$Actions = @{
              UnjoinDomain    = 'Notcompleted'
              RebootComputer01= 'Notcompleted'
              RenameComputer  = 'Notcompleted'
              JoinDomain      = 'Notcompleted'
              RebootComputer02= 'Notcompleted'
          };
          $Configuration = New-Object -TypeName psobject -Property $Actions;
      }
      
      $Configuration | Add-Member -MemberType ScriptMethod -Name SetRebootConfig -Value {
          try {
              $Command = "$($env:windir)\system32\windowspowershell\v1.0\powershell.exe -nolog -noprofile -command `"$($MyInvocation.Line)`"";
              $BatchFile = Join-Path -Path $env:TEMP -ChildPath "Resume_$($env:COMPUTERNAME).bat";
              "@ECHO OFF" | Out-File -FilePath $BatchFile -Encoding ascii;
              $Command | Out-File -FilePath $BatchFile -Encoding ascii -Append;
              "pause" | Out-File -FilePath $BatchFile -Encoding ascii -Append;
              "(goto) 2>nul & del `"%~f0`"" | Out-File -FilePath $BatchFile -Encoding ascii -Append;
              $RunOnceRegKey = 'HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce';
              $KeyValueName = 'Workflow Reboot';
              $KeyType = [Microsoft.Win32.RegistryValueKind]::String;
              $null = [Microsoft.Win32.Registry]::SetValue($RunOnceRegKey,$KeyValueName,$BatchFile,$KeyType);
              $this | ConvertTo-Json | Out-File -FilePath $ConfigurationFile -Force;
              return, 0;
          }
          catch {
              return, 1;
          };
      }
      # Dot source your script (Split your script into functions or so, like Unjoin-Domain, Reboot-Computer, Join-Domain and so on...)
      # $Mainscript = 
      . $Mainscript;
      
      if ($Configuration.UnjoinDomain -ne 'Completed') {
          Add-Member -InputObject $Configuration -MemberType NoteProperty -Name UnjoinDomain -Value 'Running' -Force;
          $RetCode = Unjoin-Computer 
          if ($RetCode -eq 0) {
              Add-Member -InputObject $Configuration -MemberType NoteProperty -Name UnjoinDomain -Value 'Completed' -Force;
          }
      }
      
      if ($Configuration.RebootComputer01 -ne 'Completed') {
          Add-Member -InputObject $Configuration -MemberType NoteProperty -Name RebootComputer01 -Value 'Completed' -Force;
          $RetCode = $Configuration.SetRebootConfig();
          if ($RetCode -eq 0) {
              Reboot-Computer
          }
      }
      
      if ($Configuration.RenameComputer -ne 'Completed') {
          Add-Member -InputObject $Configuration -MemberType NoteProperty -Name RenameComputer -Value 'Running' -Force;
          $RetCode = Change-ComputerName 
          if ($RetCode -eq 0) {
              Add-Member -InputObject $Configuration -MemberType NoteProperty -Name RenameComputer -Value 'Completed' -Force;
          }
      }
      
      if ($Configuration.JoinDomain -ne 'Completed') {
          Add-Member -InputObject $Configuration -MemberType NoteProperty -Name JoinDomain -Value 'Running' -Force;
          $RetCode = Join-Computer 
          if ($RetCode -eq 0) {
              Add-Member -InputObject $Configuration -MemberType NoteProperty -Name JoinDomain -Value 'Completed' -Force;
          }
      }
      
      if ($Configuration.RebootComputer02 -ne 'Completed') {
          Add-Member -InputObject $Configuration -MemberType NoteProperty -Name RebootComputer02 -Value 'Completed' -Force;
          $RetCode = $Configuration.SetRebootConfig();
          if ($RetCode -eq 0) {
              Reboot-Computer
          }
      }
      
      Write-Verbose $Configuration
      
    • #73003
      Profile photo of kyle dee
      kyle dee
      Participant

      Hi Kiran,

      Thank you for the help, this looks like it will solve my problem exactly!

      I'm having one problem understanding the logic here. After the reboots the .bat file is called which runs powershell with the command $Configuration.SetRebootConfig(). However, since this is after the reboot $Configuration is no longer defined and an error is thrown. How did you handle persistence for the $Configuration variable?

    • #73012
      Profile photo of Kiran P
      Kiran P
      Participant

      Hi Kyle,

      Please check the line no.4 in the code, its reading the json file.

      Thank you.

    • #73087
      Profile photo of kyle dee
      kyle dee
      Participant

      Thanks again Kiran, this was the answer to my problems!

      In case anyone else needs to use this script I did change a few things:

      There is no need for a separate RenameComputer and JoinDomain function. In JoinDomain I used the Add-Computer cmdlet which has a -NewName property which saves you time and a reboot.

      line 18 – `"$($MyInvocation.Line)`" was writing to the bat file as $Configuration.SetRebootConfig() which caused errors because after a reboot when the bat file tried to launch powershell and call the $configuration variable it was blank. I also needed to run my functions as administrator to perform Add-Computer.

      $command = "start powershell -noprofile -command `"&{start-process powershell -argumentlist '-noprofile -file c:\...pathToFile.ps1' -verb RunAs}`""
      

      line 42 and 58 – if ($RetCode -eq 0) had to be changed because it was not catching properly and so Running was not getting reset to Complete even though the function executed successfully.

      if ($RetCode.Contains(0)) 
      
  • #72863
    Profile photo of Kiran P
    Kiran P
    Participant

    You could even save the credentials to JSon file and can retrieve it whenever it requires. it will store in the format of SecureString.

  • #72866
    Profile photo of Don Jones
    Don Jones
    Keymaster

    Yeah, I think the ultimate thing here is that a properly written workflow should be able to checkpoint its progress after each Activity, and then resume automatically even if an activity triggers a restart. So long as each of your tasks is a discrete activity, and not all in one giant InlineScript, it -should- work. If it isn't, that's the thing to troubleshoot, I think, versus trying to re-engineer the resume capability through workarounds. Unfortunately, WWF can be hard to troubleshoot, which is why I'm not a fan of it.

    • #72868
      Profile photo of kyle dee
      kyle dee
      Participant

      Thank you both, I'll try Kiran's suggestion and report back later with the results.

You must be logged in to reply to this topic.