workflow with multiple reboots

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"

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?

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.

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.

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

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

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.

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

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

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

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?

Hi Kyle,

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

Thank you.

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))